Apr 02

Desde la llegada de PHP 5.0 disponemos de varios métodos mágicos para nuestras clases PHP, entre ellos tenemos __toString() que nos permite codificar cómo queremos que se comporte una clase cuando una instancia de ella se convierte a un string.

Aunque a simple vista el método __toString() pueda parecer poco importante en PHP éste toma mucha relevancia si trabajamos con symfony o algún otro framework orientado a objetos donde la información de la base datos se encuentra mapeada en un modelo de objetos.

A pesar de que __toString() está disponible desde la versión 5.0.0 del PHP en mi opinión no empieza a ser realmente útil hasta la versión 5.2.0, algo que en el changelog del PHP reflejaron con un tímido:

Changed __toString() to be called wherever applicable. (Marcus)

Hasta entonces __toString() sólo se llamaba cuando se usaba echo() o print() lo que limitaba mucho su funcionalidad. Desde la versión 5.2.0 __toString() se llama siempre que tratemos a un objeto como a un string.

Por ejemplo dada la siguiente definición de clase:

[php] class User extends BaseUser
{
public function __toString()
{
return $this->getName();
}
} [/php]

Hasta PHP 5.2.0 sólo podíamos invocar a __toString() con:

[php] $user = new User();
echo $user;
print $user; [/php]

Desde la versión 5.2.0 podemos hacer varias cosas interesantes con __toString() sobretodo relacionadas con el manejo de arrays de objetos, algo muy frecuente en los frameworks que corren por ahí hoy en día.

Por ejemplo si obtenemos el típico array de objetos con symfony:

[php] $users = UserPeer::doSelect(new Criteria()); [/php]

Trabajando directamente con las funciones de PHP entre otras muchas cosas podemos:

  • Ordenar el array de objetos con un simple sort()
  • Eliminar objetos duplicados del array con un array_unique()
  • Generar una lista separada por comas para la presentación con un implode()
  • Buscar un objeto determinado dentro del array con array_search()

Alternativas a __toString()

Si no tenemos la suerte de trabajar con PHP 5.2.x, o si queremos poder trabajar con un método distinto de __toString() para determinadas operaciones con arrays de objetos, podemos usar un código parecido al que propongo a continuación:

[php] class objectTools
{
protected static function getMethodValues($list, $method)
{
$items = array();
foreach ($list as $key => $obj)
$items[$key] = $obj->$method();
return $items;
}
protected static function getObjectList($items, $list)
{
$ret = array();
foreach ($items as $key => $item)
$ret[] = $list[$key];
return $ret;
}
public static function arraySortByMethod($list, $method, $sort=’desc’)
{
$items = self::getMethodValues($list, $method);
asort($items);
if ($sort==’desc’)
return array_values(array_reverse(self::getObjectList($items, $list)));
return self::getObjectList($items, $list);
}
public static function arrayUniqueByMethod($list, $method)
{
$items = self::getMethodValues($list, $method);
return self::getObjectList(array_unique($items), $list);
}
public static function arrayImplodeByMethod($list, $method, $sep)
{
$items = self::getMethodValues($list, $method);
return implode($sep, $items);
}
public static function arraySearchByMethod($list, $method, $needle)
{
$items = self::getMethodValues($list, $method);
return array_search($needle, $items);
}
} [/php]

Lo anterior es más una propuesta de código que algo decente para ser distribuido. Simplemente se trata de trabajar con un array temporal para almacenar los valores del método solicitado, correr la función PHP y, si es necesario, volver a construir el array de objetos.

Algunos ejemplos de uso:

[php] $ordenados = objectTools::arraySortByMethod($users, ‘getName’, ‘asc’);
$sin_duplicados = objectTools::arrayUniqueByMethod($users, ‘getName’);
$pos = objectTools::arraySearchByMethod($users, ‘getName’, ‘oriol’);
//y por último en una plantilla…
echo ‘Usuarios: ‘ . objectTools::arrayImplodeByMethod($users, ‘getName’, ‘, ‘); [/php]

Fácilmente se pueden añadir tantos métodos de tratamiento de arrays como se necesiten… o mucho mejor hacer un método que simplemente reciba como variable la función PHP a ejecutar. En mi caso de momento sólo necesito estos en concreto y también así los puedo controlar individualmente.

Aunque estos métodos de objectTools nacieron como “parche” rápido dado que no tenía PHP 5.2.x para un proyecto symfony, ahora, una vez solventando el problema con los servidores, los sigo encontrando útiles en múltiples situaciones. Por supuesto se ha de tener presente el poco rendimiento de este código frente a realizar queries a medida usando el objeto Criteria, pero si ya tenemos un array de objetos en memoria sí que será más óptimo trabajar con él en lugar de lanzar varias queries contra la base de datos.

Tagged with:
Apr 25

Una de las técnicas más habituales en el desarrollo de aplicaciones PHP orientadas a objetos es crear un archivo fuente para cada definición de clase. Con esta técnica es necesario hacer require de muchos archivos en el código para cada ejecución. Nuestro fantástico PHP5 nos ofrece la posibilidad de cargar automáticamente las clases que necesita bajo demanda definiendo la función __autoload().

Otra gran ventaja de usar autoload es que nos aseguramos de que sólo incluimos las definiciones de las clases que realmente usamos. Es frecuente en la vida de una aplicación que debido a actualizaciones se nos queden requires de clases que ya no usamos o que por ejemplo tengamos la costumbre de hacer todos los requires que va a usar una clase en la cabecera, entonces para ejecutar un sólo método que quizás sólo usa un par de clases estamos cargando clases innecesarias.

A pesar de que normalmente el uso de autoload provoca una mejora importante en el rendimiento ya que sólo se cargan las clases que usamos se ha de tener en cuenta que el código que tengamos en la función __autoload() debe ser lo más ligero posible ya que dependiendo del tamaño de la aplicación se va a ejecutar muchísimas veces.

Una función __autoload() de ejemplo podría ser algo como:

[php] function __autoload($class_name) {
$file = ‘/classes/’.$class_name.’.php’;
if (file_exists($file)) require_once $file;
} [/php]

Si tiras de file_exists() y constantemente estás añadiendo y quitando clases quizás te resulta interesante usar clearstatcache() antes para limpiar la cache de estados de archivo.

Como es necesario definir el autoload al principio de cada script resulta útil usar la directiva auto_prepend_file, si no tienes acceso a tu PHP.INI puedes añadir lo siguiente en tu .htaccess:

php_value auto_prepend_file "funcion_autoload.php"

Una última consideración es que en teoría no se pueden lanzar exceptions desde dentro de la función __autoload(), aunque usando eval() para cargar la definición de la clase exception deseada lo puedes conseguir (en las notas del manual tienes varios ejemplos).

Tagged with:
Jan 22

Una de las técnicas más usadas en el desarrollo de aplicaciones PHP es la de mezclar la lógica y el código necesarios para la presentación con la lógica de negocio de la aplicación, todo en un mismo archivo. Esto, entre otros problemas, dificulta enormemente el mantenimiento y cualquier tipo de ampliación o modificación de la aplicación.

Como solución a esta situación hace tiempo que aparecieron los sistemas de plantillas. Un sistema de plantillas tiene como objetivo principal ofrecer los mecanismos necesarios para separar por un lado la lógica de negocio y por el otro la lógica y el código necesarios para la presentación. Como consecuencia se separa el código PHP del código HTML lo que, en teoría, ofrece la posibilidad de que alguien sin conocimientos de PHP modifique la apariencia de nuestra aplicación.

Este es el objetivo que debería tener un sistema de plantillas y no, como algunos, únicamente limitarse a separar código HTML de código PHP sin ofrecer ninguna facilidad para codificar la lógica necesaria para la presentación. Existen muchos casos en los que necesitamos de cierta “lógica” para presentar los datos. Por ejemplo, si la lógica de negocio es obtener un determinado listado de BD y la lógica de presentación es mostrarlo en cuatro columnas sería incorrecto (y una tontería) hacer que la función encargada de obtener los datos de BD devolviera cuatro arrays, esa función no debería tener que preocuparse de como se presentan los datos que devuelve (forma parte del Modelo en un MVC).

Puedes intentar separar la lógica de la presentación de la presentación en si, es decir, con el ejemplo anterior sería crear una función (capa) intermedia que se encargara de separar los datos devueltos en cuatro arrays. Esto, al menos a mi modo de ver, no tiene ningún objetivo válido y son ganas de rizar el rizo, ya que ni que trabajemos con un MVC esta función que separa en cuatro arrays sigue formando parte de la Vista, sumado a que creamos un código menos óptimo y que, si te paras a pensar en un caso práctico, ¿no debería ser tarea del diseñador (plantillas) cambiar la apariencia y en lugar de listar cuatro columnas listar cinco?. A pesar de realizar esta separación dentro de la Vista existen muchos ejemplos que demuestran que es casi imposible no necesitar codificar cierta “lógica” en las plantillas (ni que sea un “if” para decidir que código se genera dependiendo del navegador). El objetivo de un sistema de plantillas como Smarty es codificar esta lógica asociada a la presentación en las plantillas ya que sino simplemente sería un sistema de sustitución de variables y no lo que es.

Después de esta breve explicación acerca de lo que es, o de lo que en mi opinión debería ser, un sistema de plantillas voy a centrarme en el caso de Smarty. Smarty cumple perfectamente los objetivos planteados a parte de ofrecer numerosas características adicionales. Personalmente empecé a usar Smarty unos tres años atrás, hasta entonces siempre había trabajado con sistemas de plantillas propios usando el PHP como lenguaje para el equivalente a las TPL separando perfectamente lógica de negocio de presentación, o muchos más años atrás con soluciones “mixtas” como trabajar con temas, algo parecido a lo que hacía el php-nuke (un poco cutre, sí, pero increíblemente cómodo y efectivo).

Aunque cuando empecé a usar Smarty no andaba muy convencido durante los tres últimos años lo he usado para casi todo en numerosos proyectos (algunos con decenas de millones de páginas vistas por mes) y como me temía, aún se han hecho más evidentes los problemas que ya detectaba desde un principio. Todos los problemas son consecuencia de lo mismo, Smarty es un sistema de plantillas basado en el procesado de archivos de tags (las TPL). Sólo aclarar que el objetivo de este post (si es que tiene alguno) no es proclamar el dejar de usar los sistemas de plantillas sino usar sistemas donde el mismo PHP sea el lenguaje de las plantillas.

Problemas comunes en los sistemas de plantillas basados en el procesado de archivos de tags (comparándolo con el uso directo del PHP):

  • Tienen un problema importante de rendimiento. No hace falta decir que con el uso de plantillas de tags estamos añadiendo un lenguaje más a procesar e interpretar desde PHP. Smarty es un sistema escrito en PHP con miles de lineas de código, pues a parte de tener que incluirlas en tu aplicación es desde el PHP que se va a interpretar este pseudo-lenguaje de las TPL. Para paliar esto Smarty compila las plantillas para no tener que parsearlas cada vez, gracias a esto es uno de los más rápidos, pero cada vez que cambies las TPL tendrá que parsearlas y se ha de tener en cuenta que el código que genera siempre será peor que el q harías tu directamente en PHP. Con la compilación Smarty reduce mucho este problema pero al disponer de tantas funcionalidades puedes llegar a hacer plantillas medianamente complejas que tardarán muchísimo a la hora de ser compiladas (en una aplicación (.com) en constante desarrollo debido a cambios drásticos en el código más de una vez es necesario borrar las plantillas compiladas manualmente para regenerarlas). Dependiendo del tipo de aplicación y número de plantillas esto puede ser muy preocupante o no: no es lo mismo una aplicación no muy dinámica que funcione con relativamente pocos tipos de pantallas (pocas plantillas distintas) como un CMS donde la mayoría de peticiones las puede atender un sistema de cache externo al PHP (Squid) y si le sumas un acelerador de código puedes llegar a trabajar bien con Smarty, que en el otro extremo, una aplicación muy dinámica (muy web 2.0 :) con participación de los usuarios, AJAX, varios RSS, etc. lo que comporta muchas plantillas distintas y poder usar comparativamente poca cache, en estos casos si que al menos has de disponer del acelerador de código sino a la que tengas mucho tráfico verás caer tu rendimiento.
  • Aunque no mucho pero dificultan el mantenimiento. En el caso de Smarty es necesario disponer de directorios para guardar las plantillas compiladas, varios archivos, TPL, probablemente archivos de configuración, y como no el Smarty en si (instalarlo, mantenerse actualizado, posibles bugs, etc.). En el caso de Smarty es necesario disponer de distintos directorios para compilación y cache para plantillas con el mismo nombre, si a esto le añades q para mantener un poco de orden y poder cómodamente administrar (y desarrollar) los distintos módulos de una aplicación has de ir creando directorios, puedes acabar con un número considerable de directorios para cache y compilación. (por cierto, varios programadores trabajando con las mismas TPL necesitarán de directorios compile_dir individuales para ver lo que están haciendo y no lo del vecino).
  • Es necesario aprender un nuevo pseudo-lenguaje de programación… Algún problema, no, pero es un fastidio tener que ir constantemente a ver como narices consigues hacer algo con Smarty que con PHP es trivial y más óptimo. En el caso de Smarty puedes llamar al PHP y solucionado claro, pero entonces… maldita la gracia, son ganas de mandar de ruta turística al intérprete de PHP por miles de lineas de código para acabar haciendo lo que podía hacer desde un principio.
  • En mi opinión, dificultan bastante el desarrollo. A parte de tener que aprender un nuevo lenguaje como siempre añadir una capa más de librerías de una tercera parte introduce problemas. En varias ocasiones en el desarrollo aparecen “fatal errors” dentro de las librerías Smarty (no en tu código) provocados por un fallo en tus plantillas o en los datos que reciben… (cojonudo!). Otro fallo frecuente es con una plantilla que ya está compilada cambiar el código PHP que la genera agregando o quitando funcionalidades usadas en la plantilla pero sin tocar para nada la plantilla… en este caso te puede llegar a aparecer un “fatal error” indicando la línea dentro de la plantilla compilada (q tampoco es tu código)… muy práctico también :|
  • No es garantía de que se cumpla el objetivo de un sistema de plantillas, imprescindible si trabajamos con MVC. Como exponía anteriormente, una de las consecuencias de necesitar de cierta lógica en las plantillas es que los sistemas como Smarty proporcionan estructuras de control (“if”, “foreach”), funciones, variables, operadores, etc. pudiendo hacer casi todo lo que puedes hacer con el mismo PHP, con lo que pasa a ser responsabilidad del programador no codificar lógica de negocio en las plantillas ya que poder, puede.
  • No es tan fácil de usar para un diseñador. No cumple el objetivo de que un diseñador sin conocimientos de programación (en PHP) pueda llegar a realizar el mantenimiento de la presentación de un site modificando las plantillas. Si a la lógica de presentación existente en las TPL (algunos “if”, “foreach”, etc.) le sumas que la gracia de usar Smarty es usar su potencial (generación de forms a base funciones, modificadores de texto, plugins propios, y un largo etc.) resultan unas TPL inteligibles para alguien que no sea programador (de PHP o de lo que sea, pero programador). En su Why Use Smarty venden como una de sus características destacables que un diseñador sin conocimientos de programación PHP puede fácilmente aprender Smarty porque son tags muy fáciles de usar, ¿realmente creéis que es mucho más fácil de entender {$foo} que <?=$foo;?> o {if –} que <?if –?> para un diseñador?…. deben haber realizado un estudio científico con un grupo de diseñadores para afirmar esto ¿no? Si quieres que un diseñador sin conocimientos de programación llegue a modificar las TPL del Smarty (con el Dreamweaver en “Modo Diseño”) de un site medianamente complejo te ves obligado a usar Smarty casi como un sistema simple de sustitución de variables.

Con el último punto sólo aclarar q en absoluto pretendo faltar a los diseñadores. Simplemente expongo que ni de coña Smarty es la solución mágica, seguro que unos cuantos de los que lean esto han tenido que justificar a alguien porque no dejas que los diseñadores editen las plantillas a la brava con Dreamweaver…. Sólo un apunte acerca de la diferencia (al menos para mi) entre “programador web” (a veces mal llamado diseñador), llámese a aquel diseñador web medio o avanzado con conocimientos de programación web: JavaScript, AJAX, XHTML, CSS, Smarty (si no lo sabe lo aprende en un plisplas, pero idem con el PHP), etc. del “diseñador” con conocimientos de diseño avanzado, con estilo, mucho photoshop, flash, mucha creatividad ,etc. pero sin programación. Lo que planteo es que el aprendizaje de Smarty es igual de fácil o difícil que el de PHP para ambos perfiles.

Es cierto que todo lo comentado son problemas “menores” dependiendo de tus recursos (dinero y tiempo). Invirtiendo más dinero en hardware y más tiempo en desarrollo y mantenimiento puedes usar Smarty con toda tranquilidad. Siempre que saber que te estás gastando más dinero y que tardarás más en los desarrollos te inspire tranquilidad. En muchas ocasiones el uso de Smarty es ocasionado por dejarse impresionar por la cantidad de cosas que puedes hacer con él: cache, plugins, transformaciones de datos, forms, filtros, etc. Pero al igual que lo que comentaba acerca de la elección de ADOdb como layer de abstracción de base de datos, es importante valorar lo que vas a usar de todo lo que ofrece, y sobretodo tener en cuenta el coste en rendimiento y tiempo de desarrollo que va a suponer.

En resumen, usar un sistema de plantillas es una buena práctica y si te dispones a desarrollar una aplicación con poco tráfico (como un dashboard de uso no público o una intranet) o dispones de dinero suficiente para hacer una buena inversión y necesitas de sus características, con tiempo y ganas, Smarty es uno de los mejores y más potentes. Pero has de tener en cuenta que si no necesitas de todo lo que ofrece y sólo quieres poder cumplir el objetivo de separar la lógica de negocio de la presentación con un sistema propio usando PHP lo puedes conseguir muy eficazmente. A mi personalmente el uso de Smarty no me compensa y después de tres años viviendo juntos hemos roto, a pesar de que todavía arrastro proyectos enormes q lo usan con todo lo nuevo q hago desde hace unos meses uso (o he vuelto a usar) un sistema de NO plantillas propio usando el mismo PHP con el añadido de un sistema de cache de objetos genérico (con él no sólo cacheo páginas HTML, sino también resultados de queries, de cálculos, etc.). Lo de “NO plantillas” viene a cuento de usar el mismo PHP para las plantillas en lugar de un lenguaje específico.

Lo que explicó y la solución que planteo no es nada nuevo… precisamente para mi y para muchos proponer el uso de un template engine basado en PHP es retomar algo “viejo”. En un siguiente post propondré un template engine basado en PHP que simula al Smarty para así poder migrar código más fácilmente, pero para a quien esto le venga de nuevo y para no dejarlo colgado explicó brevemente de que se trata (aunque siempre puedes googlear un poco).

Muy brevemente, la solución: En lugar de inicializar Smarty e ir haciendo assign() de todo lo que le quieres pasar a la plantilla vas almacenando esta información en un array (u objeto), finalmente en lugar de llamar al display() del objeto Smarty haces un include() de la plantilla escrita en PHP. Las plantillas PHP son básicamente archivos HTML que llevan incrustado PHP para sacar por pantalla los datos almacenados anteriormente y con algunos “if” y “foreach” para codificar la lógica de la presentación (puedes usar short_open_tag (<? y <?=) para hacerlas más amigables para los diseñadores :)

Un pingback obligado hacia este post de Ricardo Galli explicando su opinión al respecto y el mal rendimiento de Pligg, un intento de meneame.net usando Smarty.

Tagged with:
Jan 13

Para conseguir que los errores de PHP lancen exceptions en lugar del clásico mensaje por pantalla y/o entrada en el log, dependiendo de las directivas de configuración usadas, es necesario redefinir el handler de errores del intérprete. Esto es muy útil si estamos programando con PHP5 una aplicación orientada a objetos usando excepciones personalizadas para tratar los errores de nuestro código. En esta situación sólo nos falta capturar los errores del PHP y convertirlos en excepciones para conseguir tratar de la misma forma cualquier error que se produzca ya sea de nuestro código o del intérprete.

El código necesario es el siguiente:

[php] class MyException extends Exception {
public $file;
public $line;
public function errorHandler($errno, $errstr, $errfile, $errline) {
$e = new self();
$e->message = $errstr;
$e->code = $errno;
$e->file = $errfile;
$e->line = $errline;
throw $e;
}
}
set_error_handler(array(‘MyException’, ‘errorHandler’), E_ALL); [/php]

Si ya dispones de una clase exception personalizada puedes simplemente añadirle el método errorHandler y lanzar una de tus excepciones ante un error del PHP. Es necesario colocar este código antes de que haya empezado la ejecución de tu aplicación, una buena técnica es crear un script y hacer que la directiva auto_prepend_file apunte a su ubicación… en general yo encuentro cómodo redefinir todos los handlers necesarios en el auto_prepend_file (por ejemplo nuestros handlers de sesiones, errores y excepciones).

Tener en cuenta que en el anterior código PHP fijo el nivel de errores a E_ALL, esto provoca que se lancen exceptions con los E_NOTICE. Si para ti es demasiado que se lance una excepción porque no has inicializado una variable puedes sustituir la línea con la llamada a set_error_handler por:

[php] set_error_handler(array(‘MyException’, ‘errorHandler’), E_ALL ^ E_NOTICE); [/php]

Puedes consultar más información acerca de los niveles de error del PHP en la documentación de la función error_reporting, también puedes pegarle un vistazo al post Controlar el Script PHP: set_time_limit, memory_limit y error_reporting publicado en SyntaxError.es, un blog amigo dentro de la red SmallSquid.com. Es interesante también consultar la documentación de set_error_handler para más información acerca de los handlers de errores.

Podéis comprobar que el handler funciona con el siguiente código de test:

[php] try {
$a + 2;
print ‘aquí no llegamos ya que $a no está inicializada’;
} catch (Exception $e) {
print ‘Excepción capturada! ‘.”\n”;
print ‘Code: ‘.$e->getCode().”\n”;
print ‘Message: ‘.$e->getMessage().”\n”;
print ‘Line: ‘.$e->getLine().”\n”;
print ‘File: ‘.$e->getFile().”\n”;
} [/php]

A pesar de redefinir el handler de errores los fatal errors nunca podrán ser capturados mediante nuestro error handler ya que el intérprete se queda en un estado inestable y necesita de una terminación rápida y limpia. En un siguiente post explicaré como tratar estos casos.

Tagged with:
preload preload preload