Comunidad de diseño web y desarrollo en internet online

Introducción a Symfony2 (II)

Esta es la segunda parte del tutorial de Introducción a Symfony2, donde crearemos una aplicación de gestión de alimentos. Y llegó el momento de ponerse a cocinar código.



Si no viste la primera parte del tutorial, aquí te dejo el enlace:
Introducción a Symfony2

Generación de un Bundle


La primera idea que debe quedar clara, expresada de manera simplista, es que "todo es un bundle" en Symfony2. Por lo tanto, si queremos desarrollar una aplicación necesitaremos, por lo menos, tener un bundle para alojar el código de la misma. Comencemos por ahí. El siguiente comando de Symfony2 nos ayuda a generar el esqueleto de un bundle de manera interactiva:

Código :

 php app/console generate:bundle

A cada pregunta que nos hace le acompaña una pequeña ayuda. En primer lugar nos pregunta por el espacio de nombre que compartirán las clases del bundle. La recomendación, como se dice en el texto de ayuda del comando, es que comience por el nombre del fabricante del bundle, el nombre del proyecto o del cliente, seguido, opcionalmente, por una o más categorías, y finalizar con el nombre del bundle seguido del sufijo bundle. Es decir el nombre completo del espacio de nombres del bundle debe seguir el siguiente patrón:

Código :

Fabricante/categoria1/categoria2/../categoriaN/nombrebundleBundle

Ilustremos esto con varios ejemplos de nombres de bundles válidos:

Código :

  AulasMentor/AlimentosBundle
   AulasMentor/Tutorial/AlimentosBundle
   AulasMentor/CursoSf2/Tutorial/AlimentosBundle
   Jazzyweb/AulasMentor/AlimentosBundle

Nos quedaremos con el último de los nombres para el bundle que vamos a construir. Con este nombre se quiere expresar algo así como que el bundle AlimentosBundle ha sido creado por Jazzyweb (una empresa ficticia) para el cliente AulasMentor. Como ves, cualquier nombre vale siempre que contenga un nombre de fabricante (vendor name) y un nombre de bundle. En medio podemos poner lo que queramos para organizar nuestro trabajo.

Introduce Jazzyweb/AulasMentor/AlimentosBundle como espacio de nombres del bundle. A continuación nos pregunta por el nombre del bundle. Y nos ofrece una recomendación que es el mismo nombre del espacio de nombres anterior pero sin los separadores " /". El nombre del bundle es importante pues, en ocasiones, hay que referirse al bundle por este nombre.

Presiona enter para aceptar la sugerencia.

El próximo paso es asignarle una ubicación en la estructura de directorios del proyecto. La flexibilidad de Symfony2 permite que lo coloques donde quieras. Pero es muy recomendable que lo coloques en el directorio src, ya que está pensado para alojar nuestro código. Si lo haces así, te ahorrarás tener que incluir una línea de código en el fichero app/autoload.php para registrar el espacio de nombre en el sistema de autocarga de clases. Esto último es así porque en dicho fichero ya se ha contemplado que todas las clases que se aloje en src sean autocargadas asignándole como espacio de nombre raíz el mismo nombre que la estructura de directorios computada desde src.

Presiona enter para aceptar la sugerencia. Cuando termines de generar el bundle verás cómo se ha creado en src el directorio Jazzyweb/AulasMentor/AlimentosBundle, es decir un directorio que tiene la misma estructura que el espacio de nombres que hemos asignado al bundle. Esto es lo que se quiere decir de manera genérica en el párrafo anterior.

Los bundles llevarán asociados algo de configuración. Como mínimo será necesario configurar las rutas que mapean las URL's en acciones del bundle. Symfony2 admite 4 formas de representar las configuraciones: con ficheros XML, YML o PHP, y mediante anotaciones, que es una manera de expresar parámetros de configuración en el propio código funcional aprovechando para ello los comentarios de PHP.

Más adelante tendremos ocasión de utilizar las anotaciones y las entenderás mejor. Llegados a este punto hemos de decir que la elección es una cuestión de gusto; discutir con alguien acerca de cuál es la mejor opción sería una pérdida de tiempo. Para el caso de la configuración de los bundles (prácticamente para definir rutas como veremos después) hemos elegido los ficheros YAML como formato para la configuración.

Selecciona (escribe) yml como formato de configuración.

Por último contesta yes a la pregunta de si quieres generar la estructura completa. Esta opción generará algunos directorios y archivos extras que siguen las recomendaciones de Symfony2 para alojar código. Es posible que no los utilices, pero no hacen "daño" y sugieren cómo debe organizarse el código. No obstante el programador tiene bastante libertad a la hora de organizar los archivos del bundle como quiera.

Confirma la generación del código. Una vez generado, el asistente te realizará dos preguntas más. Primera pregunta: ¿quieres actualizar automáticamente el Kernel? y segunda pregunta ¿quieres actualizar directamente el routing? Contesta a las dos afirmativamente. Vamos a ver con más detalle las consecuencias de estas actualizaciones automáticas.

