Comunidad de diseño web y desarrollo en internet

Bases de Datos con Flash y AMFPHP

Este tutorial es para de alguna manera quitar el miedo que tienen los programadores nuevos y de nivel medio de ActionScript y PHP de desarrollar proyectos con AMFPHP, no abarcaré todo sobre esto pero trataré de darle un buen empuje hacia el aprovechamiento de Flash Remoting, y lo haré con un ejemplo de las típicas operaciones sobre Base de Datos (insertar, consulta, modificar y eliminar).

Requerimientos para seguir el tutorial:

Preparación de la Base de Datos:

Crea una base de datos para el ejemplo (o usa la que tengas si es de alojamiento gratuito), en mi caso llamaré a la base de datos AMFPHP.

Crea una tabla con 3 campos, en mi caso le di el nombre de prueba, los campos deberán tener esta descripción:

Nombre
Tipo de Dato
Características adicionales
id
bigint
not null, auto_increment, clave primaria
nombre
text
null
comentarios
text
null

Consideraciones:

  • En el ejemplo se utilizo las funciones escape y unescape de flash para la codificación de los datos, así que la forma en que se guarden los datos en la Base de Datos no será igual a como se escriben en la aplicación, por poner un ejemplo un espacio será representado en la Base de Datos con un %, otro caso sería el punto "." que estará representado por la secuencia %2E, esto se hizo con el fin de solventar los problemas de los caracteres especiales como letras acentuadas, tildes, etc. Así que si los campos nombre y comentarios de la tabla son de tipo varchar de 30 caracteres solo se guardarán los caracteres que alcancen y no precisamente los 30 caracteres que escribamos en la aplicación por lo ya explicado.
  • Si usas PHP5, por favor instala AMFPHP Milestone 2 (o superior), en este caso, el uso de escape y unescape de la consideración anterior es redundante, al igual que el uft8_encode en el script php, ya que dicha versión de AMFPHP trabaja por defecto con la codificación UTF-8 y a diferencia de la versión 0.9 trabaja con php5.

Preparación del Escenario:

Nos apoyaremos de las herramientas que nos provee Flash como son los componentes y unos textos dinámicos para crear la interfaz de nuestra pequeña aplicación, contaremos con:

Tipo
Cantidad
Nombre de Instancia(s)
DataGrid
1
datagrid
Button
6
btnConsultar, btnInsertar, btnModificar, btnEliminar, btnSi, btnNo
TextInput
3
idtxt, nombretxt y comentariostxt.
Dynamic Text
3
estadotxt, cabeceratxt y preguntatxt.
Static Text
2
----

Razón de cada uno:

datagrid: repositorio de los registros de la Base de Datos.
btnConsultar: hará un consulta a la base de datos.
btnInsertar, btnModificar y btnEliminar: prepararán los TextInput´s para la consulta que se vaya hacer sobre la base de datos.
btnSi, btnNo: servirán para confirmar o cancelar la acción sobre la base de datos.
idtxt, nombretxt y comentariostxt: contendrán los datos de la celda seleccionada en el datagrid, en el caso de que sea presionado el boton btnInsertar estos se formatearán.
estadotxt: avisará si la consulta fue realizada exitosamente o no.
preguntatxt: formulará una pregunta correspondiente para la confirmación de la acción.


Nuestra interfaz quedará así

En el desarrollo de nuestro ejemplo tendremos que codificar nuestro servicio (backend), que no es más que una clase PHP, y por supuesto la creación de nuestro swf con el código de ActionScript para comunicarnos con el servicio y procesar la información que nos mande este. Empezaremos con nuestro servicio, contendrá un método para cada operación (insertar, modificar, eliminar y consultar) sobre la Base de Datos, estaríamos hablando de 4 solo con estos, e implementaremos un 5 método dedicado a ejecutar las consultas, echemosle un vistazo al código del servicio.

Nota: este archivo debe estar guardado en la carpeta services que creamos al instalar AMFPHP en nuestro servidor.

Código PHP (misquerys.php)

<?php

class misquerys{

        function misquerys (){

                $this->methodTable = array(
                        "insertar" => array (
                        "description" => "inserta un registro",
                        "access" => "remote",
                        "arguments" => array("nombre","comentarios")
                ),
                        "eliminar" => array (
                        "description" => "elimina un registro",
                        "access" => "remote",
                        "arguments" => array("id")
                ),
                        "modificar" => array (
                        "description" => "modifica un registro",
                        "access" => "remote",
                        "arguments" => array("id")
                ),
                        "consulta" => array (
                        "description" => "consulta n campos",
                        "access" => "remote"

                ),
                        "query" => array (
                        "description" => "ejecuta un query a MYSQL",
                        "access" => "private",
                        "arguments" => array ("sql","accion")
                        )
                );
        }
        
