Mostrando entradas con la etiqueta Refactorizar. Mostrar todas las entradas
Mostrando entradas con la etiqueta Refactorizar. Mostrar todas las entradas

martes, 3 de marzo de 2009

Un framework libre para trabajar con Ajax: Prototype


El framework de Ajax más popular: Prototype

¡Saludos nuevamente blogueros!

Básicamente un framework consiste en un conjunto de utilidades y bibliotecas que nos facilitan la realización de un desarrollo de software. Uno de los puntos importantes de los frameworks es que dentro de nuestra programación varios bloques de códigos son repetitivos, por lo que es posible contar un una herramienta que automatice la generación de este código para facilitar la construcción del software. Una vez un amigo me hizo una analogía muy buena de como debíamos ver lo que es un framework y para qué nos sirve, este puede verse como el armazón de una casa de madera, en la que el armazón representa las piezas de madera sobre las que se monta toda la estructura de las paredes, los pisos y los techos. Así, este conjunto de piezas de madera sustenta toda la construcción, pero al mismo tiempo, pueden utilizarse en otras construcciones con características similares, solo hay que tener la suficiente cantidad de piezas cortadas y listas para utilizarse nuevamente, por lo que ya no nos preocuparemos por tener que fabricar nosotros mismos un nuevo armazón. Entonces, regresando a la programación, el framework representará todas esas herramientas diseñadas previamente que nos facilitarán el trabajo de desarrollar nuevos sistemas de software.

Si recordamos los temas de Javascript y Ajax, encontramos que en los diferentes ejemplos hemos estado repitiendo varias de las rutinas para acceder a los recursos del servidor, entonces en este post veremos uno de los frameworks libres que existen para el trabajo con Javascript y Ajax, llamado Prototype, el cual fue diseñado originalmente por Sam Stephenson, pero que sus ultimas versiones incorpora código de otros programadores. Consiste en una biblioteca de objetos que encapsulan varias funcionalidades para Javascript y Ajax, dentro de los métodos y propiedades que contienen, tratando de simular una estructura de clases como la de otros lenguajes de programación.

Para probarlo, basta con acceder al sitio Web oficial para su descarga: http://www.prototypejs.org/

En la sección Download, encontraremos la última versión estable de esta biblioteca, contenida en un archivo *.js. La última versión estable que yo encontré corresponde al archivo prototype-1.6.0.3.js. Descargaremos este archivo en nuestro equipo y lo guardaremos en el directorio del servidor Web que contenga nuestros archivos HTML.

Para dar un ejemplo rápido de la utilización de esta biblioteca, realizaremos la programación de un nuevo cliente Ajax dentro de una página HTML, en la que incluiremos la referencia al archivo *.js que acabamos de descargar.

Consideraciones previas

Hay tres maneras de realizar una petición Ajax con Prototype:

1) Ajax.Request
2) Ajax.PeriodicalUpdater
3) Ajax.Updater

Para el ejemplo utilizaremos la funcionalidad Request del objeto Ajax llamada Ajax.Request, la cual nos permite realizar una petición de un recurso al servidor Web. Este método del objeto Ajax se instancía como si se tratara de una clase y recibe una serie de parámetros:

url
URL del recurso Web que será solicitado al servidor Web en la petición HTTP.


method
Este puede ser HTTP GET o HTTP POST. Por defecto está definido como POST.


parameters
Los parámetros representan las parejas llave-valor que serán enviados a la pagina solicitada, que representan los mismos datos que pasamos en la cadena de consulta (query) cuando solicitamos una URL remota en los ejemplos anteriores.



Eventos Prototype de Ajax:

Además de estos parámetros, para la petición dentro de Ajax.Request también se incluye una referencia a una serie de funciones que definen un grupo específico de eventos, que se ejecutan automáticamente cuando se completa una tarea o cuando ocurre un error:


onCreate:
Este evento es llamado siempre que se realiza una nueva petición Ajax con Ajax.Request. Es útil si deseamos ver una señal de que se ha iniciado una peticón Ajax.


onSuccess:
Este evento es llamado siempre que una petición es completada y el código de estado regresado esta dentro de 2xx.


onFailure:
Este evento es llamado siempre que la petición es completada y el código de estado no esta dentro de 2xx.



Manos a la obra



Para este ejemplo nos basaremos en el código del archivo HTML del tercer ejercicio que vimos con Ajax, que titulamos Combinando Ajax con PHP, en el que realizamos un cliente Ajax que envía un serie de valores por POST a un programa PHP, el cual valida el contenido de estos datos y devuelve un mensaje como respuesta.

Dentro del código incluiremos una sentencia que reverenciará al archivo que contiene la biblioteca Prototype:
<script type="text/javascript" src="prototype-1.6.0.3.js"></script>


El código refactorizado para la petición Ajax consistirá en una única función llamada peticionAjax, dentro de la que se realizará una instancia de Ajax.Request:

function peticionAjax() {
    var url = "http://localhost/validaDatos.php";
    var objParametros = creaObjParametros();
    var ajaxObjhttp = new Ajax.Request(url,
        method: 'POST',
        parameters: objParametros,
        onCreate: function(transport) {
                document.getElementById("divRespuesta").innerHTML = "<p>Iniciando nueva petición Ajax...</p>";
                },
        onSuccess: function(transport) {
                document.getElementById("divRespuesta").innerHTML += transport.responseText;
                },
        onFailure : function(response) {
                alert("Ocurrió un error al acceder al servidor Web");
                }
    });

function creaObjParametros() {
    var obj = new Object();
    obj.txtAutor = document.getElementById("txtAutor").value;
    obj.txtAnio = document.getElementById("txtAnio").value;
    obj.txtTitulo = document.getElementById("txtTitulo").value;
    return obj;
}


El la variable url y el objeto objParametros representan los parámetros url y parameters que definimos previamente. Podemos ver que se realiza una llamada a la función creaObjParametros, con la cual se obtiene un objeto que dentro de sus propiedades contiene cada uno de estos parámetros que serán enviados junto con la petición HTTP. Posteriormente encontramos la instancia al método Ajax.Request en la que se incluye la variable url como primer parámetro, después aparece la declaración de un objeto -indicado con la apertura y cierre de las llaves { }- dentro del cual se establecen de una serie de propiedades y métodos:

var ajaxObjhttp = new Ajax.Request(url,
    method: 'POST',
    parameters: objParametros,
    onCreate: function(transport) {
        document.getElementById("divRespuesta").innerHTML = "<p>Iniciando nueva petición Ajax...</p>";
    },
    onSuccess: function(transport) {
        document.getElementById("divRespuesta").innerHTML += transport.responseText;
    },
    onFailure: function(response) {
        alert("Ocurrió un error al acceder al servidor Web");
    }
});


Las propiedades representan el resto de los parámetros necesarios para la petición como son method (tipo de método de envío) y parameters (parámetros enviados junto con la petición), mientras que los métodos declarados representan las funciones que serán ejecutados en cada uno de los eventos de la petición, identificando estos métodos con el nombre del evento que representa, ya sea onCreate, onSuccess y onFaliure.

El código completo de la implementación dentro de la página HTML es el siguiente:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Petición Ajax con Prototype</title>
<script type="text/javascript" src="prototype-1.6.0.3.js"><!-- Referencia a Prototype--></script>
<script type="text/javascript">
function peticionAjax() {
    var url = "http://localhost/validaDatos.php"; //URL Recurso Web accedido
    var objParametros = creaObjParametros(); //Llamada a la función que devuelve los parámetros dentro de un objeto
    var ajaxObjhttp = new Ajax.Request(url, {
        method: 'POST',
        parameters: objParametros,
        onCreate: function(transport) { //Método ejecutado cuando se inicie la petición
                document.getElementById("divRespuesta").innerHTML = "<p>Iniciando nueva petición Ajax...</p>";
                },
        onSuccess: function(transport) { //Método ejecutado cuando la petición es completada sin errores
                document.getElementById("divRespuesta").innerHTML += transport.responseText;
                },
        onFailure : function(response) { //Método ejecutado cuando la petición es completada con algún error
                alert("Ocurrió un error al acceder al servidor Web");
                }
    });
}

function creaObjParametros() {//Función que almacena en un objeto los valores capturados en el formulario que serán enviados por POST a la página HTML
    var obj = new Object();
    obj.txtAutor = document.getElementById("txtAutor").value;
    obj.txtAnio = document.getElementById("txtAnio").value;
    obj.txtTitulo = document.getElementById("txtTitulo").value;
    return obj;
}
</script>
</head>
<body>
<form style="background-color:#EFEDF1">
    <label for="txtAutor">Autor:</label>
    <input type="text" id="txtAutor" name="txtAutor" size="40" /><br/>
    <label for="txtAnio">Año:</label>
    <input type="text" id="txtAnio" name="txtAnio" size="10" /><br/>
    <label for="txtTitulo">Títiulo:</label>
    <input type="text" id="txtTitulo" name="txtTitulo" size="50" /><br/>
    <input type="button" value="Validar datos" onclick="peticionAjax()" />
    </form>
<div id="divRespuesta"></div>
</body>
</html>


Para probarlo solamente deberemos comprobar la ubicación del archivo de la biblioteca Prototype, representada en el código con el nombre prototype-1.6.0.3.js, dado que en ejemplo se espera que este archivo se encuentre en el mismo directorio de la página HTML. De la misma forma debemos asegurarnos de que la ruta del archivo PHP validaDatos.php dentro del servidor Web es correcta. Por si desean tener otra vez a la mano este archivo PHP aquí les dejo su código:

validaDatos.php

<?
//Expresiones regulares para la validación de los datos
$er_titulo = "/^([a-z]|[A-Z]|[ÁÉÍÓÚáéíóúÑñÜü]|[0-9]|[\s\.\-,:'\"])+$/";
$er_autor = "/^([a-z]|[A-Z]|[ÁÉÍÓÚáéíóúÑñÜü]|[\s,\.'\-])+$/";
$er_anio = "/^([1-2][0-9][0-9][0-9])$/";
//Validamos que se hayan enviado todos los datos por post
if(isset($_POST['txtAutor']) && isset($_POST['txtAnio']) && isset($_POST['txtTitulo'])){
  //Validamos el contenido de los campos
  $autor = stripslashes(trim($_POST['txtAutor']));
  $anio = trim($_POST['txtAnio']);
  $titulo = stripslashes(trim($_POST['txtTitulo']));
  if(!preg_match($er_autor,$autor)){
    $campo = "autor";
  }
  else if(!preg_match($er_anio,$anio)){
    $campo = "año";
  }
  else if(!preg_match($er_titulo,$titulo)){
    $campo = "título";
  }
  else{
    $mensaje = "Todos los campos capturados son correctos :)";
  }
  if(isset($campo))
    $mensaje = "Error: El texto capturado en el campo $campo es incorrecto";
}
else{
  $mensaje = "No se recibieron los valores";
}
?>
<p><?=$mensaje?></p>
<p><u>Valores recibidos</u>:<br />
Autor: <?=$_POST['txtAutor']?><br />
Año: <?=$_POST['txtAnio']?><br />
Título: <?=$_POST['txtTitulo']?><br />
</p>


Una vez almacenados estos archivos podremos probarlos y comprobar que el cliente realiza la misma tarea que el original,



con la diferencia de que gracias a los eventos de Prototype se despliega un mensaje al iniciar la petición, y en caso de que ocurriera un error podríamos darnos por enterados gracias al mensaje de alerta que incluimos. Es cierto que toda esta funcionalidad la podríamos haber realizado nosotros mismos utilizando la clase XMLHttpRequest, pero habríamos tenido que invertir una cantidad de tiempo importante, por lo que en este momento es cuando podemos apreciar la gran ayuda que nos brinda Prototype, y no solo al ahorrarnos tiempo de trabajo sino que además nos permite organizar mejor nuestra programación con Ajax, al brindarnos una estructura prediseñada con la cual especificamos la configuración de la petición y la funcionalidad completa del proceso, con todo lo cual se vuelve realmente más fácil nuestra labor como programadores de clientes Ajax.

Como conclusión final puedo decir que vale la pena invertir tiempo en dominar nuevas herramientas para facilitar nuestro trabajo de programación, ya que esto nos ayuda a que podamos construir mejores proyectos de software en el futuro.

miércoles, 25 de febrero de 2009

Otra forma de acceder a la base de datos con PHP


En un de los ejemplos anteriores creamos una clase en PHP para generar documentos XML que contuvieran los resultados de las consultas a una base de datos, en esta ocasión, crearé otra clase en PHP pero que, -como dice el título de este post- se encargará de manejar el acceso a la base de datos, ¿por qué? primero que nada recordemos que en las escuela nos hablaron de la importancia de separar nuestros proyectos de programación con bases de datos en tres capas: presentación, negocio y acceso a datos, y no solo porque de esta forma se ven más elaborados sino por el hecho de que así ya partimos de un muy buen principio de diseño, que ayudará a lograr un mejor proyecto de desarrollo. Por otro lado encontramos que al trabajar con capas nos ahorramos mucho esfuerzo de teclear código, además de que se facilita el mantenimiento de nuestros programas.


El gran beneficio: La principal ventaja que encuentro al utilizar capas en nuestro desarrollo es que al producirse cambios en los requerimientos de nuestros sistemas, realizar modificaciones al código resulta mucho más sencillo. Por ejemplo, si tenemos cambiar de sistema manejador de base de datos de MySQL a MS SQL Server, dentro de nuestro código solo tendremos que realizar cambios en la capa de acceso a bases de datos, dejando prácticamente intacto el resto del código, ya que veamos el ejemplo se entenderá bien como es esto.

Puntos previos a tener en cuenta

Como consideraciones previas, tendremos en cuenta que las responsabilidades que recaerán sobre esta clase son:
  • Conectarse a la base de datos.
  • Acceder a la base de datos.
  • Ejecutar consultas SQL.
  • Insertar registros.
  • Actualizar registros.
  • Borrar registros.
  • Desconectarse de la base de datos.
Al momento de traducir estos requerimientos a la programación, irán surgiendo una serie de campos y métodos que se utilizarán para realizar las tareas necesarias.

Así, las funciones mysql (mysql_connect, mysql_select_db, mysql_query, etc.) que antes se utilizaban dentro del código, serán encapsuladas en esta serie de métodos.

Nota: Al referirme a las clases y objetos utilizo términos que aprendí durante mi incursión en el lenguaje Java, por lo que surgen algunas diferencias con los que manejan los desarrolladores más asiduos de .Net, en el que se refieren a propiedades y atributos de las clases en lugar de campos como en Java, pero ya que el modelo de programación con objetos de PHP5 se apegó más a Java creo que esta terminología queda bastante bien :D

Construyamos nuestra nueva clase para acceso a datos

Siguiendo con el trabajo, ahora les presento la clase que idee para cumplir el objetivo de manejar el acceso a una base de datos -un poco larga la verdad :P-:


<?php
//Clase Acceso a Datos
class AccesoADatos{
//Campos de la clase
private $conexion;
private $ipServidor;
private $usuario;
private $contrasenia;
private $baseDeDatos;
private $resultados;
private $mensaje;
private $registrosLeidos;
//Constructor de la clase
function __construct($ipServidor, $usuario, $contrasenia, $baseDeDatos) {
$this->ipServidor = $ipServidor;
$this->usuario = $usuario;
$this->contrasenia = $contrasenia;
$this->baseDeDatos = $baseDeDatos;
$this->conexion = false;
$this->registrosLeidos = 0;
$this->resultados = array();
$this->mensaje = "";
}
//Destructor de la clase Acceso a Datos
function __destruct() {
$this->liberaResultado();
$this->cierraConexion();
}
//Abre la conexión con el servidor de base de datos. Devuelve falso si hubo error
public function abreConexion() {
$estadoDeConexion = true;
$this->conexion = @mysql_connect($this->ipServidor,$this->usuario,$this->contrasenia);
if(mysql_select_db($this->baseDeDatos,$this->conexion)) {
mysql_query("SET NAMES 'utf8'",$this->conexion);
$estadoDeConexion = true;
}
else {
$estadoDeConexion = false;
$this->estableceError("No fue posible acceder a la base de datos, consulte con el administrador de la base de datos para solucionarlo.");
}
return $estadoDeConexion;
}
//Establece el mensaje de error e inicializa los contadores de resultados
private function estableceError($tipo) {
$this->registrosLeidos = 0;
$this->mensaje = "Error de acceso a datos: ".$tipo;
}
//Ejecuta una consulta SQL contenida en una cadena
public function consulta($consultaSQL, $idResultado = "") {
$estado = true;
if($this->conexion) {
$r = mysql_query($consultaSQL,$this->conexion);
if(mysql_errno() > 0) {
$this->estableceError("No se completó la operación solicitada ".mysql_error());
$estado = false;
}
else
$this->registrosLeidos = mysql_num_rows($r);
if($idResultado == "")
$this->resultados[0] = $r;
else
$this->resultados[$idResultado] = $r;
}
else {
$this->estableceError("Conexión con la base de datos no establecida.");
$estado = false;
}
return $estado;
}
//Borra él o los registros de una tabla que cumplan los parámetros recibidos
public function borraRegistro($tabla, $condicion = "1") {
$estado = true;
if($this->conexion) {
$comando = "DELETE FROM `$tabla` WHERE $condicion";
mysql_query($comando,$this->conexion);
if($this->numeroDeError = mysql_errno() > 0) {
$this->estableceError("No se completó la operación solicitada ".mysql_error());
$estado = false;
}
}
else {
$this->estableceError("Conexión con la base de datos no establecida.");
$estado = false;
}
return $estado;
}
//Inserta un registro en la tabla específicada utilizando como valores el arreglo de datos recibido
public function insertaRegistro($tabla, $datos) {
if($this->conexion) {
$comando = "INSERT INTO `$tabla` (";
foreach($datos as $llave=>$dato) {
$campos[]= "`$llave`";
$valores[] = $dato;
}
$comando.= implode(",",$campos).") VALUES('".implode("','",$valores)."')";
mysql_query($comando,$this->conexion);
if(mysql_errno($this->conexion) > 0)
$this->estableceError("No se completó la operación solicitada ".mysql_error());
return mysql_affected_rows($this->conexion);
}
else {
$this->estableceError("Conexión con la base de datos no establecida.");
return 0;
}
}
//Actualiza el registro de una tabla utilizando los párametors recibidos
public function actualizaRegistro($tabla, $nuevosDatos, $campoLlave) {
if($this->conexion) {
$comando = "UPDATE `$tabla` SET ";
foreach($nuevosDatos as $llave=>$dato)
$nuevosValores[] = "`$llave` = '$dato'";

foreach($campoLlave as $llave=>$dato)
$condicion[] = "`$llave` = '$dato'";
$comando.= implode(",",$nuevosValores)." WHERE ".implode(" AND ",$condicion);
mysql_query($comando,$this->conexion);
if(mysql_errno($this->conexion) > 0)
$this->estableceError("No se completó la operación solicitada ".mysql_error());
return mysql_affected_rows($this->conexion);
}
else {
$this->estableceError("Conexión con la base de datos no establecida.");
return 0;
}
}
//Regresa el contenido de un registro del resultado en un arreglo
public function devuelveArreglo($idResultado = "") {
if($idResultado=="")
$idResultado = 0;
if($this->resultados[$idResultado])
return mysql_fetch_assoc($this->resultados[$idResultado]);
else
return false;
}
//Devuelve el número de registros leidos por la última consulta
public function devuelveRegsLeidos() {
return $this->registrosLeidos;
}
//Devuelve el utimo mensaje de error generado
public function devuelveError() {
return $this->mensaje;
}
//Libera de la memoria del servidor el resultado de cualquier consulta realizada
public function liberaResultado() {
foreach($this->resultados as $llave=>$r) {
if($this->resultados[$llave])
mysql_free_result($this->resultados[$llave]);
}
$this->resultados = array();
}
//Cierra la conexión con el servidor de base de datos
public function cierraConexion() {
if($this->conexion){
mysql_close($this->conexion);
$this->conexion = false;
}
}
}
?>

La clase recibe el nombre de AccesoADatos, y dentro de esta clase aparece un primer método identificado como __construct, que es el método constructor de la clase, es llamado por PHP al momento de instanciarla. En caso de no declararlo, PHP llamaría a un constructor por defecto que poseen todas las clases, como en el caso de la clase ConsultaXML que programamos en uno de los posts anteriores.
function __construct($ipServidor, $usuario, $contrasenia, $baseDeDatos) {
$this->ipServidor = $ipServidor;
$this->usuario = $usuario;
$this->contrasenia = $contrasenia;
$this->baseDeDatos = $baseDeDatos;
$this->conexion = false;
$this->registrosLeidos = 0;
$this->resultados = array();
$this->mensaje = "";
}


Dentro del constructor se reciben los parámetros necesarios para realizar la conexión con el servidor de base de datos, y para acceder a la base de datos seleccionada. Además, se inicializan los campos que se utilizarán dentro de los métodos de la clase.

En el siguiente método encontramos el identificador __destruct:

function __destruct() {
$this->liberaResultado();
$this->cierraConexion();
}


El cual representa el destructor de la clase que es llamado automáticamente por PHP, al momento que todas las referencias a un objeto de la clase AccesoADatos sean removidas o cuando el objeto sea explícitamente destruido, o en cualquier orden en la finalización de la ejecución. Dentro de éste método se liberará la memoria de cualquier resultado de las consultas realizadas, para finalmente cerrar la conexión con el servidor de base de datos.

El método abreConexion() realiza la conexión con el servidor de base de datos MySQL, utilizando los parámetros $ipServidor, $usuario y $constrasenia, establecidos en el constructor de la clase.

mysql_connect($this->ipServidor,$this->usuario,$this->contrasenia);
if(mysql_select_db($this->baseDeDatos,$this->conexion)) {
mysql_query("SET NAMES 'utf8'",$this->conexion);
$estadoDeConexion = true;
}

Inmediatamente después accede a la base de datos especificada por el campo $baseDeDatos, y finalmente establece que el juego de caracteres que se utilizará en las consultas es UTF-8 a través de la consulta "SET NAMES utf8".

Todos los mensajes de error que se generen con las operaciones de acceso a la base de datos se establecerán con el método estableceError(), y estos mensajes podrán ser consultados desde fuera de la clase a través del método devuelveMensaje().

El método consulta(), recibe una cadena dentro del parámetro $consultaSQL que representa una consulta SQL para ejecutarla con la función mysql_query(),

$r = mysql_query($consultaSQL,$this->conexion);
if(mysql_errno() > 0) {
$this->estableceError("No se completó la operación solicitada ".mysql_error());
$estado = false;
}
else
$this->registrosLeidos = mysql_num_rows($r);
if($idResultado == "")
$this->resultados[0] = $r;
else
$this->resultados[$idResultado] = $r;


almacenando el resultado de la consulta dentro del arreglo $resultados, identificandola con el parámetro $idResultado que puede ser tanto una cadena como un valor numérico. En caso de que no se reciba un valor que identifique el resultado, este se almacena dentro del arreglo como el elemento 0 en el índice.

El método borraRegistro() eliminará uno o más registros en una tabla especificada, dependiendo de los parámetros que reciben, dentro de los que se incluye el nombre de la tabla y la condición del comando SQL.

El métodos insertaRegistro(), realiza la inserción de un registro en la tabla especificada utilizando los valores contenidos en el arreglo asociativo $datos, el cual debe poseer el formato 'llave'=>"valor", donde la llave representa el nombre del campo de la tabla y el valor es el dato que será insertado en este campo.

$comando = "INSERT INTO `$tabla` (";
foreach($datos as $llave=>$dato) {
$campos[]= "`$llave`";
$valores[] = $dato;
}
$comando = $comando.implode(",",$campos).") VALUES('".implode("','",$valores)."')";



Utilizando el ciclo foreach y la función implode para construir el comando INSERT que será ejecutado.

El método actualizaRegistro() es similar a insertaRegistro(), solo que además del nombre de la tabla y del arreglo de datos, se recibe segundo arreglo asociativo llamado $campoLlave, que se utiliza para identificar él o los registros que serán actualizados por este método.

El método devuelveArreglo(), nos permite acceder a cada uno de los renglones del resultado de una consulta identificado por $idResultado, almacenado este renglón dentro de un arreglo asociativo por medio de la función mysql_fetch_assoc.

Con este código crearemos el archivo AccesoADatos.php, que a partir de ahora utilizaremos en los siguientes ejemplos para acceder a nuestras bases de datos. Cómo una muestra del uso de esta nueva clase, refactorizaremos el código PHP del último ejemplo, es decir el archivo consultaMySQL.php, para generar un nuevo archivo PHP que posea con la siguiente contenido:


<?php
require("ConsultaXML.php"); //Referencia al archivo que contiene la clase ConsultaXML
require("AccesoADatos.php");//Referencia al archivo que contiene la clase AccesoADatos
$consultaXML = new ConsultaXML();//Creación del documento XML
//Parámetros para la conexión a la base de datos
$servidor = "localhost";
$usuario = "root";
$contrasenia = "[TuConstraseña]";
$baseDeDatos = "obras_literarias";
$acceso = new AccesoADatos($servidor,$usuario,$contrasenia,$baseDeDatos);
if($acceso->abreConexion()) { //Accedemos a la base de datos
//Comprobamos que los datos se enviaron correctamente
if(isset($_POST['txtNombre']) && ($nombre = stripslashes(trim($_POST['txtNombre'])))!="") {
$consulta = "SELECT * FROM autores WHERE Nombre LIKE '%".addslashes($nombre)."%'";
if($acceso->consulta($consulta)) {//Realizamos la consulta y comprobamos el resultado
if($acceso->devuelveRegsLeidos() > 0){//Comprobamos si se encontraron registros
//Creamos el documento XML con el resultado de la consulta
$consultaXML->iniciaDocumento();
$consultaXML->startElement("Tabla");
$consultaXML->writeAttribute("nombre","autores");
while($renglon = $acceso->devuelveArreglo()) {//Extrae un renglon del resultado como un arreglo asosciativo
$consultaXML->creaRegistroDesdeArreglo($renglon, "Id");
}
$consultaXML->endElement();
$consultaXML->salida();
}
else{
$mensajeDeError = "Autor no encontrado";
}
}
else {
$mensajeDeError = $acceso->devuelveError();
}
}
else {
$mensajeDeError = "Datos imcompletos";
}
}
else {
$mensajeDeError = $acceso->devuelveError();
}
if($mensajeDeError != "") {//En caso de que exista algún mensaje de error lo devolvemos como respuesta
$consultaXML->iniciaDocumento("Error");
$consultaXML->text($mensajeDeError);
$consultaXML->salida();
}?>

El funcionamiento seguirá siendo exactamente el mismo, pero como vemos la cantidad de líneas de código y de variables se reduce, gracias al encapsulamiento de la clase. Y la parte más importa es, que nos abstraemos de la mayoría de los detalles en cuanto al acceso a la base de datos, por lo que en caso de cambiar de sistema manejador de base de datos, o agregar nuevas funcionalidades al acceso a datos, solo deberemos modificar esta clase.

En los siguientes ejemplos veremos como utilizar el resto de los métodos la clase AccesoADatos, programando una interface en Ajax y Javascript mucho más completa.

Hasta la próxima.

viernes, 6 de febrero de 2009

Refactorizar el código que procesa XML, Parte 2

Refactorización del lado del cliente

Ahora como prometimos, atenderemos la cuestión del cliente. Las modificaciones se centraran en el modo de extraer y desplegar la información del documento XML. En el último ejemplo de Ajax a partir de la raíz de este documento, se extrajeron secuencialmente los valores de cada uno de los elementos que serían desplegados, almacenándolo en una serie de variables, que después eran pasadas a una función que las desplegaba agregándolas a los renglones de una tabla HTML:


function procesaRespuestaXML() {
if(peticionHttp.readyState == READY_STATE_COMPLETE) {
if(peticionHttp.status == 200) {
var documentoXml = peticionHttp.responseXML; //Creamos el objeto de tipo documento XML
var root = documentoXml.getElementsByTagName("Informacion")[0]; //Obtenemos la raíz del documento
//Accedermos al contenido de cada campo contenido en el elemento Informacion
var infoCliente = root.getElementsByTagName("Cliente")[0].firstChild.nodeValue;
muestraRenglon("Navegador",infoCliente);
var infoSoftware = root.getElementsByTagName("Software")[0].firstChild.nodeValue;
muestraRenglon("Software del servidor",infoSoftware);
var infoFecha = root.getElementsByTagName("Fecha")[0].firstChild.nodeValue;
muestraRenglon("Fecha del servidor",infoFecha);
var infoHora = root.getElementsByTagName("Hora")[0].firstChild.nodeValue;
muestraRenglon("Hora del servidor",infoHora);
}
}
}
//Función que agrega un nuevo renglon a una tabla especificada, utilizando los valores recibidos

function muestraRenglon(colEtiqueta, colValor){
var tbody = document.getElementById('tblInfo').getElementsByTagName("TBODY")[0]; //Referencia a la tabla
var renglon = document.createElement("TR"); //Crea un renglon
var columna = document.createElement("TD"); //Crea una columna
columna.appendChild(document.createTextNode(colEtiqueta)); //Agrega texto a la columna
renglon.appendChild(columna); //Agrega la columna al renglon
columna = document.createElement("TD"); //Crea una nueva columna
columna.appendChild(document.createTextNode(colValor));
renglon.appendChild(columna);
tbody.appendChild(renglon); //Agrega el renglon a la tabla
}


Éste método resultaría en un código bastante largo si aumentara el número de elementos que serán desplegados, así que lo mejor es automatizar el proceso de extraer cada uno de los elementos del documento XML, procurando evitar los pasos secuenciales en que cae la forma anterior. Para este propósito dentro del programa Javascript se utilizará una función adicional, y además se modificara la función muestraRenglon para hacerla más compacta.

De modo que el código completo del cliente será el siguiente:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Quinto ejemplo con Ajax, combinándolo con PHP y XML (Refactorizado)</title>
<script language="javascript">
var READY_STATE_COMPLETE=4;
var peticionHttp = null;
//Función que devuelve el objeto de la clase XMLHttpRequest
function inicializaXhr() {
if(window.XMLHttpRequest) {
return new XMLHttpRequest();
}
else if(window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
//Función que inicia todo el proceso y lleva a cabo la petición al servidor
function solicita() {
peticionHttp = inicializaXhr();
if(peticionHttp) {
peticionHttp.onreadystatechange = procesaRespuestaXML;
peticionHttp.open("POST", "http://localhost/generaXMLConClase.php", true);
peticionHttp.send(null);
}
}
//Función en la que se procesa el documento XML recibido
function procesaRespuestaXML() {
if(peticionHttp.readyState == READY_STATE_COMPLETE) {
if(peticionHttp.status == 200) {
var documentoXml = peticionHttp.responseXML; //Creamos el objeto de tipo documento XML
var root = documentoXml.getElementsByTagName('Consulta')[0]; //Obtenemos la raíz del documento
//Accedermos al contenido de cada campo contenido en el elemento Informacion
var tabla = root.getElementsByTagName('Tabla')[0];
exploraRegistrosDeLaTabla(tabla);//Desplegamos todos los registros del elemento Tabla
}
}
}
/*Función que recibe el elemento que contiene los registros del documento XML, recorre cada uno de los
/*registros y almacena el valor de todos sus campos en el objeto objRenglon, al que después envía como
/*parámetro a la función muestraRenglon para desplegarlos en la tabla especificada
*/
function exploraRegistrosDeLaTabla(tablaXML) {
var objRenglon = new Object();
var tope1 = tablaXML.getElementsByTagName('Registro').length;
for(var i = 0; i < tope1; i++) {
registro = tablaXML.getElementsByTagName('Registro')[i];
tope2 = registro.getElementsByTagName('Campo').length;
for(var j = 0; j < tope2; j++) {
campo = registro.getElementsByTagName('Campo')[j];
objRenglon[campo.getAttribute('nombre')] = campo.firstChild.nodeValue;
}
muestraRenglon(objRenglon, 'tblInfo');
}
}
/**función que despliega un renglon dentro de la tabla HTML específicada a partir de las propiedades del
/* objeto objRenglon
*/
function muestraRenglon(objRenglon, idTabla) {
var tbody = document.getElementById(idTabla).getElementsByTagName("TBODY")[0]; //Referencia a la tabla
var renglon = document.createElement("TR"); //Crea un renglon
for(var llave in objRenglon){
var columna = document.createElement("TD"); //Crea una columna
columna.appendChild(document.createTextNode(objRenglon[llave])); //Agrega texto a la columna
renglon.appendChild(columna); //Agrega la columna al renglon
}
tbody.appendChild(renglon); //Agrega el renglon a la tabla
}
</script>
</head>
<body>
Para realizar la petición al servidor Web
<input type="button" value="presiona aqui" onclick="solicita()" />
<table id="tblInfo" cellspacing="1">
<tbody>
<tr style="background:#E1E1E1">
<td><strong>ID</strong></td>
<td><strong>Nombre</strong></td>
<td><strong>Nacionalidad</strong></td>
<td><strong>Profesión</strong></td>
</tr>
</tbody>
</table>
</body>
</html>


En ésta ocasión vemos que se hicieron unos cambios en la función procesaRespuestaXML, ya no se procesa el contenido completo del documento XML:
function procesaRespuestaXML() {
   if(peticionHttp.readyState == READY_STATE_COMPLETE) {
      if(peticionHttp.status == 200) {
         var documentoXml = peticionHttp.responseXML;
         var root = documentoXml.getElementsByTagName('Consulta')[0];
         var tabla = root.getElementsByTagName('Tabla')[0];
         exploraRegistrosDeLaTabla(tabla);
      }
   }
}

En cambio, después de acceder al elemento raíz del documento -en este caso Consulta- se crea el objeto tabla a partir del elemento Tabla, y éste es pasado como un parámetro a la función exploraRegistrosDeLaTabla, dentro de la cual se lleva todo a cabo la extracción de cada uno de los elementos que serán desplegados:
function exploraRegistrosDeLaTabla(tablaXML) {
   var objRenglon = new Object();
   var tope1 = tablaXML.getElementsByTagName('Registro').length;
   for(var i = 0; i < tope1; i++) {
      registro = tablaXML.getElementsByTagName('Registro')[i];
      tope2 = registro.getElementsByTagName('Campo').length;
      for(var j = 0; j < tope2; j++) {
         campo = registro.getElementsByTagName('Campo')[j];
         objRenglon[campo.getAttribute('nombre')] = campo.firstChild.nodeValue;
      }
      muestraRenglon(objRenglon, 'tblInfo');
   }
}


Ésta función esta formada por dos bucles for, dentro del primero se recorreran todos los elementos Registro que contenga el elemento Tabla, en el segundo bucle se recorrerán todos los elementos Campo que contenga del registro que sea accedido en el ciclo externo, almacenando los valores de cada uno de los campos en las propiedades del objeto objRenglon. Al terminar de recorrer todos los campos del registro se llamará a la función muestraRenglon, pesándole este objeto para que despliegue dentro de la tabla tblInfo un renglon que contenga los valores recogidos:
function muestraRenglon(objRenglon, idTabla) {
   var tbody = document.getElementById(idTabla).getElementsByTagName("TBODY")[0];
   var renglon = document.createElement("TR");
   for(var llave in objRenglon){
      var columna = document.createElement("TD");
      columna.appendChild(document.createTextNode(objRenglon[llave]));
      renglon.appendChild(columna);
   }
   tbody.appendChild(renglon);
}

Ésta función muestraRenglon tiene el propósito de agregar un nuevo renglón a la tabla especificada, con los valores que recibe como parámetro, solo que estos valores van contenidos dentro de las propiedades del objeto objRenglon, las cuales pueden ser extraídas como si se tratara de un arreglo asociativo dentro del bucle for de la función.

De esta forma queda finalizada la refactorización del cliente Ajax para el procesamiento de documentos XML, generados en PHP. Lo que queda en este caso es probar este nuevo programa almacenando en cliente en un archivo HTML, y guardando los archivos PHP ConsultaXML.php y generaXMLConClase.php en el directorio de páginas de nuestro servidor Web. Al probar el programa Javascript en el navegador deberemos obtener un resultado como el siguiente:



Conclusiones

El código PHP y Ajax que obtuvimos de este ejercicio nos será de gran utilidad en los ejemplos siguientes, sobre todo al momento de incursionar en el manejo de las bases de datos, al facilitarnos en gran medida el proceso de devolver el resultado de una consulta a nuestro cliente, así como el procesamiento de la información que éste lleva a cabo. El propósito de ésta práctica era contar con mejores armas para encarar los nuevos retos que nos estamos planteando, y aunque ahora puedan representa más trabajo, al adentrarse más en las aplicaciones de Ajax se verá rendir fruto a estos esfuerzos.

Hasta la próxima.

miércoles, 4 de febrero de 2009

Refactorizar el código que procesa XML, Parte 1


Los tres pasos de la buena programación

En el post anterior utilizamos un método para generar código XML del lado de PHP bastante simple, que funciona bien para el ejemplo debido la cantidad de datos que estábamos utilizando, sin embargo, al incrementarse el tamaño del documento XML generado, utilizar solamente llamadas a la función echo resultaría en un código fuente bastante largo y difícil de mantener, además la creación de documentos XML desde PHP se llevará a cabo en más de un programa, por lo que una forma de agilizar este proceso consiste en crear bloques de código reutilizables, que nos ahorren una parte considerable del trabajo al teclear nuestros programas. Una forma de hacerlo es utilizando grupos de funciones, cada una correspondiendo a un paso específico del proceso, otra forma sería el uso de clases para manejar el proceso completo, lo cual implicaría adentrarnos un poco en programación orientada a objetos en este lenguaje. Para la generación del documento XML diseñaremos una sencilla clase en PHP, utilizando el modelo de objetos de su versión 5. De esta forma la escritura de los siguientes programas será más ágil y sobre todo más fácil de mantener.


Consideraciones previas

La principal cuestión antes de continuar es conocer que formato tendrán los documentos XML que crearemos para los siguientes ejemplos, debido a que el próximo paso será el manejo de las bases de datos, consideraremos que los elementos del documento XML representarán los registros y campos obtenidos al realizar consultas a las bases de datos relacionales. Sería algo como lo que sigue:

<?xml version="1.0" encoding="utf-8"?>
<Consulta>
<Tabla nombre="Autores">
<Registro id="1">
<Campo nombre="Nombre">Juan Rulfo</Campo>
<Campo nombre="Nacionalidad">Mexicano</Campo>
<Campo nombre="Profesion">Novelista</Campo>
</Registro>
<Registro id="2">
<Campo nombre="Nombre">Pablo Neruda</Campo>
<Campo nombre="Nacionalidad">Chileno</Campo>
<Campo nombre="Profesion">Poeta</Campo>
</Registro>
<Registro id="3">
<Campo nombre="Nombre">Miguel de Cervantes Saavedra</Campo>
<Campo nombre="Nacionalidad">Español</Campo>
<Campo nombre="Profesion">Novelista, Poeta y Dramaturgo</Campo>
</Registro>
</Tabla>
</Consulta>


Así, los elementos principales estarán representados por la etiqueta Tabla, y cada tabla tendrá un identificador que será único, representado en este caso por el atributo nombre, que representa el nombre de la tabla dentro de la base de datos. En el ejemplo existe únicamente la tabla Autores, dentro de la cual encontramos un grupo de registros, los cuales se identifican por el atributo id. Dentro de todo registro, aparecerá una serie de elementos llamados Campos, que serán identificados por el atributo nombre, y los valores de cada campo están contenidos en sus propias etiquetas.

Refactorización del lado del Servidor

De forma que el código en PHP necesario para crear este tipo de documentos será el siguiente:


<?php
//Clase creada para manejar documentos XML que tengan el formato de una consulta a una base de datos
class ConsultaXML extends XMLWriter{
//Función que inicializa el documento XML, establece el contenido de su encabezado y abre el elemento raíz
function iniciaDocumento($elementoRaiz = "Consulta", $version = "1.0", $juegoDeCarateres = "UTF-8") {
$this->openMemory();
$this->setIndent(true);
$this->setIndentString(" ");
$this->startDocument($version,$juegosDeCaracteres);
$this->startElement($elementoRaiz);
}
/*
* Método que crea el elemento Campo dentro del documento, recibiendo como parámetros el nombre del campo
* y el valor que contendrá.
*/
public function creaCampo($nombreDelCampo, $valor) {
$this->startElement("Campo");
$this->writeAttribute("nombre",$nombreDelCampo);
$this->text($valor);
$this->endElement();
}
/*
* Método que crea el elemento Registro dentro del documento, recibiendo como parámetros el arreglo asociativo
* que contendrá los nombres y valores de los campos que poseerá el registro, junto con el identificaor del
* registro que será almacenado en su atributo "id".
*/
public function creaRegistroDesdeArreglo($arreglo, $idRegistro) {
$this->startElement("Registro");
$this->writeAttribute("id",$idRegistro);
if(is_array($arreglo)){
foreach ($arreglo as $llave => $valor){
$this->creaCampo($llave, $valor);
}
}
$this->endElement();
}
/*
* Método que crea el elemento Tabla dentro del documento, recibiendo como parámetros una matriz de arreglos
* asociativos, el nombre de la tabla y el nombre del campo que identificará a los registros que contendrá la
* tabla. Dentro de un ciclo se llamará a al método creaRegistro para cada uno de los arreglos asociativos
* que contenga la raíz, de esta forma se creará un documento XML que contenga todos los registros de una
* tabla obtenida en una consulta.
*/
public function creaTablaDesdeMatriz($matriz, $nombreDeLaTabla, $campoIdRegistro) {
$this->startElement("Tabla");
$this->writeAttribute("nombre",$nombreDeLaTabla);
if(is_array($matriz) && gettype($campoIdRegistro)=="string"){
foreach ($matriz as $llave => $renglon){
if($campoIdRegistro == "")
$this->creaRegistroDesdeArreglo($renglon, $llave);
else
$this->creaRegistroDesdeArreglo($renglon, $renglon[$campoIdRegistro]);
}
}
$this->endElement();
}

//Cierra el elemento raíz y finaliza el documento, regresando además el contenido del documento actual.
public function finalizaDocumento(){
$this->endElement();
$this->endDocument();
return $this->outputMemory();
}

/**
* Establece que el programa PHP actual generará como salida un archivo XML y envía a esta la salida el contenido del
* documento creado.
*/
public function salida(){
header('Content-type: text/xml');
echo $this->finalizaDocumento();
}
}
?>


Así, nuestra clase recibirá el nombre de ConsultaXML, ya que el propósito de ésta clase es crear documentos XML que representen conjuntos de datos estructurados, como lo que se obtienen de las consultas a bases de datos o a los archivos estructurados, o cualquier otro tipo de información que necesite de un formato similar.

Esta clase hereda sus métodos y propiedades de la clase predefinida XMLWriter, lo cual está indicado por la sentencia class ConsultaXML extends XMLWritter. Utilizamos esta clase predefinida porque ya posee todos los métodos necesarios para escribir documentos XML con etiquetas y atributos, adicionando además un control de errores para prevenir que los documentos creados contengan errores en su estructura. Otro punto importante es que XMLWriter permite dirigir el salida completa del archivo tanto a la memoria como al disco duro, de esta forma podemos utilizar la función echo para dirigir esta salida como respuesta del servidor al navegador, tal y como lo hicimos en el último ejemplo al respecto.

El primer método que encontramos en la clase es iniciaDocumento:
$this->openMemory();
$this->setIndent(true);
$this->setIndentString(" ");
$this->startDocument($version,$juegosDeCaracteres);
$this->startElement($elementoRaiz);

el cual deberá ser el primer método que se llama para crear el documento XML, en éste se establece que el nuevo objeto XMLWrite tendrá una salida en forma de cadena en la memoria, a través de la sentencia $this->openMemory(), se establece que existirá indentación hacia la derecha en el documento con $this->setIndent(true) y con $this->setIndentString(" ") definimos que el caracter espacio en blanco será utilizada para esta indentación. Se establece además el encabezado del documento, y finalmente se crea el primer elemento del documento, que es indicado por el parámetro $elementoRaiz. Como también podemos apreciar, en los parámetros se definen valores por defecto para facilitarnos el uso de este método, ya que éstos serán los que requeriremos en la mayoría de los ejemplos, teniendo siempre la posibilidad de establecer nuevos valores.

Los métodos creaCampo, creaRegistro y creaTabla llevarán a cabo toda la tarea de llenar el contenido del documento XML, permitiendo cada una de ellas crear los respectivos elementos Campo, Registro y Tabla, como habíamos establecido en las consideraciones previas.

El método finalizaDocumento cerrará la etiqueta del primer elemento que fue creado con iniciaDocumento, además finaliza el documento y devuelve todo su contenido como respuesta.

$this->endElement();
$this->endDocument();
return $this->outputMemory();

Esta respuesta puede ser recibida de diferentes formas, ya sea siendo almacenada en una variable como una cadena de texto, o mandada a alguna función para crear un archivo, o puede ser enviada a la función echo para el propósito que ya se describió, lo cual es precisamente lo que se hace en el siguiente método:
public function salida(){
header('Content-type: text/xml');
echo $this->getDocument();
}

Que antes de mandar el contenido como salida del programa, establece que el tipo de salida será la de un archivo XML, para que el navegador tenga conocimiento y la trate como tal.

Este código puede ser incluido dentro del script donde se vaya a llevar a cabo la generación del documento XML, o puede ser almacenado en un archivo .php aparte que será llamado por medio de la función require, lo cual personalmente recomiendo para facilitar su reutilización en otros programas. Para este ejemplo el código se almacenará en una archivo llamado ConsultaXML.php.

Implementación del nuevo código

Ahora pasemos a lo bueno, que es utilizar esta clase, para lo que escribiremos un nuevo programa en PHP cuyo archivo recibirá el nombre de generaXMLConClase.php, y creará un documento a partir de una matriz, conformada por arreglos asociativos. El código es el siguiente:


<?
require("PruebasPHP/ConsultaXML.php"); //Referencia al archivo que contiene la clase ConsultaXML
//Arreglos que representan la información del documento XML
$registro1 = array('idAutor'=>1,'Nombre'=>"Juan Rulfo",'Nacionalidad'=>"Mexicano",'Profesion'=>"Novelista");
$registro2 = array('idAutor'=>2,'Nombre'=>"Pablo Neruda",'Nacionalidad'=>"Chileno",'Profesion'=>"Poeta");
$registro3 = array('idAutor'=>3,'Nombre'=>"Miguel de Cervantes Saavedra",'Nacionalidad'=>"Español",'Profesion'=>"Novelista, Poeta y Dramaturgo");
$tabla = array($registro1, $registro2, $registro3);
//Creación del documento XML
$consultaXML = new ConsultaXML();
$consultaXML->iniciaDocumento();
$consultaXML->creaTablaDesdeMatriz($tabla, "Autores", "idAutor");
$consultaXML->salida();
?>


Nota: Tal vez está de más señalarlo pero recuerda que la ruta que coloques en la función require indica la ubicación del archivo ConsultaXML.php, por lo que si lo almacenas en un directorio distinto al de generaXMLConClase.php deberás indicar ese cambio en el código anterior.

Después del require, creamos una serie de arreglos asociativos que representan el contenido del documento XML definido previamente. En seguida se crea una instancia de la clase ConsultaXML llamada $consultaXML, y como vemos, al utilizar este objeto el código se vuelve bastante pequeño, que es una de las cosas que me gustan de la utilización de objetos: el encapsulamiento que ocurre dentro de los métodos, con el cual se facilita enormemente el trabajo con las clases diseñadas.

Al utilizar el método salida() todo el contenido será regresado al navegador como un archivo XML, y podemos probarlo utilizando un cliente como Internet Explorer o Mozilla Firefox, y mandando llamar al archivo PHP escribiendo una URL como la que sigue en su barra de direcciones:
http://localhost/generaXMLConClase.php

La salida se verá directamente en la pantalla del navegador, como se muestra en la siguiente ejemplo capturado, en el que se utilizó Firefox:



De ésta forma queda lista la refactorización del código para generación de XML del lado del servidor. El siguiente paso será llevar a cabo un cambio más, pero ahora del lado del cliente, para mejorar la forma en la que se acceda a los elementos del documento XML recibido. Sin embargo, ese será el tema de nuestro siguiente post.

Hasta la próxima.