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.

2 comentarios:

Unknown dijo...

¡Hola, atoBeto!

¡Felicidades por este manual de Ajax!

Lo he estado siguiendo y me parece excelente.

Sugerencia: Coloca a este artículo la etiqueta de Ajax para poder dar continuidad al tutorial.

¡Saludos!

Anónimo dijo...

Gracias a ti por la aportación, ya le agregué esa etiqueta y tendré más cuidado con la continuidad de los temas. Ahora no dispongo de mucho tiempo para iniciar nuevo temas pero esta semana trataré de ponerme al corriente.

Publicar un comentario