        function insertar($nombre,$comentarios){
                $nombre = utf8_encode($nombre);
                $comentarios = utf8_encode($comentarios);
                $sql= "INSERT INTO prueba (id,nombre,comentarios) VALUES ('','".$nombre."','".$comentarios."')";
                return $this->query($sql,1); //retornamos el id de nuestro registro insertado

        }
        
        function eliminar ($id){
                $sql= "DELETE FROM prueba WHERE id='".$id."' LIMIT 1";
                return $this->query($sql,0);
        }
        
        function modificar ($id,$nombre,$comentarios){
                $nombre = utf8_encode($nombre);
                $comentarios = utf8_encode($comentarios);
                //creamos la cadena de nuestra sentencia(query) SQL

                $sql = "UPDATE `prueba` SET `nombre` ='".$nombre."', `comentarios` ='".$comentarios."' WHERE `id` =".$id." LIMIT 1;";
                return $this->query($sql,0);
        }
        
        function consulta (){
                $sql = "SELECT * FROM prueba ORDER BY id ";
                return $this->query($sql,0);
        }
        
        function query ($sql, $accion){
                $conex= mysql_connect("tuhost","user","password") or die("no se puede conectar porque ".mysql_error());
                mysql_select_db("nombredebasededatos");
                $result= mysql_query($sql,$conex);
                if ($accion && result){
                        $result= mysql_insert_id();
                }
                mysql_close($conex);
                return $result;
        }

}

?>

Explicación:

class misquerys ( : igual al nombre del archivo, exceptuando la extensión .php
function misquerys () : constructor de la clase, identificador igual al nombre de la clase.
$this->methodTable = array( : para definir los métodos que tendrá nuestra clase.
"insertar" => array ( : nombre del método insertar de nuestra clase, al igual que las líneas similares.
"description" => "inserta un registro", : comentario del método, describimos que hace dicho método.
"access" => "remote", : tipo de acceso del método, posibles valores: public, remote y private.
"arguments" => array("nombre","comentarios") : parámetros del método.

Una descripción detallada de cada método:

Nombre
Parámetros
Explicación
Valor de Retorno
insertar
$nombre, $comentarios Inserta los parámetros a los respectivos campos de la tabla, codificando con utf8_encode los parámetros para entender la codificación que hace flash con el escape para mandarle los parámetros y crea la respectiva sentencia SQL. Retorna el id del registro insertado en tal caso de que se haya hecho exitosamente.
eliminar
$id Crea la sentencia SQL DELETE con la condición de que id sea el que recibio. Retorna 1 si fue exitosa la operación o 0 en caso contrario.
modificar
$id, $nombre, $comentarios Crea la sentencia SQL UPDATE, buscando el registro cuyo campo id corresponda con $id y actualiza los campos nombre y comentarios con los respectivos parámetros. Retorna 1 si fue exitosa la operación o 0 en caso contrario.
consulta
ninguno Crea la sentencia SQL SELECT para seleccionar todos los registros de la tabla. Retorna un RecordSet con los registros.
query
$sql, $accion $sql será la sentencia SQL que ejecutará, $accion es para hacer una excepción de que si la sentencia es un INSERT.
  • Para consulta: retorna un recordSet.
  • Para eliminar y modificar: 1 o 0 en caso de que haya sido exitosa la operación.
  • Para insertar: hay una excepción donde el parámetro $accion valdrá 1 al invocar la función query y es para indicar que retornará el id del último registro insertado.

Espero que hayan entendido hasta aquí, no esta díficil, cualquier cosa vuelve a leerlo, sino continua con el código de ActionScript:

import mx.remoting.NetServices;
import mx.remoting.Connection;

mx.remoting.debug.NetDebug.initialize();
NetServices.setDefaultGatewayUrl("http://localhost/remoting5/gateway.php");
conexion = NetServices.createGatewayConnection();
capturaRespuesta = new Object();
servicio = conexion.getService("misquerys",capturaRespuesta);
	capturaRespuesta.onStatus = function (data){
	trace("Fuck un error, por:" +data.description);
}

Las 3 primeras líneas importamos las librerias básicas para trabajar con remoting y lo inicializamos.

Las siguientes 2 líneas nos conectamos a nuestro gateway diciéndole cual es la URL absoluta y creamos una variable para crear la conexión a nuestro gateway.

Las otras 2 líneas creamos un objeto que nos servirá para escuchar al servicio, esto lo hacemos seleccionando el servicio con la variable conexión que creamos previamente.

La última función querrás que nunca sea invocada, pero que más hay que implementarla porque ella nos avisará si hay un error al conectar el servicio u otros errores, data.description nos detallará el error en caso de haber ocurrido.

capturaRespuesta.consulta_Result = function (data){
	var proveedor:Array = new Array();
	btnInsertar.enabled=true;

	if (data){
		if (data.getLength()){
			for (i=0; i<data.getLength();i++){
				proveedor.addItem({id:data.getItemAt(i).id,nombre:unescape(data.getItemAt(i).nombre),comentarios:unescape(data.getItemAt(i).comentarios)});
			}
			datagrid.dataProvider= proveedor;
			datagrid.getColumnAt(0).width=50;
			datagrid.getColumnAt(1).width=192.5;
			datagrid.getColumnAt(2).width=192.5;
			datagrid.selectedIndex = 0;
			idtxt.text = datagrid.selectedItem.id;
			nombretxt.text = datagrid.selectedItem.nombre;
			comentariostxt.text = datagrid.selectedItem.comentarios;
			btnModificar.enabled=btnEliminar.enabled=true;
			estadotxt.text="Consulta completada";
		} else {
			btnModificar.enabled=btnEliminar.enabled=false;
		estadotxt.text="No hay registro que mostrar";
		}
	} else {
		estadotxt.text="Ocurrio un error en la consulta";
	}
}        

Aquí empezamos a escuchar a nuestro servicio, nótese la secuencia del nombre de la función, primero el nombre del objeto que creamos previamente, seguido de un punto, luego el nombre del método que invocamos de nuestro servicio, despues un _ y por último Result, así que en esta función escucharíamos lo que nos retorna el método consulta de nuestro servicio, ¿cierto? Pues sí, y el parámetro contendrá el valor de lo que nos retorno nuestro método, en este caso un RecordSet.

Ahora explicaré que hago aquí, creo un array y lo lleno con los valores del RecordSet para poder decodificarlo con unescape, este array se lo asigno al datagrid en tal caso de que haya por lo menos un registro, en caso contrario deshabilitaremos los botones eliminar y modificar ya que si no hay registro no podremos realizar estas acciones .

capturaRespuesta.insertar_Result = function (data){
	mostrarComponentes(false); 
	if (data){
		datagrid.addItem({ id: data, nombre: nombretxt.text, comentarios: comentariostxt.text });
		datagrid.getColumnAt(0).width=50;
		datagrid.getColumnAt(1).width=192.5;
		datagrid.getColumnAt(2).width=192.5;
		datagrid.selectedIndex=datagrid.getLength()-1;
		idtxt.text = datagrid.selectedItem.id;
		nombretxt.text = datagrid.selectedItem.nombre;
		comentariostxt.text = datagrid.selectedItem.comentarios; 
		estadotxt.text = "La inserción se realizó exitosamente"; 
	} else {
		estadotxt.text = "La inserción no fue completada, intente de nuevo";
	}
}

Escuchamos al método insertar, si data es diferente de 0, es decir, tenemos un id, es porque se realizó la operación exitosamente, entonces insertamos nuestro registro al datagrid con el nuevo id y los valores que tenemos en los TextInput´s de nombrestxt y comentariostxt porque esto son los que insertamos, por último dejamos seleccionado el registro(o item) que acabamos de insertar y asignamos los valores del item seleccionado a los TextInput´s correspondiente para que estos no queden vacios o con valores corruptos.

Nota: digo corrupto porque cuando presionamos el boton de insertar(btnInsertar) alteramos el textInput idtxt asignandole ## y no podemos hacer una operación de modificar o eliminar con ese valor.

capturaRespuesta.modificar_Result = function (data){
	mostrarComponentes(false);
	if (data){
		datagrid.editField(datagrid.selectedIndex,"nombre",unescape(nombretxt.text));
		datagrid.editField(datagrid.selectedIndex,"comentarios",unescape(comentariostxt.text));
		estadotxt.text = "La modificacón se realizó exitosamente";
	} else {
		estadotxt.text = "La modificación no se realizo, intente de nuevo";
	}
}

Aqui data es 1 si la operación de modificar fue exitosa, por ende alteramos el item seleccionado del datagrid con los valores del TextInput´s nombretxt y comentariostxt, idtxt no lo utilizamos porque necesitamos saber la posición en el datagrid no el número de id, si en tal caso data es 0 entonces emitimos un mensaje de error, por cierto, en las funciones anteriores tambien lo hacemos.

Nota: valor de id es diferente al índice del item seleccionado del datagrid .


capturaRespuesta.eliminar_Result = function (data){
	mostrarComponentes(false); 
	if (data){
		estadotxt.text = "El registro se elimino correctamente";
		aux = datagrid.selectedIndex;
		datagrid.removeItemAt(datagrid.selectedIndex);

		//una excepcion si el datagrid se queda vacio 
		//deshabilitamos los botones modificar y eliminar
		if (datagrid.getLength()==0){
			btnModificar.enabled=btnEliminar.enabled=false;
		} else {
			(aux!=0)?datagrid.selectedIndex=aux-1:datagrid.selectedIndex=0;
			idtxt.text=datagrid.selectedItem.id;
			nombretxt.text=datagrid.selectedItem.nombre;
			comentariostxt.text=datagrid.selectedItem.comentarios;
		}
	} else{
		estadotxt.text = "El registro no se elimino, operación falló"; 
	}
}

Aqui si data es 1 entonces procedemos a eliminar el item seleccionado del datagrid, hay una excepción aquí, puede ser que el datagrid quede vacío en tal caso de que esto ocurra debemos deshabilitar los botones de modificar(btnModificar) y eliminar(btnEliminar), ya que no podremos realizar estas operaciones con el datagrid vacío, si esto no ocurre verificaremos una cosa más, que si el item eliminado no es el primer item entonces dejamos seleccionados el item previo al que se elimino, si por casualidad es el primero entonces dejamos seleccionado el item que tomo su lugar . Bueno si data es 0 es porque no se elimino el registro y emitimos nuestra advertencia.

datagrid.addEventListener("change",seleccion);
function seleccion (campoSelected){
		idtxt.text= campoSelected.target.selectedItem.id;
		nombretxt.text= campoSelected.target.selectedItem.nombre;
		comentariostxt.text= campoSelected.target.selectedItem.comentarios;
}

Aqui añadimos un listener al datagrid, que nos indicará cuando el usuario este seleccionando un item y así poder asignar los valores del item seleccionado los TextInput correspondientes.

var alClick:Object = new Object();
var accion:Number;
alClick.click = function (boton){

	switch(boton.target){
		case btnConsultar: 
			servicio.consulta();
			trace("consultar"); 
			break;

		case btnInsertar: 
			accion = 1;
			idtxt.text="##";
			preguntatxt.text="¿Desea insertar este registro?";
			nombretxt.text="";
			comentariostxt.text="";
			nombretxt.editable=true;
			comentariostxt.editable=true; 
			mostrarComponentes(true); 

			break;
		case btnModificar: 
			accion = 2;
			preguntatxt.text="¿Desea modificar este registro?";
			nombretxt.editable=true;
			comentariostxt.editable=true;
			mostrarComponentes(true); 

			break;
		case btnEliminar: 
			accion = 3; 
			preguntatxt.text="¿Desea eliminar este registro?";
			nombretxt.editable=false;
			comentariostxt.editable=false;
			mostrarComponentes(true);

			break;
		case btnSi:
			switch(accion){
				case 1: 
					servicio.insertar(escape(nombretxt.text), escape(comentariostxt.text));

					trace("confirmo insertar");
					break;
				case 2: //modificar id, nombre, comentarios
					servicio.modificar(idtxt.text,escape(nombretxt.text),escape(comentariostxt.text));

					trace ("confirmo modificar");
					break;
				case 3:
					servicio.eliminar(idtxt.text);
					break;
			}
			break;

		case btnNo:
			mostrarComponentes(false);
			idtxt.text = datagrid.selectedItem.id;
			nombretxt.text = datagrid.selectedItem.nombre;
			comentariostxt.text = datagrid.selectedItem.comentarios; 
			modificar.enabled=eliminar.enabled=true;

			break;
	}
}
btnConsultar.addEventListener("click",alClick);
btnInsertar.addEventListener("click",alClick);
btnModificar.addEventListener("click",alClick);
btnEliminar.addEventListener("click",alClick);
btnSi.addEventListener("click",alClick);
btnNo.addEventListener("click",alClick);

La variable(un objeto) alClick será para añadirle un listener a todos los botones y así poder saber que boton se ha pulsado, y la variable accion es para saber que boton se pulso y que necesite una confirmación porque se va alterar la base de datos, por supuesto estos botones serían los de insertar(btnInsertar), modificar(btnModificar) y eliminar (btnEliminar) .

Una descripcion más detallada de esta función:

Boton(Nombre de Instancia)
Descripción
Consultar(btnConsultar)
  • Invoca al método consulta del servicio.
Insertar(btnInsertar)
  • Le asigna a la variable 1 que será como este se identifique en la confirmación.
  • Le asigna a preguntatxt la pregunta apropiada.
  • Formatea los TextInput, a idtxt le asigna ## porque este dato no lo podrá asignar el usuario para evitar errores al intentar de ingresar un id ya existente, a los Textinputs nombretxt y comentariostxt los borra y les dice que se pueden editar asignándole true a la propiedad editable.
Modificar(btnModificar)
  • Le asigna a la variable 2 que será como este se identifique en la confirmación.
  • Le asigna a preguntatxt la pregunta apropiada.
  • La propiedad editable de los TextInput nombretxt y comentariostxt le asignamos true, al igual como hacemos en insertar, para que el usuario lo pueda editar.
Eliminar(btnEliminar)
  • Le asigna a la variable 3 que será como este se identifique en la confirmación.
  • Le asigna a preguntatxt la pregunta apropiada.
  • Los TextInputs nombretxt y comentariostxt no se podrán editar aquí, por lo tanto la propiedad editable le asignamos false.
Si (btnSi)
  • Verifica el valor de la variable acción para saber que acción se va realizar, dependiendo de este valor invocaremos al método correspondiente, por ejemplo, si es igual a 1 invocaremos al método insertar de nuestro servicio.
No (btnNo)
  • Aqui cancelamos la acción y hay algo particular que si el boton que pidió la confirmación fue insertar entonces el TextInput idtxt quedo corrupto, por esto necesitamos volverle asignar los valores del item seleccionado en el datagrid y así evitamos que se ejecute una acción indebida, por ejemplo, una modificación con un id igual ##.

Bueno ahora hay una línea que se veía mucho en los códigos previos la de mostrarComponentes, pues esta es una función que dependiendo del valor del parámetro pondrá visible o no visible los TextInput y los botones de confirmacion(btnSi y btnNo), de habilitar o deshabilitar el datagrid y los botones de las consultas, en tal caso de que se esté esperando la confirmación para evitar enredos entre cambios de opciones y valores corruptos en los Textinputs para enviar a los servicios, la famosa función es esta:

function mostrarComponentes(booleano:Boolean){
	datagrid.enabled=!booleano;
	btnConsultar.enabled=!booleano;
	btnInsertar.enabled=!booleano;
	btnEliminar.enabled=!booleano;
btnModificar.enabled=!booleano;
cabeceratxt._visible=booleano;
idtxt._visible = booleano;
nombretxt._visible = booleano;
comentariostxt._visible = booleano;
preguntatxt._visible = booleano;
btnSi._visible=booleano;
btnNo._visible=booleano;
}

Ahora para terminar el ejemplo, invocamos a mostrarComponentes con false para que no sea vean al inicio de la pequeña aplicación los TextInput´s ni los botones de confirmación hasta que no se pulse una opción que lo requiera, forzaremos a que se haga una consulta para el usuario vea los datos de la base de datos, claro si los hay, deshabilitamos los botones de las acciones (insertar, modificar y eliminar) para que el usuario no ejecute ninguna de ellas mientras hacemos la consulta y algo muy importante al TextInput idtxt lo deshabilitamos para que no se pueda editar nunca para evitar errores, con estas líneas logramos todo esto.

mostrarComponentes(false);
estadotxt.autoSize="left";
estadotxt.text="Consultando";
btnInsertar.enabled=false;
btnEliminar.enabled=false;
btnModificar.enabled=false;
idtxt.enabled=false;
servicio.consulta();        

Conclusión

Hay una cosa que hay que tener muy en cuenta, el hecho de saber desarrollar con Flash Remoting no quiere decir que nos debamos de olvidar de la clase LoadVars de Flash, esta última es una muy buena opción para aplicaciones que no impliquen un gran cantidad de registros en la Base de Datos, aplicaciones multiusuarios, entre otras cosas, por ejemplo, sería absurdo utilizar Flash Remoting solo para hacer un contador de visitas o el script de cuantos usuarios hay conectados en un site, es decir, debemos analizar muy bien el proyecto antes de empezar a desarrollarla con una herramienta solo porque una es mejor que otra, en tiempo de respuesta y manipulación de datos, etc.

Espero que hayan entendido todo y sino no está demás volver a leer, intente hacerlo con un lenguaje no técnico para la comprensión de todos.

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Descargar Archivo

Publica tu comentario

El autor de este artículo ha cerrado los comentarios. Si tienes preguntas o comentarios, puedes hacerlos en el foro

Entra al foro y participa en la discusión

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate