Crear la parte pública del blog

La parte pública del blog se va a programar manualmente aprovechando las utilidades de Symfony. En primer lugar, se crea el esqueleto de los dos módulos que forman la aplicación frontend mediante los comandos:

> symfony module frontend articulo
> symfony module frontend comentario

Ahora, si se accede a la dirección http://blog/articulo se ve la página de bienvenida por defecto del módulo creado. Cada uno de los módulos se subdivide en acciones, de forma que su desarrollo sea más estructurado.

El módulo articulo de la aplicación frontend debería disponer, por ejemplo, de las acciones “listar” (que muestra un listado de todos los artículos) y “mostrar” (que muestra los detalles de un artículo concreto).

Las acciones de cada módulo se incluyen en su archivo actions/actions.class.php. Cada acción se define mediante una función cuyo nombre comienza siempre por “execute“, por lo que el siguiente código añade las dos nuevas acciones:

// En apps/frontend/modules/articulo/actions/actions.class.php
public function executeListar()
{
}
  
public function executeMostrar()
{
}

El archivo actions.class.php contiene una acción por defecto llamada index y que muestra la página principal del módulo. En este caso, la página principal debería mostrar el listado de artículos, por lo que la acción index se va a redirigir a la acción listar:

public function executeIndex() {
  $this->forward('articulo', 'listar'); 
}

¿Qué página HTML muestra una acción cuando se ejecuta?

Evidentemente, depende de la acción concreta que se está ejecutando y también del resultado de la acción (resultado correcto, error, etc.). Las páginas se guardan en forma de plantillas en la carpeta templates de cada módulo.

El nombre de cada plantilla se forma mediante: nombre_de_la_accion + Resultado_de_la_accion. Cuando la acción listar obtiene un resultado correcto (“success” en inglés), se carga la plantilla listarSuccess.php. Si se crea ese archivo con el siguiente contenido:

<h1>Bienvenido a mi blog</h1>

Al cargar en el navegador la dirección http://miblog/articulo/listar o http://miblog/articulo, se mostrará esa página básica con el mensaje de bienvenida. A continuación, se obtiene la lista de artículos de la base de datos y se pasan a la plantilla para que pueda mostrarlos:

public function executeListar()
{
  $c = new Criteria();
  $articulos = ArticuloPeer::doSelect($c);

  $this->articulos = $articulos;
}

Pasar variables a una plantilla es tan fácil como establecer sus valores en la variable $this dentro de la acción. De esta forma, la instrucción $this->articulos = $articulos; permite que la plantilla asociada a esta acción disponga de una variable llamada $articulos con todos los artículos de la base de datos.

Las clases de tipo Peer (como ArticuloPeer) son las que se generan automáticamente mediante el comando symfony propel-build-model y permiten acceder a la base de datos utilizando objetos en vez de SQL.

La sintaxis que se utiliza para acceder a los datos es la que define el ORM Propel. Básicamente, el proceso consiste en crear un objeto de tipo Criteria() para establecer las condiciones utilizadas para obtener los registros de la tabla.

En este caso se quieren obtener todos los artículos, por lo que se construye un objeto vacío sin condiciones que se pasa directamente al método doSelect(), que es el que realiza la búsqueda en la base de datos (las dos instrucciones anteriores son equivalentes a realizar una consulta de tipo SELECT * FROM blog_articulo).

Ahora, sólo hace falta modificar la plantilla de esta acción (archivo listarSuccess.php) para que muestre los detalles de los artículos:

<h1>Bienvenido a mi blog</h1>

<?php foreach($articulos as $articulo): ?>
  <h2><?php echo $articulo->getTitulo() ?></h2>
  <p><?php echo $articulo->getContenido() ?></p>
<?php endforeach; ?>

El código de la plantilla anterior utiliza la sintaxis alternativa de foreach, que es una buena práctica para conseguir una mejor separación entre el código PHP y las etiquetas HTML. De esta forma, los diseñadores sin conocimientos de programación pueden manipular más fácilmente el marcado XHTML de las plantillas.

Si se vuelve a cargar la página http://miblog/articulo se observa el listado de todos los artículos de la base de datos. ¿Cómo se puede mejorar el listado de artículos? Mostrando por ejemplo sólo un resumen de su contenido y añadiendo un enlace al artículo completo. Aquí es donde se introduce el concepto de “helper“, funciones PHP sencillas que aportan utilidades para las plantillas:

<?php use_helper('Text') ?>

<h1>Bienvenido a mi blog</h1>

<?php foreach($articulos as $articulo): ?>
  <h2><?php echo $articulo->getTitulo() ?></h2>
  <p><?php echo truncate_text($articulo->getContenido(), 200) ?></p>
  <?php echo link_to('Continuar leyendo...', 'articulo/mostrar?id='.$articulo->getId()) ?>
<?php endforeach; ?>

El helper truncate_text() trunca el texto que se le pasa según el máximo número de caracteres indicado. Se trata de un helper que pertenece al grupo de las funciones relacionadas con el texto.

Para optimizar el rendimiento de la aplicación, Symfony sólo carga los helpers más utilizados (entre los que no se incluyen los del texto), por lo que para emplear helpers de texto, se deben incluir explícitamente estos helpers mediante la instrucción <?php use_helper('Text') ?>. El otro helper utilizado y que siempre se puede utilizar directamente es link_to(), que se emplea para construir enlaces internos.

Aunque las posibilidades para construir enlaces son ilimitadas (más adelante se detallan algunas de ellas) la forma más simple de construir un enlace interno es indicar el nombre del módulo, el nombre de la acción y los parámetros que se quieren pasar en la URL. El valor articulo/mostrar?id=XXXX se convierte en una URL de tipo /articulo/mostrar/id/XXXX en el entorno de producción.

Si se ejecuta la aplicación en el entorno de desarrollo, la URL generada sería /frontend_dev.php/articulo/mostrar/id/XXXX. Utilizando el helper link_to(), Symfony se encarga de construir URL correctas y limpias en función del entorno en el que se ejecuta la aplicación en cada momento.

El enlace articulo/mostrar/id/xxx que se ha incluido en la plantilla anterior, enlaza con la acción mostrar del módulo articulo. Dentro de esta acción, se obtiene el id del artículo a partir de la URL, se busca el artículo y se pasan sus datos a la plantilla:

public function executeMostrar()
{
  $id = $this->getRequestParameter('id');
  $articulo = ArticuloPeer::retrieveByPk($id);
  

  $this->forward404Unless($articulo);
  
  $this->articulo = $articulo;
}

En primer lugar, la acción obtiene el parámetro id de la URL mediante el método getRequestParameter(). Utilizando este parámetro, que es el identificador único del artículo, se busca directamente el artículo mediante el método retrieveByPK(), que obtiene una fila de la tabla de datos a partir del valor de su clave primaria.

Si el artículo obtenido no existe o no es válido, es una buena práctica redirigir al usuario a la página de error 404, lo que se consigue con la función forward404Unless(). Si el artículo es válido, la acción lo pasa a la plantilla mostrarSuccess.php, cuyo código se muestra a continuación:

<h1><?php echo $articulo->getTitulo() ?></h1>

<p><?php echo $articulo->getContenido() ?></p>

<?php echo link_to('Volver', 'articulo/listar') ?>

Aunque al principio parece que son muchos los archivos que se deben crear o modificar, en realidad se trata de desarrollar las aplicaciones de la forma más ordenada y estructurada posible. Separar las partes que forman una aplicación (base de datos, controlador, plantillas, etc.) simplifica su desarrollo, su mantenimiento y la actualización de su código.

Por último, las plantillas permiten definir y utilizar un layout, que no es más que una plantilla global que se aplica a todas las plantillas de la aplicación y que se encuentra en el archivo apps/[nombre aplicacion]/templates/layout.php.

Este layout es ideal para incluir todos los elementos invariantes de la aplicación: cabecera, logotipo, pie de página, menú de navegación, etc. Además, algunas de las partes que se repiten en las plantillas, como por ejemplo los menús de navegación, se pueden definir en elementos reutilizables llamados fragmentos.

La capa del modelo

El código mostrado anteriormente hace referencia a la capa del controlador (las acciones) y a la capa de la vista (las plantillas). El código responsable del acceso a los datos debe incluirse en la parte correspondiente al modelo, es decir, en las clases generadas automáticamente por Symfony para acceder a la base de datos.

De esta forma, el código mostrado en la acción anterior no debería acceder directamente a los datos, sino que debería delegar esta responsabilidad en la parte del modelo. Para ello, se modifica el código de la acción listar:

public function executeListar()
{
  $articulos = ArticuloPeer::getTodos();

  $this->articulos = $articulos;
}

Desde la acción (que forma parte del controlador) se llama a una función del modelo (clase ArticuloPeer). Para que la aplicación siga funcionando correctamente, se debe crear una función en el modelo que se llame getTodos() y que devuelva el listado completo de artículos que se encuentran en la base de datos. El código que hay que añadir a la clase lib/model/ArticuloPeer se muestra a continuación:

class ArticuloPeer extends BaseArticuloPeer
{
  public static function getTodos() {  
    $c = new Criteria();
    $articulos = self::doSelect($c);
    return $articulos;
  }
}

Ahora que todo el código correspondiente al acceso a los datos se encuentra en la parte del modelo, es muy sencillo añadir otras funciones útiles para acceder a los datos:

class ArticuloPeer extends BaseArticuloPeer
{
  public static function getTodos() {  
    $c = new Criteria();
    $articulos = self::doSelect($c);
    return $articulos;
  }

  public static function getRecientes($numero = 10) {  
    $c = new Criteria();
    $c->addAscendingOrderByColumn(ArticuloPeer::CREATED_AT);
    $c->setLimit($numero);
    $articulos = self::doSelect($c);
    return $articulos;
  }
  
  public static function getSegunId($id) {  
    $articulo = self::retrieveByPK($id);
    return $articulo;
  }
}

El método getSegunId() es el que utiliza la acción mostrar para obtener los datos de un artículo a partir de su identificador. El otro método getRecientes() puede ser utilizado por la acción listar para obtener un número limitado de artículos ordenados del más reciente al más antiguo.

Enlaces y sistema de enrutamiento

Una buena práctica tanto de usabilidad como de SEO para las aplicaciones web es la utilización de URL limpias y significativas. Symfony por defecto genera URL de tipo http://nombredominio.com/articulo/mostrar/id/5 en vez de las tradicionales URL tipo http://nombredominio.com/index.php?c=23423&s=a4j3h.

De hecho, el sistema de enrutamiento es otro de los puntos fuertes de Symfony, ya que ofrece unas posibilidades ilimitadas para generar de forma sencilla cualquier tipo de URL.

Las reglas que definen la forma en que se generan las URL se encuentran en el archivo apps/[nombre aplicacion]/config/routing.yml. El archivo contiene por defecto una serie de reglas básicas, como por ejemplo:

default:
  url:   /:module/:action/*

Esta regla es la que se aplica por defecto cuando se accede a una URL como las utilizadas anteriormente (articulo/listar, articulo/mostrar/id/5). La primera palabra de la URL se considera como el módulo que se quiere acceder y la segunda palabra es la acción concreta dentro de ese módulo.

Todas las reglas están compuestas obligatoriamente por un nombre, un patrón que indica mediante expresiones regulares las posibles URL a las que hace referencia esa regla y una serie de valores que se incluyen cuando el usuario realiza su petición.

Si se quiere modificar la URL anterior articulo/mostrar/id/5 por una URL más sencilla tipo articulo/mostrar/5, se debe incluir una nueva regla después de la regla homepage:

mostrar:
  url:   /articulo/mostrar/:id
  param: { module: articulo, action: mostrar }

El nombre de la regla (mostrar) se puede elegir de forma libre. El patrón de las URL utiliza los 2 puntos (:) delante de cada parte que debe considerarse como una variable.

En la URL anterior, la parte “articulo/mostrar” es fija para todas las URL de este tipo y la tercera parte de la URL puede ser “cualquier cosa” y será interpretada como una variable llamada id (en la acción correspondiente se utilizará este nombre de variable).

Para terminar de completar la regla de enrutamiento, sería bueno poder restringir los posibles valores de la variable id. Para ello, se utilizan expresiones regulares y la opción requirements:

mostrar:
  url:           /articulo/mostrar/:id
  param:         { module: articulo, action: mostrar }
  requirements:  { id: \d+ }

La expresión regular \d+ significa “uno o más números consecutivos”, por lo que la variable id podrá ser cualquier número entero y no podrá contener caracteres alfabéticos.

El nombre de las reglas también se puede utilizar para simplificar la forma en la que se incluyen los enlaces en las plantillas. Antes de poder utilizar las nuevas reglas, no olvides utilizar el entorno de desarrollo (frontend_dev.php) o borrar la caché de la configuración mediante el comando symfony cc.

<?php echo link_to('Continuar leyendo...', 'articulo/mostrar?id='.$articulo->getId()) ?>
<?php echo link_to('Continuar leyendo...', '@mostrar?id='.$articulo->getId()) ?>

Para insertar un enlace mediante el nombre de la regla, se utiliza su nombre prefijado con el símbolo "@". En este ejemplo sencillo las ventajas no parecen tan evidentes, pero cuando las URL son muy complejas, es mucho mejor utilizar el nombre de las reglas, en vez del nombre del módulo y de la acción.

Utilizando el nombre de la regla, se puede incluir fácilmente un enlace a la página principal del sitio mediante la siguiente instrucción: <?php echo link_to('Página principal', '@homepage') ?>. Utilizando los valores definidos por la opción param es muy sencillo cambiar la página principal de la aplicación para que vaya directamente al listado de artículos del blog:

homepage:
  url:   /
  param: { module: articulo, action: listar }

Ahora, cuando se accede a http://blog, la aplicación muestra directamente el listado de artículos, sin necesidad de tener que acceder a http://blog/articulo/listar. Si se accede a la aplicación en el entorno de producción, no te olvides borrar la cache de configuración con el comando symfony cc.

Archivos de log y depuración de aplicaciones

Independientemente de lo bueno o experimentado que sea el programador y de lo bueno y estable que sea el framework, durante el desarrollo de una aplicación siempre se cometen errores. Disponer de una buena herramienta para detectar la causa de los errores y las posibles mejoras en el rendimiento de una aplicación es lo que multiplica la productividad del desarrollo.

Cuando se accede a una aplicación en el entorno de desarrollo, Symfony crea un exhaustivo archivo de log en el que se recoge toda la información generada durante el procesamiento de la petición del usuario. Además, al acceder a cualquier página de la aplicación en el entorno de desarrollo, en la parte superior derecha de la ventana del navegador se muestra una barra de información y depuración como la de la siguiente imagen:

symfony1.jpg

La barra de depuración web está dividida en varias partes. En primer lugar, muestra el tiempo total que ha empleado Symfony en generar la página. Si se pincha sobre el tiempo indicado, se ve el detalle del tiempo utilizado por cada parte de Symfony (el controlador, la configuración de la aplicación, la vista, etc.).

La barra también incluye una parte llamada vars & config en la que se muestran todas las opciones de configuración del proyecto y de la aplicación, los parámetros de la petición del usuario, la configuración del servidor web, etc.

La parte más útil de la barra suele ser logs & messages en la que se muestran todos los mensajes de log que ha generado esta petición: parámetros recibidos, configuración que se ha cargado, regla de enrutamiento que se está utilizando, todas las consultas SQL realizadas a la base de datos, etc.

Imagina lo que te costaría abrir todos los archivos de log del servidor web y de la base de datos cada vez que quieres consultar un problema. Con Symfony, toda la información útil siempre está a 1 click de distancia:

symfony2.png

Si eres de los programadores más avanzados, seguramente utilizas aceleradores de PHP (eaccelerator, APC o Xcache) y herramientas de depuración como Xdebug.

Symfony se integra de forma transparente con todas estas herramientas y muchas otras, de manera que por ejemplo, la información generada por Xdebug se puede integrar automáticamente en la información de log generada por Symfony.

Plugins

Symfony es un framework muy completo que automatiza la mayor parte de las tareas habituales y repetitivas de las aplicaciones web. Por este motivo, ni Symfony ni ningún otro framework pueden incluir utilidades para cualquier característica específica de un proyecto.

Afortunadamente, no es necesario programar manualmente cualquier utilidad no incluida en el framework, ya que gracias a los plugins de Symfony, existen decenas de funcionalidades creadas por otros programadores y que se pueden reutilizar.

Todos los plugins disponibles son gratuitos, se instalan en menos de 1 minuto, funcionan igual de bien en cualquier sistema operativo y en general, están bien documentados.

Entre los más de 100 plugins disponibles, se encuentran por ejemplo SimpleBlog, que permite añadir un blog sencillo en cualquier aplicación, SimpleForum, que permite incluir un completo pero sencillo sistema de foros en cualquier aplicación Symfony y SimpleCMS, que pretende convertirse en un completo CMS (gestor de contenidos) para que los usuarios puedan gestionar los contenidos creados por ellos mismos en una aplicación Symfony.

Otras utilidades de Symfony

Los creadores de Symfony viven de programar aplicaciones reales para empresas utilizando su propio framework, por lo que son muy conscientes de lo importante que es conseguir un buen rendimiento.

Por este motivo, Symfony dispone de un espectacular sistema de cache, que abarca desde la caché de la configuración de las aplicaciones y la caché de fragmentos de las plantillas hasta el uso de las cabeceras HTTP 1.1 para controlar la caché en el navegador del usuario.

Los creadores de Symfony son europeos, lo que significa que conocen lo importante que es facilitar la creación de aplicaciones en múltiples idiomas. La internacionalización y localización de aplicaciones están completamente integradas en el framework.

De esta forma, es muy sencillo extender el modelo de datos para que soporte varios idiomas y la traducción de los elementos de la interfaz se realiza mediante gettext y el uso de diccionarios en formato XLIFF, un estándar que utilizan todas las empresas de traducción profesionales.

Symfony también dispone de numerosos helpers para incluir interacciones Ajax en las aplicaciones. Sin necesidad de conocer Ajax y sin escribir ni una sola línea de código JavaScript, cualquier programador puede incluir interacciones complejas realizadas mediante Ajax gracias a la integración de las librerías Prototype y script.aculo.us en Symfony.

Cualquier aplicación web de tamaño medio incluye decenas de formularios en sus módulos y acciones. Symfony dispone de numerosas utilidades para formularios: helpers para incluir elementos de formulario en las plantillas, relleno automático de datos y, sobre todo, un completo mecanismo de validación automática de los datos de un formulario.

Lo mejor de Symfony

  • Sólo funciona con PHP 5, lo que garantiza el máximo rendimiento y permite aprovechar PHP hasta el límite.
  • Ha sido probado en miles de aplicaciones personales, cientos de sitios web públicos y varias aplicaciones web con decenas de millones de usuarios, por lo que es escalable hasta cualquier límite (siempre que se dispongan de los recursos técnicos necesarios).
  • Se trata de software libre, con licencia MIT, con la que puedes desarrollar aplicaciones libres o comerciales.
  • Symfony dispone de una de las mejores documentaciones del mundo del software libre, ya que aúna cantidad, calidad, gratuidad y una actualización continua.
  • Programar bien con Symfony es muy sencillo, ya que simplifica al máximo la aplicación de patrones de diseño y buenas prácticas propias de los mejores programadores.
  • Internacionalización probada y completamente integrada en el framework.

Lo peor de Symfony

  • Antes de ser realmente productivo con Symfony y comprender su funcionamiento, es necesario dedicar un tiempo a su estudio. Todos los programadores que lo han hecho, han podido comprobar que Symfony devuelve multiplicada por 100 cada hora dedicada a su aprendizaje.
  • El ORM que utiliza por defecto no es ideal ya que su sintaxis es muy recargada. Existe un plugin para integrar un ORM alternativo llamado Doctrine y que será incluido en Symfony en cuanto se lance oficialmente de forma estable.

Conclusión

A pesar de su extensión, este artículo apenas cubre el 10% de las posibilidades de Symfony. Adentrarse en cada una de las partes que componen Symfony permite darse cuenta de que utilizando este framework se puede ser mucho más productivo, se puede crear código de mucha más calidad y que sea más fácil de mantener y actualizar. Symfony es un framework completo, estable, maduro y con una documentación excelente.

Referencias

La mejor referencia para aprender Symfony es el libro oficial del framework, que se puede comprar en papel (editorial Apress, ISBN-13: 978-1590597866) y que se puede leer gratuitamente en su versión original en inglés (The Definitive Guide to Symfony) y completamente traducido al español (Symfony, la guía definitiva).

También se puede leer el tutorial My First Project en el que se construye un blog completo y el tutorial de Askeet, una serie de 24 tutoriales de 1 hora cada uno con los que se construye una aplicación de preguntas y respuestas tipo web 2.0.

El grupo de usuarios de Symfony en español también es una buena referencia para resolver dudas sobre Symfony, problemas con el desarrollo de aplicaciones y cualquier otro aspecto relacionado con este framework.

El framework Symfony, una introducción práctica (I parte)