Sistema de Gastos en Flex con AMFPHP y MySQL

FlexEn este tutorial veremos cómo acceder a los registros de una base de datos MySQL desde Flex utilizando AMFPHP. Realizaremos un pequeño Sistema de Gastos en el cual haremos inserciones a la base de datos, consultas, modificaciones y eliminación de registros. Si no sabes qué es y para qué sirve el AMFPHP, visita nuestro tutorial de Introducción a AMFPHP.

En esta ocasión no veremos a detalle todo el código que involucra el sistema; sin embargo, puedes encontrar al final de esta entrada los archivos fuentes.
El sistema funciona de la siguiente manera:
- El usuario primero da de alta tipo de monedas en la Sección de Divisas (Su nombre y símbolo).
- Posteriormente el usuario puede dar de alta servicios en la Sección de Servicios (valga la redundancia). Estos servicios son opcionales y pueden seleccionarse cuando un usuario introduce un gasto en el sistema. Ejemplo de servicios: teléfono, gas, luz, educación, restaurantes, entretenimiento, etc.
- Dentro del sistema, el usuario puede dar de alta gastos e ingresos. Estos gastos o ingresos requieren de la fecha, cantidad y divisa con la cual se realizó la operación. De manera opcional se puede introducir un concepto (descripción) y se puede seleccionar un servicio (si se trata de un gasto).

Comenzaremos con el script de nuestra base de datos:

SQL:
  1. # Host: localhost    Database: codmetr_gastos
  2. # ------------------------------------------------------
  3. # Server version 4.1.22-community-nt
  4.  
  5. #
  6. # Table structure for table tbldivisa
  7. #
  8. CREATE TABLE `tbldivisa` (
  9.   `INTNUMDIVISA` int(11) NOT NULL AUTO_INCREMENT,
  10.   `STRNOMBRE` varchar(30) NOT NULL DEFAULT '',
  11.   `STRSIMBOLO` varchar(5) character SET utf8 DEFAULT NULL,
  12.   PRIMARY KEY  (`INTNUMDIVISA`)
  13. ) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
  14.  
  15. #
  16. # Table structure for table tbloperacion
  17. #
  18. CREATE TABLE `tbloperacion` (
  19.   `INTNUMOPERACION` int(11) NOT NULL AUTO_INCREMENT,
  20.   `DTMFECHA` date NOT NULL DEFAULT '0000-00-00',
  21.   `STRCONCEPTO` varchar(100) DEFAULT NULL,
  22.   `NUMCANTIDAD` decimal(10,2) DEFAULT NULL,
  23.   `INTNUMDIVISA` int(11) NOT NULL DEFAULT '0',
  24.   `INTNUMSERVICIO` int(11) DEFAULT NULL,
  25.   `INTOPERACION` int(11) NOT NULL DEFAULT '0',
  26.   PRIMARY KEY  (`INTNUMOPERACION`)
  27. ) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
  28.  
  29. #
  30. # Table structure for table tblservicio
  31. #
  32. CREATE TABLE `tblservicio` (
  33.   `INTNUMSERVICIO` int(11) NOT NULL AUTO_INCREMENT,
  34.   `STRNOMBRE` varchar(50) DEFAULT NULL,
  35.   PRIMARY KEY  (`INTNUMSERVICIO`)
  36. ) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;

Del código anterior podemos observar que tenemos una tabla tbldivisa, en la cual almacenamos los tipos de moneda que utilizamos. Una tabla tblservicio donde guardamos los servicios ingresados; y una tabla tbloperacion en donde almacenamos las operaciones (gastos e ingresos). Las relaciones entre divisas y servicios con respecto a las operaciones están dadas por el ID de estas tablas (INTNUMDIVISA e INTNUMSERVICIO).

Ahora veremos el código PHP de nuestra que funcionará como servicio:

PHP:
  1. <?php
  2. class SistemaGastos
  3. {
  4.     var $sqlstring = "";
  5.     var $server = "localhost";
  6.     var $user"usuario_database";
  7.     var $pass = "contrasena_database";
  8.     var $database = "codigometr_gastos";
  9.    
  10.     var $db = 0;
  11.     var $rs = 0;
  12.     var $row = 0;
  13.     var $recordcount = 0;
  14.     var $EOF = true;
  15.  
  16.     function _conectarBaseDatos()
  17.     {
  18.          $this->db = mysql_connect($this->server,$this->user,$this->pass);
  19.       mysql_select_db($this->database,$this->db) or die(mysql_error());
  20.     }
  21.    
  22.     function _ejecutarQuery($strSql)
  23.     {
  24.             $this->sqlstring = $strSql;
  25.             $this->_exec_command();
  26.         return $this->lastid;
  27.         }
  28.    
  29.     function _exec_command()
  30.     {
  31.         if ($this->db && $this->sqlstring!="")
  32.         {
  33.             $this->rs = mysql_query($this->sqlstring,$this->db);
  34.             if ($this->rs)
  35.             {
  36.                 $this->EOF = true;
  37.                 $this->recordcount = mysql_affected_rows();
  38.                 $this->lastid = mysql_insert_id();
  39.             }
  40.             else
  41.             {
  42.                 $this->recordcount = 0;
  43.                 $this->EOF = true;
  44.             }
  45.         }
  46.         else
  47.         {
  48.             $this->recordcount = 0;
  49.             $this->EOF = true;
  50.         }   
  51.     }   
  52.    
  53.     function _destruir()
  54.     {
  55.             if($this->db)
  56.             mysql_close($this->db);
  57.     }
  58.    
  59.     /* Métodos para la tabla tbldivisa */
  60.     function insertarDivisa($nombre, $simbolo)
  61.     {
  62.         $this->_conectarBaseDatos();       
  63.         $sql = "Insert into `tbldivisa` (`STRNOMBRE`, `STRSIMBOLO`) VALUES ('" . $nombre . "', '" . $simbolo . "')";
  64.         $this->_ejecutarQuery($sql);
  65.         $this->_destruir();
  66.     }
  67.    
  68.     function modificarDivisa($idDivisa, $nombre, $simbolo)
  69.     {
  70.         $this->_conectarBaseDatos();       
  71.         $sql = "Update `tbldivisa` set `STRNOMBRE` = '" . $nombre . "', `STRSIMBOLO` = '" . $simbolo . "' where `INTNUMDIVISA` = " . $idDivisa;
  72.         $this->_ejecutarQuery($sql);
  73.         $this->_destruir();
  74.     }
  75.    
  76.     function eliminarDivisa($idDivisa)
  77.     {
  78.         $this->_conectarBaseDatos();       
  79.         $sql = "Delete from `tbldivisa` where `INTNUMDIVISA`= " . $idDivisa;   
  80.         $result = mysql_query($sql);
  81.         $sql = "Delete from `tbloperacion` where `INTNUMDIVISA`= " . $idDivisa
  82.         mysql_query($sql);
  83.         $this->_destruir();
  84.         return $result;
  85.     }
  86.    
  87.     function obtenerDivisas()
  88.     {
  89.         $this->_conectarBaseDatos();       
  90.         $sql = "Select INTNUMDIVISA, STRNOMBRE, STRSIMBOLO from tbldivisa ORDER BY STRNOMBRE asc";
  91.         $result = mysql_query($sql);
  92.    
  93.         while ($row = mysql_fetch_object($result))
  94.         {
  95.             $divisas[] = $row;
  96.         }   
  97.        
  98.         $this->_destruir();
  99.                    
  100.         return($divisas);
  101.     }
  102.    
  103.     /* Métodos para la tabla tblservicio */
  104.     function insertarServicio($nombre)
  105.     {
  106.         $this->_conectarBaseDatos();       
  107.         $sql = "Insert into `tblservicio` (`STRNOMBRE`) VALUES ('" . $nombre . "')";       
  108.         $this->_ejecutarQuery($sql);
  109.         $this->_destruir();
  110.     }
  111.    
  112.     function modificarServicio($idServicio, $nombre)
  113.     {
  114.         $this->_conectarBaseDatos();       
  115.         $sql = "Update `tblservicio` set `STRNOMBRE` = '" . $nombre . "' where `INTNUMSERVICIO` = " . $idServicio;
  116.         $this->_ejecutarQuery($sql);
  117.         $this->_destruir();
  118.     }
  119.    
  120.     function eliminarServicio($idServicio)
  121.     {
  122.         $this->_conectarBaseDatos();       
  123.         $sql = "Delete from `tblservicio` where `INTNUMSERVICIO`= " . $idServicio
  124.         $result = mysql_query($sql);
  125.         $sql = "Delete from `tbloperacion` where `INTNUMSERVICIO`= " . $idServicio
  126.         mysql_query($sql);
  127.         $this->_destruir();
  128.         return $result;
  129.     }
  130.    
  131.     function obtenerServicios()
  132.     {
  133.         $this->_conectarBaseDatos();       
  134.         $sql = "Select INTNUMSERVICIO, STRNOMBRE from tblservicio ORDER BY STRNOMBRE asc";
  135.         $result = mysql_query($sql);
  136.    
  137.         while ($row = mysql_fetch_object($result))
  138.         {
  139.             $servicios[] = $row;
  140.         }   
  141.        
  142.         $this->_destruir();
  143.                    
  144.         return($servicios);
  145.     }
  146.    
  147.     /* Métodos para la tabla tbloperacion */
  148.     function insertarOperacion($fecha, $concepto, $cantidad, $id_divisa, $servicio, $operacion)
  149.     {
  150.         $this->_conectarBaseDatos();       
  151.         $sql = "INSERT INTO `tbloperacion` (DTMFECHA, STRCONCEPTO, NUMCANTIDAD, INTNUMDIVISA, INTNUMSERVICIO, INTOPERACION) VALUES ('" . $fecha . "','" . $concepto . "'," . $cantidad . "," . $id_divisa . "," . $servicio . "," . $operacion . ")";      
  152.         $result = $this->_ejecutarQuery($sql);
  153.         $this->_destruir();
  154.        
  155.         return $result;
  156.     }
  157.    
  158.     function actualizarOperacion($idOperacion, $fecha, $concepto, $cantidad, $id_divisa, $servicio, $operacion)
  159.     {
  160.         $this->_conectarBaseDatos();       
  161.         $sql = "Update `tbloperacion` set `DTMFECHA` = '" . $fecha . "', `STRCONCEPTO` = '" . $concepto . "', `NUMCANTIDAD` = " . $cantidad . ", `INTNUMDIVISA` = " . $id_divisa . ", `INTNUMSERVICIO` = " . $servicio . ", `INTOPERACION` = " . $operacion . " where `INTNUMOPERACION` = " . $idOperacion;
  162.         $result = $this->_ejecutarQuery($sql);
  163.         $this->_destruir();
  164.         return $result;
  165.     }
  166.    
  167.     function eliminarOperacion($idOperacion)
  168.     {
  169.         $this->_conectarBaseDatos();       
  170.         $sql = "Delete from `tbloperacion` where `INTNUMOPERACION`= " . $idOperacion;   
  171.         $result = mysql_query($sql);
  172.         $this->_destruir();
  173.         return $result;
  174.     }
  175.    
  176.     function obtenerOperaciones()
  177.     {
  178.         $this->_conectarBaseDatos();       
  179.         $sql = "Select * from `tbloperacion` ORDER BY DTMFECHA asc";
  180.         $result = mysql_query($sql);
  181.    
  182.         while ($row = mysql_fetch_object($result))
  183.         {
  184.             $operaciones[] = $row;
  185.         }   
  186.        
  187.         $this->_destruir();
  188.                    
  189.         return($operaciones);
  190.     }
  191.    
  192.     function realizarConsulta($servicio, $divisa, $concepto, $fecha1, $fecha2, $cantidad1, $cantidad2, $operacion)
  193.     {
  194.         $this->_conectarBaseDatos();
  195.         $sql = "Select top.DTMFECHA, top.STRCONCEPTO, top.NUMCANTIDAD, top.INTNUMDIVISA, top.INTNUMSERVICIO, top.INTOPERACION, td.STRNOMBRE, td.STRSIMBOLO FROM tbloperacion AS top, tbldivisa as td WHERE td.INTNUMDIVISA = top.INTNUMDIVISA";
  196.        
  197.         /* Selección Concepto */
  198.         if($concepto != "")
  199.             $sql .= " AND top.STRCONCEPTO LIKE '%" . $concepto . "%'";
  200.        
  201.         /* Selección Divisa */
  202.         if($divisa != 0) {
  203.             $sql .= " AND top.INTNUMDIVISA = " . $divisa . " AND td.INTNUMDIVISA = " . $divisa;
  204.         }
  205.        
  206.         /* Selección Servicio */
  207.         if($servicio != 0) {
  208.             $sql .= " AND top.INTNUMSERVICIO = " . $servicio;
  209.         }
  210.        
  211.         if($cantidad1 != "" && $cantidad2 != "")
  212.         {
  213.             if($cantidad1 <= $cantidad2)
  214.                 $sql .= " AND top.NUMCANTIDAD BETWEEN " . $cantidad1 . " AND " . $cantidad2;
  215.             else
  216.                 $sql .= " AND top.NUMCANTIDADBETWEEN " . $cantidad2 . " AND " . $cantidad1;
  217.         }
  218.         else if($cantidad1 != "")
  219.             $sql .= " AND top.NUMCANTIDAD = " . $cantidad1;
  220.         else if($cantidad2 != "")
  221.             $sql .= " AND top.NUMCANTIDAD = " . $cantidad2;
  222.        
  223.         /* Selección Operación */
  224.         if($operacion == 3)
  225.             $sql .= " AND top.INTOPERACION>= 1 AND top.INTOPERACION <= 2";
  226.         else
  227.             $sql .= " AND top.INTOPERACION = "  . $operacion;      
  228.            
  229.         if($fecha1 != NULL || $fecha2 != NULL)
  230.         {
  231.             if($fecha1 != "" && $fecha2 == "")
  232.             {
  233.                 $sql .= " AND top.DTMFECHA = '" . $fecha1 . "'";
  234.             }
  235.             else
  236.             {
  237.                 $sql .= " AND top.DTMFECHA>= '" . $fecha1 . "' AND top.DTMFECHA <= '" . $fecha2 . "'";
  238.             }
  239.         }
  240.        
  241.         $sql .= " ORDER BY top.DTMFECHA asc, top.NUMCANTIDAD asc";
  242.         $result = mysql_query($sql);
  243.        
  244.         while ($row = mysql_fetch_object($result))
  245.         {
  246.             $registros[] = $row;
  247.         }   
  248.        
  249.         $this->_destruir();
  250.                    
  251.         return($registros);
  252.     }
  253. }
  254. ?>

Los métodos que inician con guión bajo (_) son métodos privados y por lo tanto no podemos acceder a ellos desde Flex. Los otros métodos insertarDivisa, modificarDivisa, eliminarDivisa, obtenerDivisas, etc. son métodos públicos a los cuales accedemos desde Flex.
Observa cómo en los métodos obtenerXXX almacenamos los registros en un arreglo y lo regresamos a nuestra aplicación. Los queries en realidad son muy sencillos, por lo que no requieren explicación; solamente observa cómo en los métodos eliminarServicio y eliminarDivisa hacemos el borrado de los registros que involucran estos registros en la tabla tbloperacion. Esto es para evitar que haya inconsistencia en la base de datos.

Ahora veremos el código principal de nuestra aplicación en Flex.
Para poder hacer uso del AMFPHP necesitamos de las librerías NetConnection y Responder.

Actionscript:
  1. import flash.net.NetConnection;
  2.     import flash.net.Responder;

Para poder acceder a los servicios en AMFPHP debemos especificar la ruta en la cual se encuentra el archivo gateway.php de AMFPHP:

Actionscript:
  1. private var gateway:String = "http://www.codigometropoli.com/wp-content/uploads/2008/10/SistemaGastos/AMFPHP1.9/gateway.php";
  2.     private var connection:NetConnection;
  3.     private var responder:Responder;