Por una parte el bundle, como ya hemos explicado, es un bloque desacoplado y reutilizable de código que agrupa a una serie de funcionalidades. Si queremos utilizarlo en nuestro proyecto debemos "notificarlo" al framework. Es decir, hemos de "engancharlo". Esto se hace registrándolo en el archivo app/AppKernel.php. La primera actualización automática ha realizado dicho registro. Abre ese archivo y fíjate cómo al final del método registerBundles() aparece la siguiente línea:

Código :

   ...
   new Jazzyweb\AulasMentor\AlimentosBundle\JazzywebAulasMentorAlimentosBundle(),
   ...

Dicha línea ha sido insertada automáticamente como consecuencia de haber respondido afirmativamente a la primera pregunta. El cometido de la línea es registrar el bundle recién creado en el framework para poder hacer uso del mismo.

La segunda actualización automática "enlaza" la tabla enrutamiento general de la aplicación con la tabla de enrutamiento particular del bundle. La tabla de enrutamiento es la responsable de indicar al framework cómo deben mapearse las URL's en acciones PHP. Para ver cómo se ha realizado este enlace mira el fichero app/config/routing.yml:

Código :

 JazzywebAulasMentorAlimentosBundle:
    resource: "@JazzywebAulasMentorAlimentosBundle/Resources/config/routing.yml"
    prefix:   /

Estas líneas han sido introducidas automáticamente como consecuencia de contestar afirmativamente a la segunda pregunta. Observa que el apartado resource es la dirección en el sistema de ficheros de la tabla de enrutamiento propia del bundle que acabamos de crear. Symfony2 sabe convertir @JazzywebAulasMentorAlimentosBundle en la ubicación del bundle pues está debidamente registrado.

Es importante que conozcas cómo se acopla un bundle a la aplicación, pues si falla la actualización automática del KernelApp.php y/o del routing.yml, debes realizarlas manualmente.

Ahora puedes echarle un vistazo al fichero routing.yml del bundle src/Jazzyweb/AulasMentor/AlimentosBundle/Resources/config/routing.yml). Verás que existe una ruta mapeada contra una acción. Después explicaremos los detalles de la ruta. Esta última ruta sirve para probar el bundle. Así que accede desde tu navegador web a la siguiente URL (que es la que se corresponde con esta ruta de prueba).

Código :

http://localhost/Symfony/web/app_dev.php/hello/alberto


Si todo va bien, obtendrás como respuesta un saludo. Puedes cambiar el nombre del final de la ruta.

Resumiendo:

Para desarrollar nuestra aplicación hemos de contar al menos con un bundle para escribir el código. Según la complejidad de la aplicación será más o menos adecuado organizar el código en varios bundles. El criterio a seguir es el de agrupar en cada bundle funcionalidades similares o del mismo tipo.

Los bundles son bloques desacoplados y tienen asociado un espacio de nombre. Para acoplar un bundle al framework hay que:
  1. Registrar el espacio de nombre en el sistema de autocarga (fichero “app/autoload.php”. Este paso no es necesario si ubicamos al bundle en el directorio “src”.
  2. Registrar al bundle en el fichero app/AppKernel.php. Esta operación se puede hacer automáticamente a través del generador interactivo de bundles, pero si fallase por alguna razón (por ejemplo que los permisos de dicho archivo no estén bien definidos). Habría que hacerlo a mano.
  3. Importar las tablas de enrutamiento del bundle en la tabla de enrutamiento de la aplicación.


Anatomía de un bundle


Si has seguido las indicaciones que hemos dado en este tutorial, debes tener en tu directorio src dos directorios: Jazzyweb y Acme . El primero se corresponde con el bundle que acabamos de crear, y el segundo es un ejemplo que viene de serie con la distribución standard de Symfony2 y que contiene el código de la demo con la que has jugado hace un rato. Vamos a utilizar este último para realizar la disección de un bundle, ya que está más rellenito de código que nuestro recién horneado y esquelético bundle.

Código :

Acme/
    └── DemoBundle
   ├── AcmeDemoBundle.php
   ├── Controller
   │   ├── DemoController.php
   │   ├── SecuredController.php
   │   └── WelcomeController.php
   ├── ControllerListener.php
   ├── DependencyInjection
   │   └── AcmeDemoExtension.php
   ├── Form
   │   └── ContactType.php
   ├── Resources
   │   ├── config
   │   │   └── services.xml
   │   ├── public
   │   │   ├── css
   │   │   │   └── demo.css
   │   │   └── images
   │   │       ├── blue-arrow.png
   │   │       ├── field-background.gif
   │   │       ├── logo.gif
   │   │       ├── search.png
   │   │       ├── welcome-configure.gif
   │   │       ├── welcome-demo.gif
   │   │       └── welcome-quick-tour.gif
   │   └── views
   │       ├── Demo
   │       │   ├── contact.html.twig
   │       │   ├── hello.html.twig
   │       │   └── index.html.twig
   │       ├── layout.html.twig
   │       ├── Secured
   │       │   ├── helloadmin.html.twig
   │       │   ├── hello.html.twig
   │       │   ├── layout.html.twig
   │       │   └── login.html.twig
   │       └── Welcome
   │           └── index.html.twig
   ├── Tests
   │   └── Controller
   │       └── DemoControllerTest.php
   └── Twig
       └── Extension
      └── DemoExtension.php

  • AcmeDemoBundle.php es una clase que extiende a Symfony\Component\HttpKernel\Bundle\Bundle y que define al bundle. Se utiliza en el proceso de registro del mismo (recuerda, en el fichero app/AppKernel.php). Todos los bundles deben incorporar esta clase (bueno, el nombre cambiará según el nombre del bundle).

  • Controller es el directorio donde se deben colocar los controladores con las distintas acciones del bundle. Las acciones son las funciones (o métodos). Lo lógico y recomendado, es crear una clase Controller por cada grupo de funcionalidades. Pero no es una exigencia, si quieres puedes colocar todas tus acciones en el mismo controlador. Cuando se genera un bundle se crea el controlador DefaultController.

  • Dependency Injection. Una de las características más sobresaliente de Symfony2 es el uso intensivo que hace de la Inyección de Dependencias, un potente patrón de diseño mediante el que se facilita la creación y configuración de objetos que prestan servicios en una aplicación gracias a la gestión automática de sus dependencias. Contribuye a crear un código más desacoplado y coherente.

    En este tutorial se explica con bastante detalle cómo se trata la inyección de dependencias en Symfony2. Aunque no es un patrón complicado, es difícil de explicar con precisión y claridad. Symfony2 nos ofrece dos maneras de "cargar" la configuración de las dependencias y los servicios creados. Una más sencilla y directa, y otra más elaborada y apropiada para el caso en que nuestro bundle vaya a ser distribuido con la intención de que se utilice en otros proyectos Symfony2. En este directorio se ubican las clases relacionadas con este segundo método de gestionar las dependencias.

  • Resources, es decir, recursos. Entendemos por recursos: los ficheros de configuración del bundle (directorio config), los assets que requiere el bundle para enviar en sus respuestas (directorio public) y las plantillas con las que se renderizan (pintan) el resultado de las acciones de los controladores (directorio view). Fíjate como en este bundle, las plantillas están organizadas en tres directorios (Demo, Secured y Welcome) cuyos nombres coinciden con los de los controladores.

  • Test, es el directorio donde viven las pruebas unitarias y funcionales del bundle.


Estos son los directorios más típicos de cualquier bundle, de hecho son los que se generan automáticamente con el comando app/console generate:bundle. Sin embargo un bundle puede tener muchos más directorios y ficheros, organizados como su creador crea conveniente. En el caso del bundle AcmeDemoBundle, puedes ver los siguientes "extras":
  • Form es el directorio donde se colocarán las clases que definen los formularios de la aplicación.
  • ControllerListener.php describe un event listener que es un mecanismo muy adecuado de extender y alterar el flujo del framework sin tener que tocar el código original del componente del framework que utiliza dicho sistema. Se trata de una característica avanzada de Symfony2 raramente utilizada cuando uno se está iniciando.
  • Twig es un directorio propio de este bundle, en el que se ha implementado una extensión del sistema de plantillas.


Ahora ya nos encontramos con un mínimo bagaje para emprender el desarrollo del bundle JazzywebAulasMentorAlimentosBundle y, por tanto de la aplicación.


Flujo básico de creación de páginas en Symfony2


La creación de páginas web con Symfony2 involucra tres pasos:
  1. Creación de la ruta que mapea la URL de la página en una acción de algún controlador. Dicha ruta se registra en el archivo config/Resources/routing.yml del bundle, que a su vez debe estar correctamente importado en el archivo de rutas general de la aplicación app/config/routing.
  2. Creación de dicha acción en el controlador correspondiente. La acción, haciendo uso del modelo, realizará las operaciones necesarias y obtendrá los datos crudos (raw), es decir sin ningún tipo de formato, que facilitará a una plantilla para ser pintados (renderizados). El código de los controladores debe ubicarse en el directorio Controllers del bundle.
  3. Creación de dicha plantilla. Esto se hace en el directorio Resources/view. Con el fin de organizar bien las plantillas, es recomendable crear un directorio con el nombre de cada controlador. También es muy recomendable utilizar Twig como sistema de plantillas, aunque también se puede utilizar PHP.

Estos pasos son, por supuesto, una guía general y mínima que debemos seguir en la creación de las páginas de nuestra aplicación. No obstante, en muchos casos tendremos que realizar otras operaciones que se salen de este flujo y que tienen que ver más con la construcción del modelo de la aplicación.

Definición de las rutas del bundle


Ya hemos visto que en Symfony2 todas las peticiones a la aplicación se realizan a través de un script PHP que se llama controlador frontal (app.php). Este script "sabe" lo que debe devolver como respuesta al usuario "mirando" la ruta que lo acompaña en cada petición.

Cada ruta en Symfony2 consiste en un conjunto de parámetros separados por el caracter "/". Ejemplos de URL's con rutas de Symfony2 serían:

Código :

 http://tu.servidor/app.php
   http://tu.servidor/app.php/listar
   http://tu.servidor/app.php/ver/4


Las rutas correspondientes serían:

Código :

 
   /
   /listar
   /ver/4


Es decir, los parámetros que siguen al controlador frontal. Esta forma de pasar parámetros a través de la URL mejora en varios aspectos a la clásica query string del tipo:

Código :

?param1=val1&param2=val2&...&paramN=valN


En primer lugar el usuario que utiliza el navegador “siente” que la URL que aparece en la barra de direcciones forma parte de la aplicación que está utilizando. Por tanto, cualquier URL llena de caracteres extraños y demasiado larga redunda en una degradación estética. En segundo lugar y más allá de cuestiones estéticas, cuando utilizamos query strings clásicas estamos dando información innecesaria al usuario, ya que el nombre de los parámetros (paramX) es algo que tiene sentido únicamente para la aplicación en el servidor. Esta información extra, además de dar lugar a URL’s horribles, supone un problema de seguridad, ya que el usuario podría utilizarlas para sacar conclusiones acerca de la aplicación y utilizarla para comprometerla.

El aspecto de las URL's puede mejorar aún más si utilizamos el módulo Rewrite del servidor web, ya que también podemos eliminar el nombre del controlador frontal app.php). Así además de mejorar el estilo de la URL, ocultamos al usuario información acerca del lenguaje de programación que estamos utilizando en el servidor.

Nos quedarían URL's de este tipo:

Código :

http://tu.servidor/
   http://tu.servidor/listar
   http://tu.servidor/ver/4


¡Mucho más legibles y elegantes!

En el directorio “web” existe un fichero “.htaccess” con el siguiente contenido:

Código :

<IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule ^(.*)$ app.php [QSA,L]
      </IfModule>

La función de dicho fichero es, precisamente, reescribir las rutas anteponiendo “app.php”, de manera que no sea necesario especificar el controlador frontal en la URL. Para que esto funcione es necesario que el servidor web tenga instalado el módulo “Rewrite”, y permita el cambio de directivas a través de ficheros “.htaccess”.


La siguiente tabla muestra las rutas que definiremos en nuestra aplicación y la acción que deben disparar.

Código :

/          (mostrar pantalla inicio)
/listar    (listar alimentos)
/insertar  (insertar un alimento)
/buscar    (buscar alimentos)
/ver/x     (ver el alimento x)


En Symfony2 las rutas se definen en el archivo app/config/routing.yml. Para que los bundles no pierdan la autonomía que debe caracterizarlos, las rutas que se mapean en un controlador de un determinado bundle deberían definirse dentro del propio bundle. Concretamente en el archivo Resources/config/routing.yml del bundle. Y para hacerlas disponibles a la aplicación, se importa este último fichero en app/config/routing.yml.

Aunque el sitio recomendado para ubicar el fichero routing.yml de un bundle es Resources/config, Symfony2 no lo exige, ya que en el archivo app/config/routing.yml, que es el que realmente define las rutas, puedes indicar la ruta concreta de los archivos que se quieren importar.


Abre el archivo src/Jazzyweb/AulasMentor/AlimentosBunle/Resources/config/routing.yml y borra las siguientes líneas:

Código :

 JazzywebAulasMentorAlimentosBundle_homepage:
    pattern:  /hello/{name}
    defaults: { _controller: JazzywebAulasMentorAlimentosBundle:Default:index }


Las líneas que acabas de borrar definían la ruta de la acción de ejemplo que se crea automáticamente al generar el bundle. Fíjate en la estructura de la definición de una ruta; consisten en un identificador de la ruta (JazzywebAulasMentorAlimentosBundle_homepage), que puede ser cualquiera siempre que sea único en todo el framework, el patrón de la ruta (pattern: /hello/{name}), que describe la estructura de la ruta, y la declaración del controlador sobre el que se mapea la ruta
(defaults: { _controller: JazzywebAulasMentorAlimentosBundle:Default:index }).

Creamos nuestra primera ruta añadiendo al archivo anterior lo siguiente:

Código :

JAMAB_homepage:
    pattern:  /
    defaults: { _controller: JazzywebAulasMentorAlimentosBundle:Default:index }


Como el nombre de la ruta debe ser único en toda la aplicación, es una buena práctica nombrarlas anteponiendo un prefijo con el nombre del bundle, o con algo que lo identifique. Como el nombre de nuestro bundle es muy largo, hemos optado por usar como prefijo las siglas JAMAB.

Una vez definida la ruta debemos implementar la acción del controlador especificada en la misma, es decir JazzywebAulasMentorAlimentosBundle:Default:index.

Fíjate en el patrón que se utiliza para especificar la acción del controlador: JazzywebAulasMentorAlimentosBundle:Default:index. A esto se le llama en Symfony2 un nombre lógico. Está compuesto por el nombre del bundle, el nombre del controlador, y el nombre de la acción separados por el caracter ":". En este caso, el nombre lógico hace referencia al método indexAction() de una clase PHP llamada Jazzyweb\AulasMentor\AlimentosBundle\Controller\DefaultController. Es decir, hay que añadir el sufijo Controller al nombre del controlador, y el sufijo Action al nombre de la acción.


Creación de la acción en el controlador


Editamos el fichero src/Jazzyweb/AulasMentor/AlimentosBundle/Controller/DefaultController.php, y reescribimos el método indexAction():

Código :

 <?php
    
    namespace Jazzyweb\AulasMentor\AlimentosBundle\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    
    class DefaultController extends Controller
    {
        
        public function indexAction()
        {
            $params = array(
                'mensaje' => 'Bienvenido al curso de Symfony2',
                'fecha' => date('d-m-yyy'),
            );
            
            return $this->render('JazzywebAulasMentorAlimentosBundle:Default:index.html.twig',
            $params);
        }
    }


Analicemos el código anterior. La clase DefaultController "vive" en el espacio de nombres Jazzyweb\AulasMentor\AlimentosBundle\Controller, por lo que su nombre completo es Jazzyweb\AulasMentor\AlimentosBundle\Controller\DefaultController. La clase extiende de Symfony\Bundle\FrameworkBundle\Controller\Controller, la cual forma parte de Symfony2 y, aunque no es necesario que nuestros controladores deriven de dicha clase, si lo hacemos nos facilitará mucho la vida, ya que esta clase base cuenta con potentes herramientas para trabajar con Symfony2.

Posiblemente la más útil sea el Contenedor de Dependencias también conocido como Contenedor de Servicios, con el que podemos obtener fácilmente instancias bien configuradas de cualquier servicio del framework, tanto de los incluidos en la distribución estándar, como de los que nosotros creemos o de los que se añadan en las extensiones de terceros (vendors) que podamos instalar. Quédate tranquilo con esto de los servicios pues será un tema que abordaremos más adelante. Por lo pronto es suficiente con que sepas que los servicios son objetos ofrecidos por el framework para realizar determinadas tareas (como por ejemplo enviar emails o manipular una base de datos).

Sobre los espacios de nombre de PHP 5.3
Si en la línea 7 se utiliza únicamente el nombre Controller en lugar del nombre completo Symfony\Bundle\FrameworkBundle\Controller\Controller, es porque previamente, en la línea 5, se ha indicado en el archivo que se va a utilizar la clase Controller de dicho espacio de nombre.


El método indexAction() es una acción, es decir, un método que está mapeado en una URL a través de una ruta. Dichas rutas se definen en un fichero, que utilizarás intensivamente cuando desarrollas aplicaciones con Symfony2, denominado routing.yml. La acción indexAction() define un array asociativo con los datos "crudos" (raw) mensaje y fecha, y se los pasa a una plantilla para que los pinte. Esto último se hace en la línea 17 utilizando el método render de la clase padre Symfony\Bundle\FrameworkBundle\Controller\Controller. Este método recibe dos argumentos, el primero es el nombre lógico de la plantilla que se desea utilizar, y el segundo es un array asociativo con los datos.

Las acciones terminan con la devolución de un objeto Response. Precisamente, el método render convierte una plantilla en un objeto de este tipo.

El método render es uno de los servicios disponibles en el framework y accesible desde cualquiera que derive de Symfony\Bundle\FrameworkBundle\Controller\Controller. Es un servicio que usaremos hasta la saciedad. El nombre lógico de una plantilla, es similar al nombre lógico de un controlador; está compuesto por el nombre del bundle, el nombre del directorio que aloja a la plantilla en el directorio Resources/view (que suele coincidir con el nombre del controlador, en este caso Default), y el nombre del archivo que implementa la plantilla (en este caso index.html.twig). Es decir que el nombre lógico:

:

JazzywebAulasMentorAlimentosBundle:Default:index.html.twig
hace referencia al archivo

:

src/Jazzyweb/AulasMentor/AlimentosBundle/Resources/view/Default/index.html.twig


Creación de la plantilla


Siguiendo los pasos para la creación de una página en Symfony2, lo siguiente que tenemos que hacer es crear la plantilla. Edita el fichero src/Jazzyweb/AulasMentor/AlimentosBundle/Resources/view/Default/index.html.twig con el siguiente contenido:

Código :

<h1>Inicio</h1>
   <h3> Fecha: {{fecha}}  </h3>
   {{mensaje}}


Aunque Symfony2 permite el uso de PHP como sistema de plantillas, en este tutorial utilizaremos Twig, que es lo recomendado oficialmente. El código anterior es una plantilla Twig.

En Twig, el contenido dinámico, es decir, los datos "crudos" que le son pasados desde el controlador (segundo argumento del método render en la acción indexAction()), se referencian con dobles llaves ({{ dato }}). En el ejemplo anterior {{ fecha }} hace referencia al elemento fecha del array construido en el controlador, y {{ mensaje }}, como ya has deducido, al elemento mensaje de dicho array.

Pues con esto hemos terminado. Vamos a probar lo que acabamos de hacer. Introduce en la barra de direcciones de tu navegador la URL correspondiente a la ruta que acabamos de crear. Utiliza el controlador de desarrollo:

Código :

http://localhost/Symfony/web/app_dev.php/


¡Vaya! parece que nada de lo que hemos hecho ha funcionado. Vuelve a aparecer la aplicación demo de Symfony2.

Ahora prueba con el controlador de producción:

Código :

http://localhost/Symfony/web/app.php/


¡Ahora sí! Vemos la pantalla de inicio de nuestro bundle. Pero entonces, ¿qué está pasando? Las rutas tienen distinto sentido según el controlador frontal que usemos. ¿Por qué? La respuesta a este comportamiento se encuentra en las distintas configuraciones que se cargan en función del entorno de ejecución. Cuando utilizamos el controlador frontal de desarrollo app_dev.php, se carga el fichero de routing app/config/routing_dev.php. Si le echas un vistazo al fichero verás que comienza con la siguiente ruta:

Código :

 _welcome:
   pattern:  /
   defaults: { _controller: AcmeDemoBundle:Welcome:index }


La cual colisiona con la que nosotros hemos creado, ya que el patrón de la ruta es el mismo: "/". El sistema de enrutamiento de Symfony2 va leyendo todas las rutas y cuando encuentra una que coincide con la URL que se ha pedido, ejecuta la acción asociada. No sigue leyendo más rutas. Por eso, si en un mismo proyecto hay dos rutas, o más precisamente, dos patrones de rutas que coincidan, se ejecutará la primera que se encuentre. Atención porque no se producirá ningún error. Esto hay que tenerlo muy en cuenta cuando se desarrolla con Symfony2 para evitarnos algún que otro dolor de cabeza.

En el caso del controlador frontal de producción, el framework lee el fichero routing.yml, ya que no existe routing_prod.yml. Mira el fichero y podrás comprobar que no hay ninguna ruta que colisione con la que nosotros hemos definido. Por tanto todo está bien y se ejecuta la acción correcta.

Una vez que sabemos las causas del problema, si queremos que el controlador de desarrollo cargue la ruta de nuestro bundle, cualquier solución que propongamos pasa por evitar la colisión entre rutas. Y para ello podemos hacer varias cosas:
  1. Deshabilitar el plugin AcmeDemoBundle y sus rutas.
  2. Cambiar el patrón de las rutas del plugin AcmeDemoBundle, anteponiéndole a todas ellas un prefijo (acme, por ejemplo).
  3. Cambiar el patrón de las rutas del Jazzyweb/AulasMentorAlimentosBundle, anteponiéndole a todas ellas un prefijo (alimentos, por ejemplo).


Con el fin de ilustrar una característica del sistema de routing, hemos optado por la tercera solución. Podemos añadir un prefijo a todas las rutas del bundle sin más que cambiar el parámetro prefix en la ruta importada en el archivo app/config/routing.yml:

Código :

 JazzywebAulasMentorAlimentosBundle:
     resource: "@JazzywebAulasMentorAlimentosBundle/Resources/config/routing.yml"
     prefix:   /alimentos


Ahora, para ver la página de inicio de nuestro bundle, apuntamos nuestro navegador a:

Código :

 http:://localhost/app_dev.php/alimentos/


Y ya está! A partir de ahora todas las rutas de nuestro bundle llevarán el prefijo alimentos delante.

Como hemos cambiado un fichero de configuración, para que el cambio se haga efectivo en el entorno de producción hay que borrar la caché con el siguiente comando:

Código :

  # app/console  cache:clear --env=prod


Decoración de la plantilla con un layout


Te habrás dado cuenta que hemos pintado un bloque HTML incompleto. Si no te has percatado de ello mira el código fuente HTML que llega al navegador. Nos falta someter a la plantilla al proceso de decoración, mediante el cual se le añade funcionalidad. En el caso de la aplicación de gestión de alimentos hay que añadir la cabecera con el menú, el pie de página y los estilos.

Sobre el proceso de decoración:
En una aplicación web, muchas de las páginas tienen elementos comunes. Por ejemplo, un caso típico es la cabecera (donde se coloca el mensaje de bienvenida), el menú y el pie de página. Este hecho, y la aplicación del conocido principio de buenas prácticas de programación DRY (Don't Repeat Yourself, No Te Repitas), lleva a que cualquier sistema de plantillas que se utilice para implementar la vista utilice un conocido patrón de diseño: El Decorator, o Decorador. Aplicado a la generación de vistas la solución que ofrece dicho patrón es la de añadir funcionalidad adicional a las plantillas. Por ejemplo, añadir el menú y el pie de página a las plantillas que lo requieran, de manera que dichos elementos puedan reutilizarse en distintas plantillas. Literalmente se trata de decorar las plantillas con elementos adicionales reutilizables.


El sistema de plantillas Twig, está provisto de un mecanismo de herencia gracias al cual la decoración de plantillas resulta de una flexibilidad y versatilidad total. Podemos hacer cualquier cosa que nos imaginemos, como por ejemplo fragmentar la vista en distintas plantillas organizadas por criterios funcionales, y combinarlas para producir la vista completa. Podemos colocar en una, un menú, en otra, un pie de página, en otra, la estructura básica del documento HTML, otra puede pintar un listado de twitts, etcétera.

La herencia es un mecanismo típico de la programación orientada a objetos en la cual un componente software hereda todas las funcionalidades de otro y puede extenderlas y/o cambiarlas. Es exactamente esto lo que ocurre cuando una plantilla Twig hereda de otra.

En Twig la herencia se implementa mediante el concepto de bloque. En las plantillas podemos delimitar bloques que comienzan con un {% block nombre_bloque %} y finalizan con {% endblock %}. Las plantillas heredan todas las funcionalidades de las plantillas que extienden y pueden cambiar el código de los bloques heredados. Como siempre un ejemplo vale más que mil palabras.

Fíjate en el fichero app/Resources/view/base.html.twig que viene de serie en la distribución standard de Symfony2:

app/Resources/view/base.html.twig

Código :

 <!DOCTYPE html>
   <html>
    <head>
       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
       <title>{% block title %}Welcome!{% endblock %}</title>
           {% block stylesheets %}{% endblock %}
       <link rel="shortcut icon" href="{{ asset('favicon.ico') }}" />
    </head>
    <body>
       {% block body %}{% endblock %}
       {% block javascripts %}{% endblock %}
    </body>
   </html>


Representa la estructura básica de un documento HTML. Y presenta varios bloques: title, stylesheets, body y javascripts. Esta plantilla es ofrecida por Symfony2 para que sirva de ejemplo. Pero puede utilizarse como plantilla básica de casi cualquier aplicación web.

Vamos a modificar nuestra plantilla index.html.twig para que la herede (o para que la extienda, son dos maneras de decir lo mismo):

src/Jazzyweb/AulasMentor/AlimentosBundle/Resources/view/Default/index.twig.html

Código :

{% extends '::base.html.twig' %}

   {% block body %}
   
   <h1>Inicio</h1>
   <h3> Fecha: {{fecha}}  </h3>
   {{mensaje}}
   
   {% endblock %}


En la línea 1 se indica la herencia de la plantilla base. Esto significa que la plantilla JazzywebAulasMentorAlimentosBundle:Default:index.twig.html asume todo el contenido de la plantilla ::base.html.twig. Pero además se modifica el contenido del bloque body con las líneas 5-7.

Si además queremos modificar el bloque title, no tenemos más que añadirlo en nuestra plantilla index.html.twig:

src/Jazzyweb/AulasMentor/AlimentosBundle/Resources/view/Default/index.twig.html

Código :

  {% extends '::base.html.twig' %}

   {%  block title %}
     Bienvenido a la aplicación alimentos
   {% endblock %}

   {% block body %}
   
   <h1>Inicio</h1>
   <h3> Fecha: {{fecha}}  </h3>
   {{mensaje}}
   
   {% endblock %}


Ahora, en la sección <title> del documento se pintará: Bienvenido a la aplicación alimentos en lugar de Welcome.

Puedes probar a recargar la página a través de la URL:

Código :

  http://localhost/Symfony/web/app_dev.php/alimentos/


Aunque el aspecto de la página es el mismo que antes, si ves el código fuente HTML en el navegador, comprobarás que el documento está completo, es decir, con todas sus etiquetas HTML. También puedes comprobar que, al utilizar el controlador frontal de desarrollo, aparece en la parte de abajo de la página la barra de depuración de Symfony2.

Recuerda el concepto de nombre lógico de una plantilla. Y fíjate en el nombre lógico de la plantilla ::base.html.twig. Como no pertenece a ningún bundle (es común a la aplicación), y está ubicada directamente en el directorio view, no lleva nada ni antes del primer ":" ni del segundo.


La herencia de plantillas puede llevarse a cabo a varios niveles, esto es, una plantilla puede heredar de otra plantilla que a su vez hereda de otra plantilla, etcétera. No obstante no se recomienda llevar a cabo muchos niveles de herencia, ya que puede llegar a ser bastante confuso e incontrolable. La estrategia que recomiendan los creadores de Symfony2 es usar tres niveles de herencia:
  • en el primer nivel se colocan la estructura básica del documento HTML, se corresponde con lo que hace la plantilla ::base.html.twig,
  • en el segundo se colocan los elementos específicos de cada sección del sitio, por ejemplo el menú de la sección,
  • y en el tercero se reserva para los elementos propios de la acción, se corresponde con nuestra plantilla JazzywebAulasMentorAlimentosBundle:Default:index.twig.html


Tan sólo nos falta incluir los menús que serán comunes a todas las páginas de la aplicación. Seguiremos la estrategia de tres niveles de herencia que acabamos de exponer. Creamos la plantilla general JazzywebAulasMentorAlimentosBundle::layout.html.twig. Según la lógica de los nombres lógicos, esta se debe ubicar en:

:

src/Jazzyweb/AulasMentor/AlimentosBundle/Resources/view/layout.twig.html


Código :

 {% extends '::base.html.twig' %}

   {% block body %}
   <div id="cabecera">
     <h1>Información de alimentos</h1>
   </div>

   <div id="menu">
   <hr/>
     <a href="{{ path('JAMAB_homepage')}}">inicio</a> |
     <a href="#">ver alimentos</a> |
     <a href="#">insertar alimento</a> |
     <a href="#">buscar por nombre</a> |
     <a href="">buscar por energia</a> |
     <a href="">búsqueda combinada</a>
   <hr/>
   </div>

   <div id="contenido">
   {% block contenido %}

   {% endblock %}
   </div>

   <div id="pie">
   <hr/>
   <div align="center">- pie de página -</div>
   </div>

   {% endblock %}


En la línea 10 hemos usado la función “path” de twig para construir las URL's del menú. Esta función recibe como argumento el nombre de la ruta cuya URL se desea calcular. Únicamente la hemos usado en el primer enlace del menú, pues, por ahora, es la única ruta que hemos definido.


Ahora es ésta plantilla la que extiende a la plantilla base, por tanto, habrá que cambiar la plantilla JazzywebAulasMentorAlimentosBundle:Default:index.twig.html para que extienda de JazzywebAulasMentorAlimentosBundle::layout.twig.html, y para que redefina el bloque contenido de esta última. Quedaría así:

Código :

{% extends 'JazzywebAulasMentorAlimentosBundle::layout.html.twig' %}

   {% block contenido %}
   
   <h1>Inicio</h1>
   <h3> Fecha: {{fecha}}  </h3>
   {{mensaje}}

   {% endblock %}


Vuelve a probar la página. Ya sólo nos falta incorporarle estilos CSS's.

Instalación de los assets de un bundle


Ya hemos dicho que un bundle es un directorio que aloja todo aquello relativo a una funcionalidad determinada. Puede incluir clases PHP, plantillas, configuraciones, CSS’s y Javascripts.

Cuando los bundles incluyen assets, es decir archivos que no son procesados por PHP si no que son servidos directamente por el servidor web (CSS's, javascripts e imágenes son los assets más habituales), estos deben ser copiados dentro del directorio web del proyecto o enlazados desde dicho directorio, ya que es ahí únicamente donde el servidor web puede acceder en busca de archivos (suponiendo que lo hemos configurado correctamente para un entorno de producción).

Por otro lado, en un bundle los assets deben ser ubicados en el directorio Resources/public. Si lo examinas verás que tiene la siguiente estructura:

Código :

 Resources
    └─ public
       ├── css
       ├── images
       └── js


Se ha reservado un directorio para cada tipo de asset. Copia el siguiente código CSS's en el archivo Resources/public/css del bundle.

Código :

 body {
      padding-left: 11em;
      font-family: Georgia, "Times New Roman",
            Times, serif;
      color: purple;
      background-color: #d8da3d }
    ul.navbar {
      list-style-type: none;
      padding: 0;
      margin: 0;
      position: absolute;
      top: 2em;
      left: 1em;
      width: 9em }
    h1 {
      font-family: Helvetica, Geneva, Arial,
            SunSans-Regular, sans-serif }
    ul.navbar li {
      background: white;
      margin: 0.5em 0;
      padding: 0.3em;
      border-right: 1em solid black }
    ul.navbar a {
      text-decoration: none }
    a:link {
      color: blue }
    a:visited {
      color: purple }
    address {
      margin-top: 1em;
      padding-top: 1em;
      border-top: thin dotted }
    #contenido {
      display: block;
      margin: auto;
      width: auto;
      min-height:400px;
    }


Para que el servidor web la pueda cargar, se utiliza el siguiente comando de consola:

Código :

php app/console assets:install web --symlink


La función de este comando es realizar una copia o un enlace simbólico (si se especifica la opción --symlink, aunque en la plataforma Windows esto último no es posible) del contenido de los directorios Resouces/public de todos los bundles que se encuentren registrados en el framework. El comando requiere un argumento (web en nuestro caso), que especifica el directorio donde se realizará la copia o el enlace simbólico.

Dicha copia o enlazado se organiza de la siguiente manera:

Código :

web
    ├─ nombre_bundle_1
    |  ├── css
    |  ├── images
    |  └── js
    ├─ nombre_bundle_2
    |  ├── css
    |  ├── images
    |  └── js
    ...
    └─ nombre_bundle_N
       ├── css
       ├── images
       └── js


Ya sólo falta incluir una referencia en el código HTML al CSS que acabamos de incorporar. Aunque es posible incluir el enlace al CSS directamente en la plantilla ::base.html.twig, el lugar correcto es en la plantilla JazzywebAulasMentosAlimentosBundle::layout.html.twig. Teniendo en cuenta lo que hemos explicado acerca del mecanismo de herencia, habría que añadir un bloque stylesheets (heredado de la plantilla padre ::base.html.twig), en el que se haga referencia al archivo CSS.

:

src/Jazzyweb/AulasMentor/AlimentosBundle/Resources/view/layout.html.twig

Código :

...
   {% block stylesheets %}
    <link href="{{ asset('bundles/jazzywebaulasmentoralimentos/css/estilo.css') }}" type="text/css" rel="stylesheet" />
   {% endblock %} 
   ...


En este código hemos utilizado la función de Twig asset, la cual crea la URL correcta que apunta al asset en cuestión. La ruta que toma como argumento la función asset se especifica tomando como raíz el directorio web.
Puedes colocar el bloque stylesheets delante o detrás del bloque body.


Recarga la página y la verás con los estilos aplicados.

Y con esto ponemos fin a la segunda parte del tutorial. Hemos presentado los principales elementos del framework de manera que ya podrías iniciar el desarrollo de una aplicación web con Symfony2. En la tercera y última parte completaremos el resto de la aplicación. Aunque serán menos los conceptos que estudiemos, permanece atento pues se introducirán algunos elementos interesantes.

Este trabajo, por Juan David Rodríguez García<juanda at ite.educacion.es>, se encuentra bajo una Licencia Creative Commons Reconocimiento-NoComercial-CompartirIgual 3.0 Unported.

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Publica tu comentario

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate