domingo, 23 de diciembre de 2007

Plantillas en PHP (IV)

Cuarta parte de esta serie. Como había dicho vamos a finalizar con el ejemplo e incluir el paginador en la plantilla principal.


Podríamos, como dije en el anterior post, incluir el paginador en nuestra plantilla haciendo un include del archivo que contiene la plantilla. Podríamos pensar que si todas las plantillas están en la misma carpeta no tenemos que especificar la ruta de la plantilla paginador(¿podemos poner simplemente: include "paginador.phpt";?), pero esto no es así. Como vimos, la plantilla principal en última instancia se carga con un include por lo tanto tendrá como path el del archivo que la haya invocado. Esto es un problema ya que si la plantilla principal puede ser invocada desde scripts en diferentes carpetas la ruta del include del paginador debería variar! Para solventar esto añadiremos un método __toString al objeto Template:

function __toString(){
$this->dump();
return "";
}

Ojo, este método no devuelve una cadena de texto, sino que hace un echo de la plantilla. Es decir, no tendría sentido hacer $cadena=$miPlantilla->__toString() ya que $cadena quedaría vacía. Se que esto no es del todo ortodoxo pero veremos que es una forma muy buena de representar objetos en una plantilla.

Esto nos permitirá crear ambas plantillas por separado de modo que especificamos las rutas de la plantillas desde el script que las va a usar:

$miPlantilla=new Template("plantillas/miPlantilla.phpt");
$paginador=new Template("plantillas/paginador.phpt");

Ahora en nuestra plantilla principal añadimos debajo de la tabla:

<?=$PAGINADOR?>

y en nuestro código PHP haremos:

$miPlantilla->set("PAGINADOR",$paginador);

De esta forma conseguimos incluir el paginador en la plantilla principal de manera elegante.

A continuación veremos como nos quedaría nuestra plantilla principal:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title><?=$TITULO?></title>
<style type="text/css">
#paginador{
}
#paginador a.pagina{
}
#paginador a.pagina:hover{
}
#paginador a.pagina:visited{
}
</style>
</head>
<body>
<table>
<thead>
<tr><th>campo 1</th><th>campo 2</th></tr>
</thead>
<tbody>
<? foreach($DATOS AS $D): ?>
<tr><td><?=$D->CAMPO1?></td><td><?=$D->CAMPO2?></td></tr>
<? endforeach ?>
</tbody>
</table>
<?=$PAGINADOR?>
</body>
</html>

Si nos fijamos en la sección style he referenciado todos los estilos al id paginador de forma que estos estilos no van a afectar al resto de la página. Tendremos que tener esto en cuenta para no definir ningún otro elemento con id="paginador", ya que los id deben ser únicos.

Hemos dicho en el anterior post que para indicar la página a mostrar usaríamos la variable $_GET["pagina"]. Por lo que si no está definida tendremos que mostrar la primera página y en caso contrario la que nos indique la variable. Veamos entonces la parte de código PHP:

<?php
$num_por_pagina=10;
$pagina=(isset($_GET["pagina"])?$_GET["pagina"]:1);
$desplazamiento=$num_por_pagina*($pagina-1);

$miPlantilla=new Template("plantillas/miPlantilla.phpt");
$miPlantilla->set("TITULO","KROWORK: Anotaciones de PHP (página ".$pagina.")");
$miPlantilla->set("PAGINADOR",$paginador=new Template("plantillas/paginador.phpt"));

list($num)=mysql_fetch_row($result=mysql_query("SELECT COUNT(*) FROM mitabla"));
mysql_free_result($result);

$paginador->set("NUM_PAGINAS", ceil($num/num_por_pagina) );

$result=mysql_query(
"SELECT micampo1 as CAMPO1, micampo2 as CAMPO2 FROM mitabla LIMIT ".$desplazamiento.",".$num_por_pagina
);
while($obj=mysql_fetch_object($result)) $miPlantilla->setArray("DATOS",$obj);

$miPlantilla->dump();
?>

Es sencillo, legible y tenemos separada la parte de presentación de la lógica de la aplicación.

Otra mejora sería cambiar en la plantilla del paginador el src de las imágenes que hacen de botones de "página siguiente" y "página anterior" por dos variables. Si usamos, por ejemplo $URL_FLECHA_IZQUIERDA y $URL_FLECHA_DERECHA, podremos especificar distintas imágenes según el script que use la plantilla y además evitamos problemas con las rutas como sucedía con el include de la plantilla paginador en la principal.

LEER MÁS……

lunes, 17 de diciembre de 2007

Plantillas en PHP (III)

En los dos anteriores artículos hemos definido un modo de crear plantillas utilizando el motor PHP y hemos visto un pequeño ejemplo de uso. En este post mostraré como realizar una plantilla más elaborada a partir de la que ya hemos visto añadiéndole una plantilla paginador.


Siguiendo con el ejemplo de los anteriores post, si tenemos muchos registros en la base de datos deberíamos dividir su visualización en páginas. Para navegar entre ellas podemos poner unos botones de "alante" y "atrás" o poner un paginador. Un paginador muestra los enlaces a las primeras páginas, a las vecinas de la actual y a las últimas páginas, además de los botones "anterior" y "siguiente".

Para probar esta plantilla sólo tendremos que cargarla con un objeto Template y establecer la variable NUM_PAGINAS:

<?php
include "/classes/Template.class.php";

$tmplt=new Template("platillas/paginador.phpt");
$tmplt->set("NUM_PAGINAS",20);
@$tmplt->dump();
?>


La plantilla en cuestión es la siguiente, aunque el código es muy complejo no es necesario entenderlo para usarla, como ya hemos visto:

<?
$CENTRO=2; //numero de paginas a cada lado de la página seleccionada ...k-2,k-1,k,k+1,k+2...
$LADO=3; //número de primeras paginas 1,2,3... y de ultimas...n-2, n-1, n

//Preparamos la URL siguiente
$PAGINA=(empty($_GET["pagina"])?1:$_GET["pagina"]);
unset($_GET["pagina"]);
$URL_PAGINA=$_SERVER["PHP_SELF"]."?".http_build_query($_GET);
?>
<div id="paginas">
<? if($NUM_PAGINAS>1): ?>

<!-- BOTON PAGINA ANTERIOR -->
<? if($PAGINA>1): ?>
<a href="<?=$URL_PAGINA?>&amp;pagina=<?=($PAGINA-1)?>" class="pagina">
<img src="imagenes/flecha_izq" alt="Página anterior" />
</a>
<? endif ?>

<!-- ENLACES A LAS PRIMERAS PAGINAS -->
<? for($i=1;$i<$LADO+1 && $i<$NUM_PAGINAS+1 && $i<$PAGINA-$CENTRO;$i++): ?>
<a href="<?=$URL_PAGINA?>&amp;pagina=<?=$i?>" class="pagina">
<?=$i?>
</a>
<? endfor ?>

<!-- SEPARADOR -->
<? if( $PAGINA>$LADO+$CENTRO+1 ): ?>
...
<? endif ?>

<!-- PAGINAS VECINAS A LA ACTUAL -->
<? for( $i=((($PAGINA-$CENTRO)>0)?($PAGINA-$CENTRO):1); $i<$NUM_PAGINAS+1 && $i<$PAGINA+$CENTRO+1; $i++ ): ?>
<? if($i==$PAGINA): ?>
<?=$i?>
<? else: ?>
<a href="<?=$URL_PAGINA?>&amp;pagina=<?=$i?>" class="pagina">
<?=$i?>
</a>
<? endif ?>
<? endfor ?>

<!-- SEPARADOR -->
<? if($NUM_PAGINAS>$PAGINA+$CENTRO+$LADO): ?>
...
<? endif ?>

<!-- ENLACES A LAS ULTIMAS PAGINAS -->
<? if( $NUM_PAGINAS>$PAGINA+$CENTRO ): ?>
<? for( $i=((($NUM_PAGINAS-$LADO+1)<=($PAGINA+$CENTRO))?($PAGINA+$CENTRO+1):($NUM_PAGINAS-$LADO+1)); $i<$NUM_PAGINAS+1; $i++ ): ?>
<a href="<?=$URL_PAGINA?>&amp;pagina=<?=$i?>" class="pagina">
<?=$i?>
</a>
<? endfor ?>
<? endif ?>

<!-- BOTON PAGINA SIGUIENTE -->
<? if($PAGINA<$NUM_PAGINAS): ?>
<a href="<?=$URL_PAGINA?>&amp;pagina=<?=($PAGINA+1)?>" class="pagina">
<img src="imagenes/flecha_der.gif" alt="Página siguiente" />
</a>
<? endif ?>
<? endif ?>
</div>

Como es de espera estaremos accediendo al script que muestra la lista a través de una llamada GET. La plantilla se encarga de regenerar la URL de esta llamada (con todas sus variables) añadiendo la variable "pagina" para indicar(obviamente) la página a la que se desea acceder. Esto estrictamente no debería estar en la plantilla y la variable URL_PAGINA deberíamos establecerla nosotros desde fuera de la plantilla, pero vamos a utilizar este atajo ya que tenemos las funciones PHP a nuestra disposición y esto nos simplifica mucho las cosas.

¿Pero como incluirla en la plantilla de la lista? Pues con un include, pero esto y otros detalles sobre los CSS de la plantilla lo dejamos para el próximo post.

LEER MÁS……

jueves, 13 de diciembre de 2007

Plantillas en PHP (II)

Hemos visto en el anterior post que no es necesario aprender la sintaxis de ningún lenguaje de plantillas para poder trabajar con ellas. Una sintaxis reducida del PHP será más que suficiente para disfrutar de sus ventajas.


En este post vamos a definir la clase Template; a través de un objeto de esta clase se carga la plantilla y se le da valores a las variables:

<?php
class Template{
private $VARS=array();
private $archivo;

function __construct($archivo){
$this->archivo=$archivo;
}
//establecemos las variables simples de la plantilla
public function set($nombre,$valor){
if($valor===NULL) unset($this->VARS[$nombre]);
else $this->VARS[$nombre]=$valor;
}
public function setArray($nombre,$valor){
if(empty($this->VARS[$nombre])) $this->VARS[$nombre]=array();
$this->VARS[$nombre][]=$tmp;
}
public function get($nombre){
return $this->VARS[$nombre];
}

public function dump($buffer=false){
if($buffer) ob_start();
if(!empty($this->VARS)) foreach($this->VARS as $variable=>&$valor)
$$variable=$valor;
include($this->archivo);
if($buffer) return ob_get_clean();
}

function __clone() {
foreach($this->VARS as $k=>$v)
if(is_object($v)) $this->VARS[$k]=clone $this->VARS[$k];
}
}
?>

Veamos como utilizarla. Supongamos que hemos guardado la plantilla del anterior post en el archivo "miPlantilla.phpt" si queremos ponerle el título solo tendremos que hacer:

<?php
$miPlantilla=new Template("plantillas/miPlantilla.phpt");
$miPlantilla->set("TITULO","KROWORK: Anotaciones de PHP");
?>

Ahora veamos como crear el array $DATOS de la plantilla a partir de una consulta a la base de datos:

$result=mysql_query("SELECT micampo1 as CAMPO1, micampo2 as CAMPO2 FROM mitabla");
while($obj=mysql_fetch_object($result)) $miPlantilla->setArray("DATOS",$obj);
?>

Y por último mostramos los datos:

<?php
@$miPlantilla->dump();
?>

Utilizamos @ para ocultar los posibles errores/warnings de la plantilla, ya que va a ser habitual que para simplificar comprobemos la existencia de las varibles con una expresión del tipo: <? if($VAR): ?> ... <? endif ?>. Si no hemos definido $VAR se producirá un warning que de esta forma no se mostrará aunque tengamos el reporte de errores al máximo. Evidentemente mientras probamos la plantilla no pondremos la @ para poder detectar los errores.

Como dije en el post anterior, no es raro crear plantillas con archivos xml's. La construcción de un xml nodo a nodo a través de la extensión DOM de PHP es enormemente tediosa. Una forma mucho más fácil de hacerlo es creando una plantilla (ojo: si utilizas short tags deberás hacer uso del truco del post anterior), rellenarla y al final:

<?php
file_put_contents("mi_nuevo_archivo.xml",$miPlantilla->dump(true));
?>

Al pasar true al método dump() hacemos que en lugar de mostrarse el resultado se devuelva en forma de string.

Como vemos las plantillas son muy versátiles, muy fácil de usarlas, de crearlas y al estar escritas en PHP su procesamiento es de lo más rápido que os podais encontrar. Hasta la próxima.

LEER MÁS……

viernes, 16 de noviembre de 2007

Plantillas en PHP (I)

Tras siglos desde mi último post me he decidido a retomar mis escritos con otra serie dedicada a las plantillas. Este primer artículo lo dedicaré a dar una visión general sobre el tema, veremos porqué son útiles y en qué nos ayudan. En los post siguientes propondré una implementación propia de un sistema de plantillas.


Mucha gente, entre la que me incluyo, empezó a programar PHP como un lenguaje de scripting insertado dentro del código HTML. Resultaba muy intuitivo extender las funcionalidades de tu página incluyendo pequeños trozos de código PHP.

El problema surgió cuando a medida que añadimos más funcionalidades el código de nuestras páginas se convierte en El Laberinto del Minotauro. Encontrar y corregir errores tanto HTML/JavaScript como PHP se hace enormemente tedioso al tener todo el código mezclado. Este tipo de escritura de código es altamente interdependiente además de compartir lógica y presentación la mismas variables.
Esta situación es mala de cara a la gestión de errores, mala frente a futuras modificaciones, mala porque no podemos aprovechar código de un proyecto a otro y mala porque no podemos dividir el trabajo entre diseñador y programador.

¿Como hacer código menos interdependiente?


Pensemos como sería una pagina HTML con el mínimo código PHP, para representar el típico resultado de una búsqueda en nuestra base de datos:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title><?=$TITULO?></title>
</head>
<body>
<table>
<thead>
<tr><th>campo 1</th><th>campo 2</th></tr>
</thead>
<tbody>
<? foreach($DATOS AS $D): ?>
<tr><td><?=$D->CAMPO1?></td><td><?=$D->CAMPO2?></td></tr>
<? endforeach ?>
</tbody>
</table>
</body>
</html>


Primero, SÍ, utilizo short tags, para activarlas deberás editar tu php.ini poniendo short_open_tag=1. Pero, un momento, seguro que habréis visto en más de un sitio que no se deben utilizar las short tag que son la reencarnación del diablo y otras lindeces, veamos porqué:

  1. No se deben utilizar las short tags porque producen problemas si queremos hacer un include de un xml. Los archivos xml empiezan con <?xml y por lo tanto el PHP (con short tags activadas) interpreta que ahí empieza un bloque PHP. Esto se soluciona si en lugar de empezar de ese modo el xml lo empezamos así: <<? ?>?xml . ¿Supone esto un gran problema para no hacer uso de las short tags? Cada uno tendrá su opinión.

  2. No es raro hacer un include de un xml sobre todo si vamos a utilizar un sistema de plantillas basado en PHP, que en última instancia hará un include de la plantilla, y si ésta es un xml surgirá el problema.


A mi personalmente no me parece un gran problema si a cambio puedo utilizar <?= ?> que resulta un gran aliado para legibilidad de las plantillas, en todo caso cada uno es libre de utilizar <?php echo.

Dicho esto diré también que ya tenemos nuestra primera plantilla PHP. Metan ese código en un archivo, guárdenlo en UTF-8 y pónganle una extensión del tipo .phpt. A partir de ahora diferentes scripts podrán utilizar la misma plantilla o un mismo script utilizar plantillas diferentes. Además, se requieren unos mínimos conocimientos de PHP para hacer una plantilla por lo que fácilmente podrá hacerlas un diseñador mientras nosotros nos dedicamos a la miga de la aplicación y que se pelee él con el JavaScript, el DOM y las CSS (sí teneis la gran fortuna de tener un buen diseñador a mano).

Pero, ¿como instanciar esa plantilla y rellenar los huecos? paciencia, lo veremos en la próxima entrada.

LEER MÁS……

lunes, 14 de mayo de 2007

Guardar propiedades static en $_SESSION (IV)

En esta tercera parte del artículo veremos la clase ClassStore.

Cada objeto ClassStore va a servir como un contenedor de las propiedades static de cada una de las clases utilizadas durante la ejecución del script. Todos estos objetos se van a guardar en un array indexado por el nombre de la clase: $_SESSION["__statics"][$nombre_de_la_clase]. El método estático __storeStatics (identico al código visto al final del último post) realiza estas acciones llamando a los métodos __getStatics() de las clases declaradas.


Para ello debemos hacer una lladama a __storeStatics cuando finalice la ejecución del script:

register_shutdown_function(array('ClassStore','__storeStatics'));

A continuación la clase ClassStore:

class ClassStore{
private $nombre;
private $statics;

function __construct($nombreClase,$statics){
$this->nombre=$nombreClase;
$this->statics=$statics;
}

function __wakeup(){
call_user_func(array($this->nombre,'__restoreStatics'),$this->statics);
}

static function __storeStatics(){
$classes=get_declared_classes();

foreach($classes as $name){
$reflejo=new ReflectionClass($name);

if($reflejo->hasMethod('__getStatics')){
$method=$reflejo->getMethod('__getStatics');

if($method->getDeclaringClass()->getName()===$name){

if(empty($_SESSION["__statics"]))
$_SESSION["__statics"]=array();

$_SESSION["__statics"][$name]=new ClassStore($name,call_user_func(array($name,'__getStatics')));

}
}
}
}
}

Si nos fijamos bien, cada vez que se recupere la sesion con session_start() el array $_SESSION se deserializa y se reconstruyen los objetos que guarda. Después de reconstruidos se llama al __wakeup de todos aquellos que lo tengan definido. De este modo cada unos de los objetos ClassStore devolverá a su valor anterior las propiedades static de cada una de las clases.

RECORDEMOS: en aquellas clases que queramos mantener estas propiedades debemos definir los métodos __getStatics y __restoreStatics como hemos visto. A continuación, en el script de cabecera, dónde tengamos el autoload de las clases y el session_start() registramos la llamada a la función __storeStatics para cuando finalice el script: register_shutdown_function(array('ClassStore','__storeStatics')). Y voilá, ya se mantienen las propiedades static.

Ir a... [I][II][III]

LEER MÁS……

viernes, 11 de mayo de 2007

Guardar propiedades static en $_SESSION (III)

Como había dicho en mi anterior post, en éste veremos el API de Reflexión con el que viene provisto el PHP 5.

Nuestro primer contacto con este API lo hemos tenido cuando definíamos la función __getStatics(), recordemos:



static function __getStatics(){
$statics=array();
$reflejo=new ReflectionClass(__CLASS__);
$properties=$reflejo->getProperties();

foreach($properties as $p) if($p->isStatic() &&
$p->getDeclaringClass()->getName()===__CLASS__ ) {

$nombre=$p->getName();
$valor=self::$$nombre;
if(!is_resource($valor)) $statics[$nombre]=$valor;
}
return $statics;
}

Como vemos el API nos sirve para obtener información de las clases que hay definidas. En este caso obtenemos el objeto ReflectionClass que representa a la clase en la que está definido éste método. El método getProperties() nos devuelve todas las propiedades en un array de objetos ReflectionProperty que representa a cada una de las propiedades de la clase.

Este array nos devuelve TODAS las propiedades, incluso las heredadas, por lo que tenemos que hacer una criba y seleccionar únicamente aquellas que están declaradas directamente en la clase, que sean static y no sean un recurso (ya que no tiene sentido guardarlos de una ejecucion a otra).

Como habíamos dicho éste método debemos definirlo en todas aquellas clases en las que queramos mantener valores static de una ejecución a otra del script, ahora bien debemos ver como llamar a este método de una forma automatizada:
$classes=get_declared_classes();

foreach($classes as $name){
$reflejo=new ReflectionClass($name);

if($reflejo->hasMethod('__getStatics')){
$method=$reflejo->getMethod('__getStatics');

if($method->getDeclaringClass()->getName()===$name){

if(empty($_SESSION["__statics"]))
$_SESSION["__statics"]=array();

$_SESSION["__statics"][$name]=new ClassStore($name,call_user_func(array($name,'__getStatics')));
}
}
}

Para cada una de las clases declaradas en el script comprobamos cuales de ellas tienen definido el método __getStatics (no heredado). Para cada una de esas clases se crea un objeto ClassStore al que se le pasan el array de valores de las propiedades static. Cada uno de estos objetos se guarda en $_SESSION["__statics"].

En el siguiente post veremos el objet ClassStore, que automatizará la recuperación de los datos de las propiedades static.

Ir a... [I][II][IV]

LEER MÁS……

jueves, 10 de mayo de 2007

Guardar propiedades static en $_SESSION (II)

Como contaba en mi anterior entrada al contrario del resto de propiedades de un objeto (almacenado en el array $_SESSION) las static, por pertenecer a la clase y no al objeto, no se recuperan con la llamada a la función session_start(). Exprimiendo un poco el coco llegué a una solución bastante eficiente y poco sangrante.



¿A que me refiero con poco sangrante?

Cuando se está buscando un parche, como en este caso, siempre hay que tragar con unas cuantas líneas de código extra. Lo más importante es que éste resulte localizable y que sea fácil de eliminar llegado el caso. El código que propongo se compone de una clase extra que actuará independientemente, y de dos método "mágicos" en cada clase que tenga propiedades static que deseemos mantener en session. Estos métodos son iguales para todas y son muy simples:

static function __restoreStatics($statics){
foreach($statics as $nombre=>$valor)
self::$$nombre=$valor;
}

static function __getStatics(){
$statics=array();
$reflejo=new ReflectionClass(__CLASS__);
$properties=$reflejo->getProperties();

foreach($properties as $p) if($p->isStatic() &&
$p->getDeclaringClass()->getName()===__CLASS__ ) {

$nombre=$p->getName();
$valor=self::$$nombre;
if(!is_resource($valor)) $statics[$nombre]=$valor;
}
return $statics;
}

¿Por qué son imprescindible estos métodos?

Sólo un método de la propia clase puede acceder las propiedades private y protected (en las primeras versiones, PHP 5 no era tan estricto). Cualquier posible solución tiene que contar con dos métodos definido en aquellas clases con propiedades static que queramos conservar.
Ojo: estos métodos no pueden ser heredados, hay que definirlos para cada clase. Veamos como funcionan:

  1. El método __getStatics apollado en un objeto ReflectionClass obtiene los atributos static de la clase (objetos ReflectionProperty). Comprueba que no se trata ni de un atributo heredado ni de un recurso y devuelve un array asociativo indexado por el nombre de cada atributo.

  2. El método __restoreStatics hace lo contrario, se le pasa el array de statics y vuelve establecer los atributos a los valores dados.


Por lo dicho ya vemos que el método __getStatics debería ser invocado cuando se finalice el script, almacenando el resultado que obtengamos para cada clase. Al contrario, el método __restoreStatics será llamado cuando se inicie la ejecución del script. Si prefieres puedes ver estos dos métodos como dos más de los "métodos mágicos", como __sleep o __wakeup, así no suena tan mal.

Con estos dos métodos ya casi tenemos solucionado todo el problema. En el siguiente post veremos con más detalle la API de Reflexión que parece va adquiriendo importancia en este problema.

Hasta el próximo
Ir a... [I][III][IV]

LEER MÁS……