Al iniciar la aplicación debemos llamar a la función connect pasándole como parámetro el gateway:

Actionscript:
  1. function iniciaAplicacion()
  2. {
  3.     connection = new NetConnection;
  4.     connection.connect(gateway);
  5. }

Dentro del constructor de la clase Responder debemos especificar las funciones que se ejecutarán en caso de que la llamada haya sido sido exitosa y en caso de que haya existido un error. Para hacer la llamada al servicio utilizamos la función call; a la cual pasamos como parámetro el nombre de la función que queremos llamar y que se encuentra en la clase SistemaGastos.php, nuestro objeto de la clase Responder y los parámetros que debemos pasarle a la función:

Actionscript:
  1. responder = new Responder(insertarOperacionResult, errorConsulta);   
  2. connection.call("SistemaGastos.insertarOperacion", responder, param1, param2, param3...);

Del código anterior podemos deducir que el nombre de nuestra clase en PHP es SistemaGastos y que tiene un método público llamado insertarOperacion.

En caso de que la llamada haya sido exitosa, se ejecutará el método insertarOperacionResult:

Actionscript:
  1. private function insertarOperacionResult(result:Object):void {
  2.     if(result.toString() != "0")
  3.     {
  4.         restablecerForma();
  5.         Alert.show("Registro almacenado", "Aviso");
  6.     }
  7.     else
  8.         Alert.show("No se pudo almacenar el registro", "Error");
  9. }

En caso contrario, si existiera algún error en la llamada al servicio, la función errorConsulta se ejecutará:

Actionscript:
  1. private function errorConsulta(fault:Object):void {
  2.     Alert.show(fault.description, "Error");
  3.     CursorManager.removeBusyCursor();
  4. }

Nuestra forma de alta de operación (Gasto o Ingreso) será la siguiente:

Formulario de alta de gastos o ingresos

De la imagen anterior puedes saber qué tipo de información estamos solicitando, cuál es la obligatoria (Fecha, Cantidad y Divisa), así como la forma en qué estamos mostrando al usuario las divisas y servicios dados de alta (por medio de ComboBox).

Las partes de código mostradas forman parte de la llamada a una función que se encarga de insertar un registro. El código no tendrá variación cuando se trate de modificar un registro; y cuando se trate de eliminar un registro solamente le pasaremos el ID del registro a eliminar. ¿Pero qué pasa cuando se trata de una consulta? ¿Cómo obtenemos los registros registros regresados por la base de datos? ¿Cómo mostramos esos registros?

Si leíste el tutorial sobre Componente DataGrid y las cuatro entregas del tutorial Sistema de Clientes en AIR sabrás que podemos recorrer cada uno de los registros obtenidos de la base de datos y almacenarlos en un arreglo por medio del método push; posteriormente ese arreglo es pasado al DataGrid como su proveedor de datos (dataProvider).

Primero hacemos la llamada al método realizarConsulta del servicio SistemaGastos:

Actionscript:
  1. responder = new Responder(realizarConsultaResult, errorConsulta