Está en la página 1de 113

Symblog

Creando un blog con symfony 2


Creando un blog en Symfony2

Introduccin
Esta gua te llevara a travs del proceso en la creacin de un sitio web completo caracterizado para blog usando Symfony2. Utilizaremos la Edicin estndar de Symfony2, la cual incluye los principales componentes necesarios en la construccin de tu propio sitio web. La gua est dividida en varias partes, cada parte cubre diferentes aspectos de Symfony2 y sus componentes. Est destinado a trabajarse de la misma manera que su smil Jobeet de Symfony 1.

Partes de la gua
[Parte 1] Configurando Symfony2 y sus plantillas [Parte 2] Pgina de contacto: Validadores, formularios y correo electrnico [Parte 3] El modelo del Blog: Usando Doctrine 2 y accesorios [Parte 4] El modelo de comentarios: Agregando comentarios, repositorios y migraciones de Doctrine [Parte 5] Personalizando la vista: extensiones Twig, la barra lateral y Assetic [Parte 6] Probando: Unidades y funcionales con PHPUnit

Sitio web de demostracin


Puedes visitar el sitio web en http://symblog.co.uk/. El cdigo fuente est disponible en Github. De ah se desprende cada parte de esta gua.

Cobertura
Esta gua tiene como objetivo cubrir las tareas comunes a que te enfrentas a la hora de crear sitios web utilizando Symfony2. 1. Paquetes 2. Controladores 3. Plantillas (usando TWIG) 4. Modelo - Doctrine 2 5. Migraciones 6. Accesorios 7. Validadores 8. Formularios 9. Enrutado 10.Gestin de activos 11.Correo electrnico

12.Entornos 13.Personalizando pginas de error 14.Seguridad 15.El usuario y sesiones 16.Generando CRUD 17.Memoria cach 18.Probando 19.Desplegando Symfony2 es altamente personalizable y proporciona una serie de maneras diferentes para realizar la misma tarea. Algunos ejemplos de esto incluyen la redaccin de las opciones de configuracin en YAML, XML, PHP, o anotaciones, y la creacin de plantillas con Twig o PHP. Para mantener esta gua lo ms sencilla posible vamos a utilizar YAML y anotaciones para la configuracin y Twig para las plantillas. El Libro de Symfony proporciona una gran fuente de ejemplos sobre cmo usar los otros mtodos. Si deseas contribuir con la realizacin de los mtodos alternativos simplemente bifurca el repositorio en Github y enva tus peticiones de atraccin :)

Autor
Esta gua la est escribiendo dsyph3r.

Colaborando
El cdigo fuente y la documentacin para esta gua est disponible en Github. Si quieres mejorar y extender esta gua simplemente bifurca el proyecto y enva tus peticiones de atraccin. Tambin puedes plantear problemas en el rastreador de GitHub. Si alguien est interesado en crear un diseo mucho ms agradable a la vista, por favor, pngase en contacto http://twitter.com/#!/dsyph3r!

Crditos
Un agradecimiento especial a todos los colaboradores de la Documentacin oficial de Symfony2. Esta proporcion un invaluable recurso de informacin.

Buscando
En busca de un tema especfico? Usa la bsqueda.

[Parte 1] Configurando Symfony2 y sus plantillas

Descripcin
Este captulo cubre los primeros pasos para crear un sitio web Symfony2. Descargaremos y configuraremos la Edicin estndar de Symfony2, crearemos el paquete del Blog y adjuntaremos las principales plantillas HTML. Al final de este captulo habremos configurado un sitio web Symfony2 que estar disponible a travs de un dominio local, por ejemplo, http://symblog.dev/. El sitio web contendr la estructura HTML principal del blog junto con algn soso contenido. En este captulo cubriremos las siguientes reas: 1. La creacin de una aplicacin Symfony2 2. Configurando un dominio de desarrollo 3. Los paquetes de Symfony2

4. El enrutado 5. Los controladores 6. Las plantillas con Twig

Descargando e instalando
Como hemos dicho vamos a utilizar la edicin estndar de Symfony2. Esta distribucin viene con las bibliotecas del ncleo de Symfony2 y los paquetes ms comunes que se requieren para crear sitios web. Puedes descargar el paquete de Symfony2 de su sitio web. Puesto que no quiero repetir la excelente documentacin proporcionada por el libro de Symfony2, por favor consulta el captulo de la Instalacin y configuracin de Symfony2 para ver los requisitos detallados. Esto te guiar en el proceso de cul es el paquete a descargar, cmo instalar los proveedores necesarios, y la forma correcta de asignar permisos a los directorios. Advertencia Es importante prestar especial atencin a la seccin Configuracin de Permisos en el captulo de instalacin. Este explica las distintas formas en que puedes asignar permisos a los directorios app/cache y app/logs para que el usuario del servidor web y el usuario de lnea de ordenes tengan acceso de escritura a ellos.

Creando un dominio de Desarrollo


A efectos de esta gua vamos a utilizar el dominio local http://symblog.dev/ sin embargo, puedes elegir cualquier dominio que desees. Estas instrucciones son especficas para Apache y se supone que ya has configurado Apache y est corriendo en tu mquina. Si te sientes cmodo configurando dominios locales, o utilizas un servidor web diferente, tal como nginx puedes omitir esta seccin. Nota Estos pasos se llevaron a cabo en la distribucin Fedora de Linux por lo tanto los nombres de ruta, etc., pueden variar en funcin de tu sistema operativo. Vamos a empezar creando un servidor virtual con Apache. Busca el archivo de configuracin de Apache y anexa los siguientes ajustes, asegurndote de cambiar las rutas de DocumentRoot y Directory consecuentemente. La ubicacin y nombre del archivo de configuracin de Apache puede variar mucho dependiendo de tu sistema operativo. En Fedora se encuentra ubicado en /etc/httpd/conf/httpd.conf. Tendrs que editar este archivo con privilegios sudo.
# /etc/httpd/conf/httpd.conf NameVirtualHost 127.0.0.1 <VirtualHost 127.0.0.1> ServerName symblog.dev DocumentRoot "/var/www/html/symblog.dev/web" DirectoryIndex app.php <Directory "/var/www/html/symblog.dev/web"> AllowOverride All Allow from All </Directory> </VirtualHost>

Luego agrega un nuevo dominio en la parte inferior del archivo host ubicado en /etc/hosts. Una vez ms, tendrs que editar este archivo con privilegios sudo.

# /etc/hosts 127.0.0.1

symblog.dev

Por ltimo no olvides reiniciar el servicio Apache. Esto deber cargar los ajustes de configuracin actualizados que hemos hecho.
$ sudo service httpd restart

Truco Si te encuentras creando dominios virtuales todo el tiempo, puedes simplificar este proceso, usando servidores virtuales dinmicos. Ahora deberas poder visitar http://symblog.dev/app_dev.php/.

Si esta es tu primera visita a la pgina de bienvenida de Symfony2, dedica algn tiempo para ver las pginas de demostracin. Cada pgina de demostracin ofrece fragmentos de cdigo que muestran cmo funciona cada pgina en segundo plano. Nota Tambin notars una barra de herramientas en la parte inferior de la pantalla de bienvenida. Esta es la barra de depuracin web y te proporcionar informacin muy valiosa sobre el estado de tu aplicacin. La informacin incluye el tiempo consumido en la elaboracin de la pgina, el uso de memoria, las consultas hechas a la base de datos, el estado de autenticacin y puedes ver mucho ms desde esta barra de herramientas. Por omisin, la barra de herramientas slo est visible cuando se ejecuta en el entorno dev, puesto que proporcionar la barra de herramientas en produccin sera un gran riesgo de seguridad ya que expone una gran cantidad de informacin interna de tu aplicacin. A travs de esta gua, nos referiremos constantemente a la barra de herramientas a medida que introduzcamos nuevas caractersticas.

Configurando Symfony: La interfaz Web


Symfony2 introduce una interfaz web para configurar varios aspectos relacionados con el sitio web, tal como la configuracin de la base de datos. Para este proyecto necesitamos una base de datos, por lo tanto esto nos permite empezar utilizando el configurador. Visita http://symblog.dev/app_dev.php/ y haz clic en el botn Configurar. Introduce los detalles de configuracin de la base de datos (esta gua asume el uso de MySQL, pero puedes elegir cualquier otra base de datos a la que tengas acceso), seguidos en la siguiente pgina por la generacin de un segmento CSRF. Se te presentar la configuracin de los parmetros que Symfony2 ha generado. Presta especial atencin a la notificacin en la pgina, es probable que tu archivo app/paramaters.yml no se pueda escribir, por lo tanto tendrs que copiar y pegar los ajustes en el archivo que se encuentra en app/parameters.yml (esta configuracin puede sustituir los ajustes existentes en el archivo).

Paquetes: bloques de construccin de Symfony2


Los paquetes son el elemento fundamental de cualquier aplicacin Symfony2, de hecho Symfony2 en s mismo es un paquete. Los paquetes nos permiten separar la funcionalidad proporcionando unidades de cdigo reutilizable. Ellos encierran todo lo necesario para apoyar el propsito del paquete incluyendo controladores, modelo, plantillas, y diversos recursos, como imgenes y CSS. Vamos a crear un paquete para nuestro sitio web bajo el espacio de nombres Blogger. Si no ests familiarizado con los espacios de nombres en PHP debes dedicar algn tiempo leyendo sobre ellos, ya que en Symfony2 se utilizan profusamente, todo es un espacio de nombres. Ve el cargador automtico de Symfony2 para obtener detalles especficos sobre cmo logra Symfony2 cargar tus clases automticamente. Truco Una buena comprensin de los espacios de nombres te puede ayudar a eliminar problemas comunes que puedes encontrar cuando la estructuracin de directorios no se hace correctamente en las estructuras del espacio de nombres.

Creando el paquete
Para encapsular la funcionalidad del blog vamos a crear un paquete, le daremos un nombre original para salir de lo comn y corriente, lo llamaremos Blog. Este contar con todos los archivos y recursos necesarios, por lo tanto fcilmente se podra instalar en otra aplicacin de Symfony2. Symfony2 ofrece una serie de tareas que nos ayudarn a realizar las operaciones ms comunes. Una de estas tareas es el generador de paquetes. Para arrancar el generador de paquetes ejecuta la siguiente orden. Se te presentar una serie de instrucciones que te permitirn la manera de configurar tu paquete. En esta ocasin, vamos a utilizar el valor predeterminado para cada pregunta.
$ php app/console generate:bundle --namespace=Blogger/BlogBundle --format=yml

Una vez finalizado el generador, Symfony2 habr construido el diseo bsico para el paquete. Aqu debemos tener en cuenta unos cuantos cambios importantes. Truco No es obligatorio que utilices el generador de tareas proporcionado por Symfony2, este simplemente est ah para ayudarte. Podras haber creado manualmente la estructura de directorios y los archivos del paquete. Si bien no es obligatorio usar los generadores, proporcionan algunos beneficios, como son la rapidez y ejecucin de todas las tareas necesarias para conseguir todo lo necesario en el

paquete y funcionando. Un ejemplo de ello es el registro del paquete. Registrando el paquete Nuestro nuevo paquete BloggerBlogBundle automticamente se ha registrado en el ncleo de nuestra aplicacin situado en app/AppKernel.php. Symfony2 nos obliga a registrar todos los paquetes que necesita usar la aplicacin. Tambin notars que algunos paquetes se registran slo cuando ests en el entorno dev o test. Cargar esos paquetes en el entorno prod (por produccin) sera introducir una sobrecarga adicional de cierta funcionalidad que no se utilizara. El siguiente fragmento de cdigo muestra cmo se ha registrado el paquete BloggerBlogBundle.
// app/AppKernel.php class AppKernel extends Kernel { public function registerBundles() { $bundles = array( // .. new Blogger\BlogBundle\BloggerBlogBundle(), ); // .. } } return $bundles;

// ..

Enrutado El enrutado del paquete se ha importado al archivo de enrutado principal de la aplicacin ubicado en app/config/routing.yml.
# app/config/routing.yml BloggerBlogBundle: resource: "@BloggerBlogBundle/Resources/config/routing.yml" prefix: /

La opcin prefix nos permite montar todo el enrutado de BloggerBlogBundle con un prefijo. En nuestro caso hemos optado por montarlo en el valor predeterminado /. Si por ejemplo quisieras que todas tus rutas llevaran el prefijo /blogger slo tienes que cambiar el prefijo a prefix: /blogger. Estructura predeterminada Por omisin el diseo del paquete se crea bajo el directorio src. Esto comienza en el nivel superior con el directorio Blogger, el cual corresponde directamente con el espacio de nombres Blogger, en el que hemos creado nuestro paquete. Bajo este, tenemos el directorio BlogBundle el cual contiene el paquete real. Examinaremos el contenido de este directorio a medida que vayamos avanzando. Si ests familiarizado con las plataformas MVC, algunos de los directorios se explican por s mismos.

El controlador predeterminado
Como parte del generador de paquetes, Symfony2 ha creado un controlador predeterminado. Podemos ejecutar este controlador, visita la direccin

http://symblog.dev/app_dev.php/hello/symblog. Deberas ver una pgina con un simple saludo. Intenta cambiando la parte symblog de la URL por tu nombre. Podemos examinar cmo se gener esta pgina a un alto nivel. Enrutado El archivo de enrutado del BloggerBlogBundle ubicado en src/Blogger/BlogBundle/Recursos/config/routing.yml contiene la siguiente regla de enrutado.
# src/Blogger/BlogBundle/Resources/config/routing.yml BloggerBlogBundle_homepage: pattern: /hello/{name} defaults: { _controller: BloggerBlogBundle:Default:index }

La ruta se compone de un patrn y algunas opciones predeterminadas. El patrn se compara con la URL, y las opciones defaults especifican el controlador que se ejecutar si coincide la ruta. En el patrn /hello/{name}, el marcador de posicin {name} coincide con cualquier valor puesto que no se han establecido requisitos especficos. La ruta tampoco especifica ningn formato, mtodo HTML, o regin. Puesto que no existen requisitos para mtodos HTTP, todas las peticiones GET, POST, PUT, etc., sern elegibles para concordar con el patrn. Si la ruta rene todos los criterios especificados, ser ejecutada por el _controller de la opcin defaults. La opcin _controller especifica el nombre lgico del controlador que permite a Symfony2 asignarla a un archivo especfico. El ejemplo anterior har que la accin index en el controlador Default ubicado en src/Blogger/BlogBundle/Controller/DefaultController.php se ejecute. El controlador El controlador en este ejemplo es muy sencillo. La clase DefaultController extiende la clase Controller que proporciona algunos mtodos tiles, como el mtodo render que utilizaremos a continuacin. Debido a que nuestra ruta define un marcador de posicin este se pasa a la accin como el argumento $name. La accin no hace ms que llamar al mtodo render especificando la plantilla index.html.twig ubicada en el directorio Default de las vistas del BloggerBlogBundle. El formato del nombre de la plantilla es paquete:controlador:plantilla. En nuestro ejemplo, este es BloggerBlogBundle:Default:index.html.twig el cual designa a la plantilla index.html.twig, ubicada en el directorio Default de las vistas del BloggerBlogBundle, o fsicamente en el archivo src/Blogger/BlogBundle/Resources/views/Default/index.html.twig. Puedes utilizar diferentes variaciones del formato de la plantilla para reproducir plantillas en diferentes lugares de tu aplicacin y sus paquetes. Veremos esto ms adelante en este captulo. Tambin le pasamos la variable $name a la plantilla a travs de una matriz de opciones.
<?php // src/Blogger/BlogBundle/Controller/DefaultController.php namespace Blogger\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class DefaultController extends Controller { public function indexAction($name) { return $this->render('BloggerBlogBundle:Default:index.html.twig',

array('name' => $name)); } }

La plantilla (la Vista) Como puedes ver la plantilla es muy simple. Esta imprime Hello seguido por el argumento nombre pasado desde el controlador.
{# src/Blogger/BlogBundle/Resources/views/Default/index.html.twig #} Hello {{ name }}!

Limpiando
Puesto que no necesitamos algunos de los archivos creados por el generador de paquetes, podemos limpiar un poco. Puedes eliminar el archivo del controlador src/Blogger/BlogBundle/Controller/DefaultController.php, junto con el directorio de la vista y su contenido en src/Blogger/BlogBundle/Resources/views/Default/. Por ltimo, elimina la ruta definida en src/Blogger/BlogBundle/Resources/config/routing.yml

Plantillas
Cuando utilizamos Symfony2, tenemos 2 opciones predeterminadas para el motor de plantillas; Twig y PHP. Incluso, podras optar por no usar ninguno de estos y elegir una biblioteca diferente. Esto es posible gracias al contenedor de inyeccin de dependencias de Symfony2. Vamos a usar Twig como nuestro motor de plantillas por una serie de razones: 1. Twig es rpido Las plantillas Twig se compilan hasta clases PHP por lo que hay muy poca sobrecarga al usar las plantillas Twig. 2. Twig es conciso Twig nos permite realizar funcionalidad de plantillas con muy poco cdigo. Comparando esto con PHP en que estas son unas declaraciones a ser muy detalladas. 3. Twig admite la herencia entre plantillas Esto personalmente es uno de mis favoritos. Las plantillas tienen la capacidad de extender y sustituir otras plantillas, lo cual permite a las plantillas hijas cambiar los valores predeterminados proporcionados por sus padres. 4. Twig es seguro De manera predeterminada Twig tiene activado el escape de toda su produccin, e incluso proporciona un entorno de rea de seguridad para plantillas importadas. 5. Twig es extensible Twig viene con un montn de funcionalidades bsicas comunes que se espera tenga un motor de plantillas, pero para aquellas ocasiones donde se requiere una funcionalidad adicional a medida, fcilmente puedes extender a Twig. Estos slo son algunos de los beneficios de Twig. Para ms razones por las que debes utilizar Twig ve el sitio oficial de Twig.

Estructurando el diseo
Debido a que Twig admite la herencia entre plantillas, vamos a utilizar el enfoque de la herencia de tres niveles. Este enfoque nos permite modificar la vista en tres distintos niveles dentro de la aplicacin, lo cual nos da un montn de espacio para nuestras personalizaciones.

La plantilla principal Nivel 1 Vamos a empezar creando nuestra plantilla bsica a nivel de bloques para symblog. Aqu, necesitaremos dos archivos, la plantilla y la hoja de estilo CSS. Puesto que Symfony2 es compatible con HTML5 tambin lo vamos a utilizar.
<!-- app/Resources/views/base.html.twig --> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html"; charset=utf-8" /> <title>{% block title %}symblog{% endblock %} - symblog</title> <!--[if lt IE 9]> <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> {% block stylesheets %} <link href='http://fonts.googleapis.com/css?family=Irish+Grover' rel='stylesheet' type='text/css'> <link href='http://fonts.googleapis.com/css?family=La+Belle+Aurore' rel='stylesheet' type='text/css'> <link href="{{ asset('css/screen.css') }}" type="text/css" rel="stylesheet" /> {% endblock %} <link rel="shortcut icon" href="{{ asset('favicon.ico') }}" /> </head> <body> <section id="wrapper"> <header id="header"> <div class="top"> {% block navigation %} <nav> <ul class="navigation"> <li><a href="#">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Contact</a></li> </ul> </nav> {% endblock %} </div> <hgroup> <h2>{% block blog_title %}<a href="#">symblog</a>{% endblock %}</h2> <h3>{% block blog_tagline %}<a href="#">creating a blog in Symfony2</a>{% endblock %}</h3> </hgroup> </header> <section class="main-col"> {% block body %}{% endblock %} </section> <aside class="sidebar"> {% block sidebar %}{% endblock %} </aside> <div id="footer"> {% block footer %} Symfony2 blog tutorial - created by <a href="https://github.com/dsyph3r">dsyph3r</a> {% endblock %}

</div> </section> {% block javascripts %}{% endblock %} </body> </html>

Nota Hay tres archivos externos incluidos en la plantilla, un JavaScript y dos CSS. El archivo JavaScript corrige la carencia de apoyo para HTML5 en los navegadores IE previos a la versin 9. Los dos archivos CSS importan tipos de letra desde Google Web font. Esta plantilla marca la estructura principal de nuestro sitio web. La mayor parte de la plantilla est compuesta por HTML, con las extraas directivas de Twig. Ahora vamos a examinar estas directivas de Twig. Empecemos enfocndonos en el HEAD del documento. Veamos de cerca el ttulo:
<title>{% block title %}symblog{% endblock %} - symblog</title>

La primer cosa que notamos es esa extraa etiqueta {%. Esta no es HTML, y definitivamente tampoco es PHP. Esta es una de las tres etiquetas de Twig. Esta etiqueta es la etiqueta de Twig que Hace algo. Se utiliza para ejecutar expresiones tales como instrucciones de control y para definir elementos de bloque. Puedes encontrar una lista completa de las estructuras de control en la documentacin de Twig. El bloque Twig que definimos en el ttulo hace dos cosas; Establece el identificador del bloque para el ttulo, y proporciona una salida predeterminada entre las directivas block y endblock. Al definir un bloque podemos tomar ventaja del modelo de herencia de Twig. Por ejemplo, en una pgina para mostrar un blog deseamos que el ttulo de la pgina refleje el ttulo del blog. Lo podemos lograr extendiendo la plantilla y reemplazando el bloque del ttulo.
{% extends '::base.html.twig' %} {% block title %}The blog title goes here{% endblock %}

En el ejemplo anterior hemos extendido la plantilla base de la aplicacin que por primera vez defini el bloque ttulo. Notars que al formato de plantilla utilizado en la directiva extends le faltan las partes paquete y controlador, recuerda que el formato de la plantilla es paquete:controlador:plantilla. Al excluir las partes paquete y controlador estamos especificando que se usen las plantillas de la aplicacin definidas a nivel de app/Resources/views/. A continuacin definimos otro bloque de ttulo y pusimos un cierto contenido, en este caso el ttulo del blog. Puesto que la plantilla principal ya contiene un bloque ttulo, este lo sustituye por el nuestro. El ttulo ahora reproduce El ttulo del blog va aqu - symblog. Esta funcionalidad proporcionada por Twig se utiliza profusamente en la creacin de plantillas. En el bloque stylesheet introducimos la siguiente etiqueta de Twig, la etiqueta {{, o la etiqueta que Dice algo.
<link href="{{ asset('css/screen.css') }}" type="text/css" rel="stylesheet" />

Esta etiqueta se utiliza para imprimir el valor de una variable o expresin. En el ejemplo anterior esta imprime el valor devuelto por la funcin asset, el cual nos proporciona una forma porttil para vincular nuestros elementos de la aplicacin, tales como CSS, JavaScript e imgenes. La etiqueta {{ tambin se puede combinar con filtros para manipular la salida antes de imprimirla.
{{ blog.created|date("d-m-Y") }}

Para ver una lista completa de los filtros ve la documentacin de Twig. La ltima etiqueta de Twig, que hasta ahora no hemos visto en las plantillas, es la etiqueta de comentario {#. Su uso es el siguiente:
{# The quick brown fox jumps over the lazy dog #}

No hay otros conceptos a introducir en esta plantilla. Esta proporciona el diseo principal que necesitamos listo para personalizarlo. A continuacin vamos a aadir algunos estilos. Crea una hoja de estilos en web/css/screen.css y aade el siguiente contenido. Esto agregar estilos para la plantilla principal.
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abb r,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,str ong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,tab le,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure, figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,a udio,video{border:0;font-size:100%;font:inherit;verticalalign:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer ,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{liststyle:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before, q:after{content:none}table{border-collapse:collapse;border-spacing:0} body { line-height: 1;font-family: Arial, Helvetica, sans-serif;font-size: 12px; width: 100%; height: 100%; color: #000; font-size: 14px; } .clear { clear: both; } #wrapper { margin: 10px auto; width: 1000px; } #wrapper a { text-decoration: none; color: #F48A00; } #wrapper span.highlight { color: #F48A00; } #header { border-bottom: 1px solid #ccc; margin-bottom: 20px; } #header .top { border-bottom: 1px solid #ccc; margin-bottom: 10px; } #header ul.navigation { list-style: none; text-align: right; } #header .navigation li { display: inline } #header .navigation li a { display: inline-block; padding: 10px 15px; borderleft: 1px solid #ccc; } #header h2 { font-family: 'Irish Grover', cursive; font-size: 92px; text-align: center; line-height: 110px; } #header h2 a { color: #000; } #header h3 { text-align: center; font-family: 'La Belle Aurore', cursive; fontsize: 24px; margin-bottom: 20px; font-weight: normal; } .main-col { width: 700px; display: inline-block; float: left; border-right: 1px solid #ccc; padding: 20px; margin-bottom: 20px; } .sidebar { width: 239px; padding: 10px; display: inline-block; } .main-col a { color: #F48A00; } .main-col h1, .main-col h2 { line-height: 1.2em; font-size: 32px; margin-bottom: 10px; font-weight: normal; color: #F48A00; } .main-col p { line-height: 1.5em; margin-bottom: 20px; } #footer { border-top: 1px solid #ccc; clear: both; text-align: center; padding: 10px; color: #aaa; }

Plantilla del paquete nivel 2 Ahora, pasemos a la creacin del diseo del paquete Blog. Crea un archivo ubicado en src/Blogger/BlogBundle/Resources/views/base.html.twig con el siguiente contenido:
{# src/Blogger/BlogBundle/Resources/views/base.html.twig #} {% extends '::base.html.twig' %} {% block sidebar %} Sidebar content {% endblock %}

A primera vista, esta plantilla puede parecer un tanto simple, pero su simplicidad es la clave. En primer lugar, esta extiende a la plantilla base de la aplicacin, la cual hemos creado antes. En segundo lugar, sustituye el bloque de la barra lateral padre con cierto texto. Debido a que la barra lateral estar presente en todas las pginas de nuestro blog tiene sentido llevar a cabo la personalizacin en este nivel. Puedes preguntarte por qu no slo ponemos la personalizacin en la plantilla de la aplicacin, ya que estar presente en todas las pginas? Esto es simple, la aplicacin no sabe nada sobre el paquete y tampoco debera. El paquete debe autocontener toda su funcionalidad y hacer que la barra lateral sea parte de esta funcionalidad. Bien, as que por qu no basta con colocar la barra lateral en cada una de las plantillas de pgina? Una vez ms esto es simple, tendramos que duplicar la barra lateral cada vez que agreguemos una pgina. Adems, esta plantilla de nivel 2 nos da la flexibilidad para que en el futuro agreguemos otras personalizaciones que heredarn todas las plantillas hijas. Por ejemplo, posiblemente queramos cambiar los derechos de autor del pie de pgina en todas las pginas, este sera un gran lugar para hacerlo. Plantillas de pgina Nivel 3 Finalmente estamos listos para el diseo del controlador. Estos diseos comnmente se relacionan con un controlador de accin, es decir, la accin show blog debe tener una plantilla show blog. Vamos a empezar creando el controlador de la pgina inicial y su plantilla. Ya que se trata de la primera pgina que estamos creando, es necesario crear el controlador. Crea el controlador en src/Blogger/BlogBundle/Controller/PageController.php con el siguiente contenido:
<?php // src/Blogger/BlogBundle/Controller/PageController.php namespace Blogger\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class PageController extends Controller { public function indexAction() { return $this->render('BloggerBlogBundle:Page:index.html.twig'); } }

Ahora crea la plantilla para esta accin. Como puedes ver en la accin del controlador vamos a reproducir la plantilla de la pgina ndice. Crea la plantilla en src/Blogger/BlogBundle/Resources/views/Page/index.html.twig
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #} {% extends 'BloggerBlogBundle::base.html.twig' %}

{% block body %} Blog homepage {% endblock %}

Esta introduce el formato que queramos especificar a la plantilla final. En este ejemplo, la plantilla BloggerBlogBundle::base.html.twig extiende a la plantilla nombrada pero omitimos la parte del Controlador. Al excluir la parte Controlador estamos especificando que se use la plantilla a nivel del paquete creada en src/Blogger/BlogBundle/Resources/views/base.html.twig. Ahora vamos a agregar una ruta para nuestra pgina inicial. Actualiza la configuracin de ruta del paquete ubicada en src/Blogger/BlogBundle/Resources/config/routing.yml.
# src/Blogger/BlogBundle/Resources/config/routing.yml BloggerBlogBundle_homepage: pattern: / defaults: { _controller: BloggerBlogBundle:Page:index } requirements: _method: GET

Por ltimo tenemos que eliminar la ruta predeterminada para la pantalla de bienvenida de Symfony2. Elimina la ruta _welcome de la parte superior del archivo de enrutado dev ubicado en app/config/routing_dev.yml. Ahora estamos listos para ver nuestra plantilla del blogger. Apunta tu navegador a http://symblog.dev/app_dev.php/.

Deberas ver el diseo bsico del blog, con el contenido principal y la barra lateral reflejando los bloques que hemos sustituido en las plantillas correspondientes.

La pgina Sobre
La tarea final de esta parte de la gua es crear una pgina esttica para la pgina sobre. Esta te mostrar la manera de vincular las pginas y, adems, cumplir el criterio de la herencia de tres niveles que hemos adoptado.

La ruta
Al crear una nueva pgina, una de las primeras tareas debera ser crear su ruta. Abre el archivo de enrutado del BloggerBlogBundle ubicado en src/Blogger/BlogBundle/Resources/config/routing.yml y aade la siguiente regla de enrutado.
# src/Blogger/BlogBundle/Resources/config/routing.yml BloggerBlogBundle_about: pattern: /about defaults: { _controller: BloggerBlogBundle:Page:about } requirements: _method: GET

El controlador
A continuacin abre el controlador de la Pgina que se encuentra en src/Blogger/BlogBundle/Controller/PageController.php y agrega la accin para procesar la pgina sobre.
// src/Blogger/BlogBundle/Controller/PageController.php class PageController extends Controller { // .. public function aboutAction() { return $this->render('BloggerBlogBundle:Page:about.html.twig'); } }

La vista
Para la vista, crea un nuevo archivo situado en src/Blogger/BlogBundle/Resources/views/Page/about.html.twig y copia el siguiente contenido.
{# src/Blogger/BlogBundle/Resources/views/Page/about.html.twig #} {% extends 'BloggerBlogBundle::base.html.twig' %} {% block title %}About{% endblock%} {% block body %} <header> <h1>About symblog</h1> </header> <article> <p>Donec imperdiet ante sed diam consequat et dictum erat faucibus. Aliquam sit amet vehicula leo. Morbi urna dui, tempor ac posuere et, rutrum at dui. Curabitur neque quam, ultricies ut imperdiet id, ornare varius arcu. Ut congue

urna sit amet tellus malesuada nec elementum risus molestie. Donec gravida egestas tellus sed tortor adipiscing fringilla. Donec nulla mauris, mollis

condimentum laoreet, lacinia vel lorem. Morbi vitae justo sit amet felis vehicula commodo a placerat lacus. Mauris at est elit, nec vehicula urna. Duis a lacus nisl. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae.</p> </article> {% endblock %}

La pgina sobre no es nada espectacular. Su nica accin es reproducir un archivo de plantilla con un inspido contenido. Sin embargo, s nos lleva a la siguiente tarea.

Enlazando pginas
Ahora casi tenemos lista la pgina. Echa un vistazo a http://symblog.dev/app_dev.php/about para ver esto. En su estado actual no hay forma de que un usuario de tu blog vea la pgina sobre, a no ser que escriba la URL completa al igual que lo hicimos nosotros. Como era de esperar Symfony2 ofrece ambas partes de la ecuacin de enrutado. Puede coincidir rutas, como hemos visto, y tambin puede generar las URL de esas rutas. Siempre debes usar las funciones de enrutado que proporciona Symfony2. En tu aplicacin nunca deberas tener la tentacin de poner lo siguiente:
<a href="/contact">Contact</a> <?php $this->redirect("/contact"); ?>

Tal vez te ests preguntando qu es lo incorrecto con este enfoque, tal vez la forma en que siempre se enlazan sus pginas. Sin embargo, hay una serie de problemas con este enfoque. 1. Este utiliza un enlace duro e ignora el sistema de enrutado de Symfony2 por completo. Si, en algn momento, quieres cambiar la ubicacin de la pgina de contacto tendras que encontrar todas las referencias al enlace duro y cambiarlas. 2. Este ignora el entorno de tus controladores. El entorno es algo que no hemos explicado todava, pero que has estado utilizando. El controlador frontal app_dev.php nos da acceso a nuestra aplicacin en el entorno dev. Si sustituyes app_dev.php con app.php vas a ejecutar la aplicacin en el entorno prod. La importancia de estos entornos se explica ms adelante en la gua, pero por ahora es importante tener en cuenta que el enlace fijo definido anteriormente no mantiene el entorno actual en que nos encontramos, el controlador frontal no se antepone a la URL. La forma correcta para enlazar pginas, es con los mtodos path y url proporcionados por Twig. Ambos son muy similares, excepto que el mtodo url nos proporcionar direcciones URL absolutas. Permite actualizar la plantilla de la aplicacin principal que se encuentra en app/Resources/views/base.html.twig para enlazar la pgina sobre y la pgina inicial.
<!-- app/Resources/views/base.html.twig --> {% block navigation %} <nav> <ul class="navigation"> <li><a href="{{ path('BloggerBlogBundle_homepage') }}">Home</a></li> <li><a href="{{ path('BloggerBlogBundle_about') }}">About</a></li> <li><a href="#">Contact</a></li> </ul>

</nav> {% endblock %}

Ahora actualiza tu navegador para ver que los enlaces de las pginas Inicio y Sobre trabajan como se esperaba. Si ves el cdigo fuente de las pginas te dars cuenta de que el enlace se ha prefijado con /app_dev.php/. Este es el controlador frontal explicado ms arriba, y como puedes ver al usar path lo ha mantenido. Finalmente actualicemos los enlaces del logotipo para que redirijan a la pgina inicial. Actualiza la plantilla ubicada en app/Resources/views/base.html.twig.
<!-- app/Resources/views/base.html.twig --> <hgroup> <h2>{% block blog_title %}<a href="{{ path('BloggerBlogBundle_homepage') }}">symblog</a>{% endblock %}</h2> <h3>{% block blog_tagline %}<a href="{{ path('BloggerBlogBundle_homepage') }}">creating a blog in Symfony2</a>{% endblock %}</h3> </hgroup>

Conclusin
Hemos cubierto los aspectos bsicos respecto a una aplicacin de Symfony2 incluyendo cierta personalizacin de la aplicacin y ahora est funcionando. Comenzamos a explorar los conceptos fundamentales detrs de una aplicacin Symfony2, incluyendo el enrutado y el motor de plantillas Twig. A continuacin veremos cmo se crea la pgina de Contacto. Esta pgina es un poco ms complicada que la pgina Sobre, ya que permite a los usuarios interactuar con un formulario web para enviar consultas. El siguiente captulo introducir conceptos ms avanzados como la validacin y formularios.

[Parte 2] Pgina de contacto: Validadores, formularios y correo electrnico

Descripcin
Ahora que tenemos en su lugar las plantillas HTML bsicas, es hora de hacer una de las pginas funcionales. Vamos a empezar con una de las pginas ms simples; La pgina de Contacto. Al final de este captulo tendrs una pgina de Contacto que permite a los usuarios enviar sus consultas al administrador del sitio. Estas consultas sern enviadas por correo electrnico al administrador del sitio. En este captulo cubriremos las siguientes reas: 1. Validadores 2. Formularios 3. Ajuste de los valores de configuracin del paquete

Pgina de contacto
Enrutando
Al igual que con la pgina sobre creada en el captulo anterior, vamos a comenzar definiendo la ruta de la pgina de Contacto. Abre el archivo de enrutado del BloggerBlogBundle ubicado

en src/Blogger/BlogBundle/Resources/config/routing.yml y aade la siguiente regla de enrutado.


# src/Blogger/BlogBundle/Resources/config/routing.yml BloggerBlogBundle_contact: pattern: /contact defaults: { _controller: BloggerBlogBundle:Page:contact } requirements: _method: GET

No hay nada nuevo aqu, la regla coincide con el patrn /contact, para el mtodo GET del protocolo HTTP y ejecuta la accin contact del controlador Page en el BloggerBlogBundle.

Controlador
A continuacin vamos a aadir la accin para la pgina Contacto al controlador Page en el BloggerBlogBundle situado en src/Blogger/BlogBundle/Controller/PageController.php.
// src/Blogger/BlogBundle/Controller/PageController.php // .. public function contactAction() { return $this->render('BloggerBlogBundle:Page:contact.html.twig'); } // ..

Por ahora la accin es muy simple, slo reproduce la vista de la pgina contacto. Ms tarde volveremos al controlador.

La vista
Crea la vista de la pgina contacto en src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig y aade el siguiente contenido.
{# src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig #} {% extends 'BloggerBlogBundle::base.html.twig' %} {% block title %}Contact{% endblock%} {% block body %} <header> <h1>Contact symblog</h1> </header> <p>Want to contact symblog?</p> {% endblock %}

Esta plantilla tambin es muy simple. Extiende la plantilla del diseo de BloggerBlogBundle, remplazando el bloque de ttulo para establecer un ttulo personalizado y define algn contenido para el bloque body.

Enlazando la pgina
Por ltimo tenemos que actualizar el enlace en la plantilla de la aplicacin ubicada en app/Resources/views/base.html.twig para enlazar la pgina de contacto.

<!-- app/Resources/views/base.html.twig --> {% block navigation %} <nav> <ul class="navigation"> <li><a href="{{ path('BloggerBlogBundle_homepage') }}">Home</a></li> <li><a href="{{ path('BloggerBlogBundle_about') }}">About</a></li> <li><a href="{{ path('BloggerBlogBundle_contact') }}">Contact</a></li> </ul> </nav> {% endblock %}

Si diriges tu navegador a http://symblog.dev/app_dev.php/ y haces clic en el enlace de contacto en la barra de navegacin, deberas ver una pgina de contacto muy bsica. Ahora que hemos configurado la pgina correctamente, es hora de empezar a trabajar en el formulario de Contacto. Esto se divide en dos partes bien diferenciadas; Los validadores y el formulario. Antes de que podamos abordar el concepto de los validadores y el formulario, tenemos que pensar en cmo vamos a manejar los datos de la consulta de Contacto.

La entidad Contacto
Vamos a empezar creando una clase que representa una consulta de Contacto de un usuario. Queremos capturar informacin bsica como nombre, asunto y el cuerpo de la consulta. Crea un nuevo archivo situado en src/Blogger/BlogBundle/Entity/Enquiry.php y pega el siguiente contenido:
<?php // src/Blogger/BlogBundle/Entity/Enquiry.php namespace Blogger\BlogBundle\Entity; class Enquiry { protected $name; protected $email; protected $subject; protected $body; public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } public function getEmail() { return $this->email; } public function setEmail($email) { $this->email = $email; }

public function getSubject() { return $this->subject; } public function setSubject($subject) { $this->subject = $subject; } public function getBody() { return $this->body; } public function setBody($body) { $this->body = $body; }

Como puedes ver esta clase slo define algunas de las propiedades protegidas y mtodos para acceder a ellas. No hay nada aqu que defina cmo validar las propiedades, o cmo se relacionan las propiedades con los elementos de formulario. Volveremos a esto ms adelante. Nota Vamos a desviarnos un poco para hablar rpidamente sobre el uso de los espacios de nombres en Symfony2. La clase entidad que hemos creado establece el espacio de nombres Blogger\BlogBundle\Entity. Puesto que Symfony2 es compatible con la carga automtica del estndar PSR-0 el espacio de nombres denota la estructura de directorios del paquete. La clase entidad Enquiry se encuentra en src/Blogger/BlogBundle/Entity/Enquiry.php lo cual garantiza que Symfony2 est en condiciones de cargar la clase automtica y correctamente. Cmo hace el cargador automtico de Symfony2 para saber que el espacio de nombres del Blogger se puede encontrar en el directorio src? Esto es gracias a los ajustes en el cargador automtico en app/autoloader.php
// app/autoloader.php $loader->registerNamespaceFallbacks(array( __DIR__.'/../src', ));

Esta expresin registra un retroceso para cualquier espacio de nombres que no est registrado ya. Debido a que el espacio de nombres del Blogger no est registrado, el cargador automtico de Symfony2 buscar los archivos necesarios en el directorio src. La carga automtica y el espacio de nombres son un concepto muy potente en Symfony2. Si se producen errores donde PHP es incapaz de encontrar las clases, es probable que haya un error en el espacio de nombres o la estructura de directorios. Tambin puedes verificar el espacio de nombres que se ha registrado en el cargador automtico como se muestra arriba. Nunca debes ceder a la tentacin de corregir esto usando las directivas PHP require o include.

Formularios
A continuacin vamos a crear el formulario. Symfony2 viene empacado con una plataforma de formularios muy potente que facilita la tediosa tarea de tratar con formularios. Como con todo los componentes de Symfony2, lo puedes utilizar fuera de Symfony2 en tus propios proyectos. El cdigo

fuente del componente Form est disponible en Github. Vamos a empezar creando una clase AbstractType que representa el formulario de la consulta. Podramos haber creado el formulario directamente en el controlador y no molestarnos con esta clase, sin embargo, separar el formulario en su propia clase nos permite volver a utilizar el formulario a travs de la aplicacin. Tambin nos evita saturar el controlador. Despus de todo, se supone que el controlador es simple. Su propsito es proporcionar el pegamento entre el Modelo y la Vista.

EnquiryType
Crea un nuevo archivo situado en `src/Blogger/BlogBundle/Form/EnquiryType.php y pega el siguiente contenido:
<?php // src/Blogger/BlogBundle/Form/EnquiryType.php namespace Blogger\BlogBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class EnquiryType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('name'); $builder->add('email', 'email'); $builder->add('subject'); $builder->add('body', 'textarea'); } public function getName() { return 'contact'; }

La clase EnquiryType introduce la clase FormBuilder. La clase FormBuilder es tu mejor amiga cuando se trata de crear formularios. Esta es capaz de simplificar el proceso de definicin de campos basndose en los metadatos con que cuenta el campo. Debido a que nuestra entidad Enquiry es tan simple an no hemos definido los metadatos para el FormBuilder los cuales por omisin tienen el tipo de campo de entrada de texto. Esto es conveniente para la mayora de los campos, excepto para el cuerpo, para el cual queremos un textarea, y el email donde deseamos tomar ventaja del nuevo tipo de entrada email de HTML5. Nota Un punto clave a mencionar aqu es que el mtodo getName debe devolver un identificador nico.

Creando el formulario en el controlador


Ahora que hemos definido la entidad Enquiry y el EnquiryType, podemos actualizar la accin Contacto para usarlas. Remplaza el contenido de la accin Contacto ubicada en src/Blogger/BlogBundle/Controller/PageController.php con lo siguiente:
// src/Blogger/BlogBundle/Controller/PageController.php public function contactAction() { $enquiry = new Enquiry(); $form = $this->createForm(new EnquiryType(), $enquiry);

$request = $this->getRequest(); if ($request->getMethod() == 'POST') { $form->bindRequest($request); if ($form->isValid()) { // realiza alguna accin, como enviar un correo electrnico // Redirige - Esto es importante para prevenir que el usuario reenve // el formulario si actualiza la pgina return $this->redirect($this>generateUrl('BloggerBlogBundle_contact')); } } return $this->render('BloggerBlogBundle:Page:contact.html.twig', array( 'form' => $form->createView() )); }

Empezamos creando una instancia de la entidad Enquiry. Esta entidad representa los datos de una consulta de contacto. A continuacin, creamos el formulario real. Especificamos el EnquiryType que creamos antes, y le pasamos nuestro objeto entidad Enquiry. El mtodo createForm es capaz de utilizar estos dos indicios para crear una representacin del formulario. Ya que esta accin del controlador tratar de mostrar y procesar el formulario presentado, tenemos que verificar el mtodo HTTP. Los formularios presentados se suelen enviar a travs del mtodo POST, y nuestro formulario no ser la excepcin. Si el mtodo de la peticin es POST, una llamada a bindRequest transformar los datos presentados de nuevo a las propiedades de nuestro objeto $enquiry. En este punto el objeto $enquiry ahora tiene una representacin de lo que el usuario envi. A continuacin hacemos una comprobacin para ver si el formulario es vlido. Como no hemos especificado ningn validador en este el punto, el formulario siempre ser vlido. Finalmente especificamos la plantilla a reproducir. Ten en cuenta que ahora le estamos pasando a la plantilla una representacin de la vista del formulario. Este objeto nos permite reproducir el formulario en la vista. Debido a que hemos utilizado dos nuevas clases en nuestro controlador, necesitamos importar los espacios de nombres. Actualiza el archivo controlador que se encuentra en src/Blogger/BlogBundle/Controller/PageController.php con lo siguiente. Las declaraciones use se deben colocar bajo las use existentes.
<?php // src/Blogger/BlogBundle/Controller/PageController.php namespace Blogger\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; // Importa el nuevo espacio de nombres use Blogger\BlogBundle\Entity\Enquiry; use Blogger\BlogBundle\Form\EnquiryType; class PageController extends Controller // ..

Reproduciendo el formulario
Gracias a los mtodos de reproduccin de formularios de Twig es muy simple. Twig proporciona un sistema de capas para representar formularios, el cual te permite reproducir el formulario como una entidad completa, o como errores individuales y elementos, dependiendo del nivel de personalizacin que requieras. Para demostrar el poder de los mtodos de Twig puedes utilizar el siguiente fragmento de cdigo para reproducir el formulario completo.
<form action="{{ path('BloggerBlogBundle_contact') }}" method="post" {{ form_enctype(form) }}> {{ form_widget(form) }} <input type="submit" /> </form>

Si bien esto es muy til para formularios simples y prototipos, tiene sus limitaciones cuando necesitas personalizacin extendida, que a menudo es el caso con los formularios. Para nuestro formulario de contacto, vamos a optar por un trmino medio. Reemplaza el cdigo de la plantilla ubicada en src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig con el siguiente.
{# src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig #} {% extends 'BloggerBlogBundle::base.html.twig' %} {% block title %}Contact{% endblock%} {% block body %} <header> <h1>Contact symblog</h1> </header> <p>Want to contact symblog?</p> <form action="{{ path('BloggerBlogBundle_contact') }}" method="post" {{ form_enctype(form) }} class="blogger"> {{ form_errors(form) }} {{ {{ {{ {{ form_row(form.nombre) }} form_row(form.email) }} form_row(form.subject) }} form_row(form.body) }}

{{ form_rest(form) }} <input type="submit" value="Submit" /> </form> {% endblock %}

Como puedes ver, utilizamos cuatro nuevos mtodos de Twig para reproducir el formulario. El primer mtodo form_enctype establece el tipo de contenido del formulario. Este se debe establecer cuando tu formulario trata con la subida de archivos. Nuestro formulario no tiene ningn uso para este mtodo, pero es buena prctica utilizarlo siempre en todos tus formularios en caso de que puedas agregar la carga de archivos en el futuro. Depurar un formulario que gestiona la carga de archivos y que no tiene establecido el tipo de contenido, se puede convertir en un verdadero rascadero de cabeza!

El segundo mtodo form_errors reproducir cualquier error del formulario en caso de que la validacin haya fallado. El tercer mtodo form_row reproduce todos los elementos relacionados con cada campo del formulario. Esto incluye los errores del campo, la etiqueta label para el campo y el elemento grfico real del campo. Por ltimo utilizamos el mtodo form_rest. El cual siempre es una apuesta segura para utilizar el mtodo al final del formulario para reproducir los campos que puedes haber olvidado, incluidos los campos ocultos y el segmento CSRF de los formularios de Symfony2. Nota La falsificacin de peticin en sitios cruzados (CSRF) se explica con detalle en el captulo Formularios del libro de Symfony2.

Estilizando el formulario
Si ves el formulario de contacto en http://symblog.dev/app_dev.php/contact te dars cuenta de que no se ve tan atractivo. Le vamos a aadir algo de estilo para mejorar ese aspecto. Puesto que los estilos son especficos al formulario dentro de nuestro paquete de Blog vamos a crear los estilos en una hoja de estilo dentro del propio paquete. Crea un nuevo archivo ubicado en src/Blogger/BlogBundle/Resources/public/css/blog.css y pega el siguiente contenido:
.blogger-notice { text-align: center; padding: 10px; background: #DFF2BF; border: 1px solid; color: #4F8A10; margin-bottom: 10px; } form.blogger { font-size: 16px; } form.blogger div { clear: left; margin-bottom: 10px; } form.blogger label { float: left; margin-right: 10px; text-align: right; width: 100px; font-weight: bold; vertical-align: top; padding-top: 10px; } form.blogger input[type="text"], form.blogger input[type="email"] { width: 500px; line-height: 26px; font-size: 20px; min-height: 26px; } form.blogger textarea { width: 500px; height: 150px; line-height: 26px; fontsize: 20px; } form.blogger input[type="submit"] { margin-left: 110px; width: 508px; lineheight: 26px; font-size: 20px; min-height: 26px; } form.blogger ul li { color: #ff0000; margin-bottom: 5px; }

Tenemos que hacerle saber a la aplicacin que queremos utilizar esta hoja de estilos. Podramos importar la hoja de estilos en la plantilla de contacto, pero, debido a que ms tarde o ms temprano, otras plantillas tambin utilizarn esta hoja de estilos, tiene sentido importarla en el diseo del BloggerBlogBundle que creamos en el captulo 1. Abre el diseo de src/Blogger/BlogBundle/Resources/views/layout.html.twig y sustityelo con el siguiente contenido:
{# src/Blogger/BlogBundle/Resources/views/base.html.twig #} {% extends '::base.html.twig' %} {% block stylesheets %} {{ parent() }} <link href="{{ asset('bundles/bloggerblog/css/blog.css') }}" type="text/css" rel="stylesheet" /> {% endblock %} {% block sidebar %} Sidebar content {% endblock %}

Puedes ver que hemos definido un bloque de hojas de estilo para sustituir el bloque de hojas de estilo definido en la plantilla padre. Sin embargo, es importante que tengas en cuenta la llamada al mtodo parent(). Este importar el contenido del bloque de las hojas de estilo en la plantilla padre ubicada en app/Resources/base.html.twig, lo cual nos permite aadir nuestra nueva hoja de estilos. Despus de todo, no deseamos reemplazar el estilo existente. A fin de que la funcin asset vincule correctamente los recursos, necesitamos copiar o vincular los recursos en el paquete al directorio web de la aplicacin. Esto lo puedes hacer con la siguiente orden:
$ php app/console assets:install web --symlink

Nota Si ests usando un sistema operativo que no es compatible con enlaces simblicos, tal como Windows, tendrs que olvidarte de la opcin de enlace simblicos de la siguiente manera.
php app/console assets:install web

Este mtodo en realidad va a copiar los recursos desde los directorios public de todos los paquetes al directorio web de la aplicacin. Puesto que los archivos se copian en realidad, ser necesario ejecutar esta tarea cada vez que realices un cambio a un recurso pblico en alguno de tus paquetes. Ahora bien, si actualizas la pgina del formulario de contacto estar bellamente decorada.

Truco Si bien la funcin asset proporciona la funcionalidad que necesitas para usar tus recursos, hay una mejor alternativa para esto. La biblioteca Assetic de Kris Wallsmith incluida de manera predeterminada en la edicin estndar de Symfony2. Esta biblioteca proporciona una gestin de activos ms all de las capacidades estndar de Symfony2. Assetic nos permite ejecutar filtros en los activos para combinarlos automticamente, minifyzarlos y comprimirlos con gzip.

Tambin puede ejecutar filtros de compresin en imgenes. Adems Assetic nos permite hacer referencia a los recursos directamente en los directorios public de los paquetes sin tener que ejecutar la tarea assets:install. Exploraremos el uso de Assetic en captulos posteriores.

Fallo en la presentacin
Si no pudiste reprimir tus ganas de enviar el formulario sers recibido con un error de Symfony2.

Este error nos est diciendo que no hay una ruta que coincida con /contact para el mtodo POST de HTTP. La ruta slo acepta peticiones GET y HEAD. Esto se debe a que configuramos la ruta con el requisito del mtodo GET. Actualicemos la ruta de contacto ubicada en src/Blogger/BlogBundle/Resources/config/routing.yml para permitir tambin las peticiones POST.
# src/Blogger/BlogBundle/Resources/config/routing.yml BloggerBlogBundle_contact: pattern: /contact defaults: { _controller: BloggerBlogBundle:Page:contact } requirements: _method: GET|POST

Truco Tal vez ests preguntndote por qu la ruta permitira el mtodo HEAD cuando slo hemos especificado GET. Esto es porque HEAD es una peticin GET, pero slo se devuelven las cabeceras HTTP. Ahora, cuando enves el formulario deber funcionar como se esperaba, aunque en realidad no esperes que haga mucho todava. La pgina slo te regresar al formulario de contacto.

Validadores
Los validadores de Symfony2 nos permiten realizar la tarea de validacin de datos. La validacin es una tarea comn cuando se trata de datos de formularios. Asimismo, la validacin se debe realizar en los datos antes de guardarlos en una base de datos. El validador de Symfony2 nos permite separar nuestra lgica de validacin fuera de los componentes que puedas utilizar, como el componente formulario o el componente de base de datos. Este enfoque significa que tenemos un conjunto de reglas de validacin para un objeto. Vamos a empezar actualizando la entidad Enquiry ubicada en src/Blogger/BlogBundle/Entity/Enquiry.php para especificar algunos validadores.

Asegrate de aadir las 5 nuevas declaraciones use en la parte superior del archivo:
<?php // src/Blogger/BlogBundle/Entity/Enquiry.php namespace Blogger\BlogBundle\Entity; use use use use use Symfony\Component\Validator\Mapping\ClassMetadata; Symfony\Component\Validator\Constraints\NotBlank; Symfony\Component\Validator\Constraints\Email; Symfony\Component\Validator\Constraints\MinLength; Symfony\Component\Validator\Constraints\MaxLength;

class Enquiry { // .. public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('name', new NotBlank()); $metadata->addPropertyConstraint('email', new Email()); $metadata->addPropertyConstraint('subject', new NotBlank()); $metadata->addPropertyConstraint('subject', new MaxLength(50)); } $metadata->addPropertyConstraint('body', new MinLength(50));

// .. }

Para definir los validadores debemos implementar el mtodo esttico loadValidatorMetadata. Este nos proporciona un objeto ClassMetadata. Podemos utilizar este objeto para establecer restricciones a las propiedades de nuestra entidad. La primera declaracin aplica la restriccin NotBlank a la propiedad name. El validador NotBlank es tan simple como suena, slo devolver true si el valor a comprobar no est vaco. A continuacin configuramos la validacin para la propiedad email. El servicio Validador de Symfony2 proporciona un validador para emails el cual incluso revisar los registros MX para garantizar que el dominio es vlido. En la propiedad subject deseamos establecer una restriccin NotBlank y una MaxLength. Puedes aplicar a una propiedad tantos validadores como desees. En los documentos de referencia de Symfony2 hay una lista completa de las restricciones de validacin. Tambin es posible crear restricciones de validacin personalizadas. Ahora, cuando enves el formulario de contacto, los datos presentados se pasan a travs de las restricciones de validacin. Intntalo escribiendo una direccin de correo electrnico incorrecta. Deberas ver un mensaje de error informndote que la direccin de correo electrnico no es vlida. Cada validador proporciona un mensaje predeterminado el cual puedes utilizar de ser necesario para remplazarlo. Para cambiar el mensaje producido por el validador de correo electrnico tienes que hacer lo siguiente:
$metadata->addPropertyConstraint('email', new Email(array( 'message' => 'symblog does not like invalid emails. Give me a real one!' )));

Truco Si ests utilizando un navegador compatible con HTML5 (lo cual es lo ms probable) se te

informar con los mensajes de HTML5 los cuales fuerzan determinadas restricciones. Esta es la validacin del lado del cliente y Symfony2 establecer las restricciones HTML5 apropiadas basndose en los metadatos de tu entidad. Esto lo puedes ver en el elemento de correo electrnico. La salida HTML es:
<input type="email" value="" required="required" name="contact[email]" id="contact_email">

Este ha utilizado uno de los nuevos tipos de campo de entrada HTML5, email y ha establecido el atributo necesario. La validacin del lado del cliente es grandiosa, ya que no requiere un viaje de vuelta al servidor para validar el formulario. Sin embargo, no debes usar solo la validacin del lado del cliente. Siempre debes validar los datos presentados en el servidor, debido a que es bastante fcil para un usuario malintencionado eludir la validacin del lado del cliente.

Enviando correo electrnico


Aunque nuestro formulario de contacto permitir a los usuarios enviar consultas, realmente nada sucede con ellos todava. Actualicemos el controlador para enviar un correo electrnico al administrador del blog. Symfony2 viene con la biblioteca Swift Mailer completa para enviar mensajes de correo electrnico. Swift Mailer es una biblioteca muy potente; Slo araaremos la superficie de lo que esta biblioteca puede realizar.

Configurando las opciones de Swift Mailer


Swift Mailer ya est configurado fuera de la caja para trabajar en la edicin estndar de Symfony2, sin embargo tenemos que configurar algunos parmetros relacionados a los mtodos de envo, y las credenciales. Abre el archivo de parmetros situado en app/parameters.yml y encuentra los ajustes con el prefijo mailer_.
mailer_transport="smtp" mailer_host="localhost" mailer_user="" mailer_password=""

Swift Mailer proporciona una serie de mtodos para enviar correos electrnicos, incluyendo el uso de un servidor SMTP, utilizando una instalacin local de sendmail, o incluso con una cuenta de GMail. Por simplicidad vamos a utilizar una cuenta de GMail. Actualiza los parmetros con lo siguiente, sustituyendo tu nombre de usuario y contrasea cuando sea necesario.
mailer_transport="gmail" mailer_encryption="ssl" mailer_auth_mode="login" mailer_host="smtp.gmail.com" mailer_user="your_username" mailer_password="your_password"

Advertencia Ten cuidado si ests usando un sistema de control de versiones (CVS) como Git para tu proyecto, especialmente si es repositorio de acceso pblico, puesto que tu nombre de usuario y contrasea de Gmail sern enviados al repositorio, y estarn disponibles para que cualquiera los vea. Debes asegurarte de agregar el archivo app/parameters.yml a la lista de ignorados de tu CVS. Un enfoque comn a este problema es aadir un sufijo al nombre del archivo que contiene informacin sensible, tal como app/parameters.yml con .dist. A continuacin, proporcionar los parmetros predeterminados para la configuracin de este archivo y agregar el archivo real, es decir, app/parameters.yml a la lista de ignorados por tu CVS. A continuacin, puedes desplegar el

archivo *.dist con tu proyecto y permitir al desarrollador a eliminar la extensin .dist y cumplimentar los ajustes necesarios.

Actualizando el controlador
Actualiza el controlador Page que se encuentra en src/Blogger/BlogBundle/Controller/PageController.php con el siguiente contenido:
// src/Blogger/BlogBundle/Controller/PageController.php public function contactAction() { // .. if ($form->isValid()) { $message = \Swift_Message::newInstance() ->setSubject('Contact enquiry from symblog') ->setFrom('enquiries@symblog.co.uk') ->setTo('email@email.com') ->setBody($this>renderView('BloggerBlogBundle:Page:contactEmail.txt.twig', array('enquiry' => $enquiry))); $this->get('mailer')->send($message); $this->get('session')->setFlash('blogger-notice', 'Your contact enquiry was successfully sent. Thank you!'); // Redirige - Esto es importante para prevenir que el usuario reenve // el formulario si actualiza la pgina return $this->redirect($this->generateUrl('BloggerBlogBundle_contact')); } // .. }

Cuando utilizas la biblioteca Swift Mailer para crear una instancia de Swift_Message, la cual puedes enviar como correo electrnico. Nota Debido a que la biblioteca Swift Mailer no utiliza espacios de nombres, es necesario prefijar la clase Swift Mailer con una \. Esto le indica a PHP que escape de nuevo al espacio global. Ser necesario que prefijes todas las clases y funciones que no estn en un espacio de nombres con una \. Si no colocas este prefijo antes de la clase Swift_Message, PHP debera buscar la clase en el espacio de nombres actual, que en este ejemplo es Blogger\BlogBundle\Controller, provocando que se lance un error. Tambin hemos establecido un mensaje flash en la sesin. Los mensajes flash son mensajes que persisten durante exactamente una peticin. Despus de eso, Symfony2 los limpia automticamente. El mensaje flash se mostrar en la plantilla de contacto para informar al usuario que se ha enviado la consulta. Como los mensajes flash slo persisten durante exactamente una peticin, son perfectos para notificar al usuario del xito de las acciones anteriores. Para mostrar el mensaje flash tenemos que actualizar la plantilla de contacto situada en src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig. Actualiza el contenido de la plantilla con lo siguiente:
{# src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig #} {# resto de la plantilla ... #}

<header> <h1>Contact symblog</h1> </header> {% if app.session.hasFlash('blogger-notice') %} <div class="blogger-notice"> {{ app.session.flash('blogger-notice') }} </div> {% endif %} <p>Want to contact symblog?</p> {# resto de la plantilla ... #}

Esto comprueba si hay un mensaje flash con el identificador 'blogger-notice' y, de existir, lo devuelve.

Registrando el correo electrnico del administrador


Symfony2 ofrece un sistema de configuracin que podemos utilizar para definir nuestros propios valores. Vamos a utilizar este sistema para establecer la direccin de correo electrnico del administrador del sitio web en lugar de la codificacin fija del controlador de arriba. De esa forma puedes volver a usar este valor en otros lugares, sin duplicar tu cdigo. Adems, cuando tu blog ha generado mucho trfico las consultas son demasiadas para que puedas procesarlas actualizando fcilmente la direccin de correo electrnico para transmitir los mensajes a tu asistente. Crea un nuevo archivo en src/Blogger/BlogBundle/Resources/config/config.yml y pega lo siguiente:
# src/Blogger/BlogBundle/Resources/config/config.yml parameters: # direccin de correo electrnico para contacto del Blogger blogger_blog.emails.contact_email: contact@email.com

Al definir parmetros es una buena prctica romper el nombre del parmetro en una serie de componentes. La primera parte debe ser una versin en minsculas del nombre del paquete con un subrayado para separar las palabras. En nuestro ejemplo, hemos transformado el paquete BloggerBlogBundle a `` blogger_blog``. El resto del nombre del parmetro puede contener cualquier nmero de partes separadas por un carcter de . (punto). Esto nos permite agrupar lgicamente los parmetros. A fin de que la aplicacin Symfony2 utilice los nuevos parmetros, tenemos que importar los ajustes en el archivo de configuracin principal de la aplicacin ubicado en app/config/config.yml. Para lograrlo actualiza la directiva imports en la parte superior del archivo a lo siguiente:
# app/config/config.yml imports: # .. aqu la importacin existente - { resource: @BloggerBlogBundle/Resources/config/config.yml }

La ruta de importacin es la ubicacin fsica del archivo en el disco. La directiva @BloggerBlogBundle se resolver en la ruta del BloggerBlogBundle que es src/Blogger/BlogBundle. Finalmente actualicemos la accin Contacto para utilizar el parmetro.
// src/Blogger/BlogBundle/Controller/PageController.php public function contactAction()

{ // .. if ($form->isValid()) { $message = \Swift_Message::newInstance() ->setSubject('Contact enquiry from symblog') ->setFrom('enquiries@symblog.co.uk') ->setTo($this->container>getParameter('blogger_blog.emails.contact_email')) ->setBody($this>renderView('BloggerBlogBundle:Page:contactEmail.txt.twig', array('enquiry' => $enquiry))); $this->get('mailer')->send($message); // .. } // .. }

Truco Puesto que el archivo de ajustes se importa en la parte superior del archivo de configuracin de la aplicacin, fcilmente puedes reemplazar cualquiera de los parmetros importados en la aplicacin. Por ejemplo, aadiendo lo siguiente en la parte inferior de app/config/config.yml para redefinir el valor establecido para el parmetro.
# app/config/config.yml parameters: # direccin de correo electrnico para contacto del Blogger blogger_blog.emails.contact_email: assistant@email.com

Esta personalizacin nos permite proporcionar parmetros predeterminados razonables para valores del paquete que la aplicacin puede sustituir. Nota Si bien es fcil crear parmetros de configuracin para el paquete utilizando este mtodo, Symfony2 tambin proporciona un mtodo en el que expones la configuracin semntica de un paquete. Vamos a explorar este mtodo ms adelante en la gua.

Creando la plantilla para un correo electrnico


El cuerpo del correo electrnico se determina en una plantilla para reproducirla. Crea esa plantilla en src/Blogger/BlogBundle/Resources/view/Page/contactEmail.txt.twig y agrgale lo siguiente:
{# src/Blogger/BlogBundle/Resources/view/Page/contactEmail.txt.twig #} A contact enquiry was made by {{ enquiry.name }} at {{ "now" | date("Y-m-d H:i") }}. Reply-To: {{ enquiry.email }} Subject: {{ enquiry.subject }} Body: {{ enquiry.body }}

El contenido del correo electrnico es slo la consulta que enva el usuario. Posiblemente tambin hayas notado que la extensin de esta plantilla es diferente a las otras plantillas que hemos creado. Esta utiliza la extensin .txt.twig. La primera parte de la extensin .txt especifica el formato del archivo a generar. Los formatos ms comunes aqu son,

.txt, .html, .css, .js, .xml y .json. La ltima parte de la extensin especifica el motor de plantillas a usar, en este caso, Twig. Una extensin de .php debera usar PHP para reproducir la plantilla. Ahora, cuando enves una consulta, se enviar un correo electrnico a la direccin indicada en el parmetro blogger_blog.emails.contact_email. Truco Symfony2 nos permite configurar el comportamiento de Swift Mailer, mientras que la biblioteca opera en diferentes entornos de Symfony2. Ya lo podemos ver en accin en el entorno test. Por omisin, la edicin estndar de Symfony2 configura el Swift Mailer para no enviar correos electrnicos cuando se ejecuta en el entorno test. Esto se establece en el archivo de configuracin de pruebas ubicado en app/config/config_test.yml.
# app/config/config_test.yml swiftmailer: disable_delivery: true

Tal vez sera til duplicar esta funcionalidad en el entorno dev. Despus de todo, durante el desarrollo, no deseas enviar accidentalmente un correo electrnico a la direccin de correo electrnico incorrecta. Para lograr esto, aade la configuracin anterior al archivo de configuracin dev ubicado en app/config/config_dev.yml. Tal vez te ests preguntando ahora cmo puedo probar el envo de los correos electrnicos y especficamente su contenido, en vista de que ya no sern entregados a una direccin de correo electrnico real? Symfony2 tiene una solucin para esto a travs de la barra de depuracin web. Cuando se le enva un correo electrnico aparecer un icono de notificacin de correo electrnico en la barra de depuracin web, el cual tiene toda la informacin sobre el correo electrnico que Swift Mailer habra entregado.

Si realizas una redireccin despus de enviar un correo electrnico, al igual que lo hicimos con el formulario de contacto, tendrs que establecer en true la opcin intercept_redirects en app/config/config_dev.yml para ver la notificacin de correo electrnico en la barra de depuracin web. En su lugar, podramos haber configurado Swift Mailer para enviar todos los correos electrnicos a una direccin de correo electrnico especfica en el entorno dev colocando la siguiente configuracin en el archivo de configuracin dev ubicado en app/config/config_dev.yml.
# app/config/config_dev.yml swiftmailer: delivery_address: development@symblog.dev

Conclusin
Hemos demostrado los conceptos detrs de la creacin de una de las partes ms fundamental de cualquier sitio web; los formularios. Symfony2 viene completo con una excelente biblioteca para validacin de formularios y nos permite separar la lgica de validacin del formulario para poder utilizarlo en otras partes de la aplicacin (tal como el modelo). Adems introdujimos los parmetros de configuracin personalizados que puede leer nuestra aplicacin.

A continuacin vamos a ver una gran parte de esta gua, el Modelo. Introduciremos Doctrine 2 y lo utilizaremos para definir el modelo del blog. Tambin vamos a crear la pgina para mostrar el blog y explorar el concepto de accesorios.

[Parte 3] El modelo del Blog: Usando Doctrine 2 y accesorios

Descripcin
En este captulo comenzaremos a explorar el modelo del blog. Implementaremos el modelo con el Object Relation Mapper (ORM o Asignador ObjetoRelacional) Doctrine 2. Doctrine 2 nos proporciona la persistencia de nuestros objetos PHP. Tambin proporciona un dialecto SQL llamado Doctrine Query Language (DQL o lenguaje de consulta doctrine). Adems de Doctrine 2, tambin introduciremos el concepto de datos de prueba. Los datos de prueba (en adelante: accesorios) son un mecanismo para llenar nuestras bases de datos de desarrollo y probar con datos de prueba adecuados. Al final de este captulo habrs definido el modelo del blog, actualizando la base de datos para reflejar el nuevo modelo, y creado algunos accesorios. Tambin habremos construido las bases para la pgina show del blog.

Doctrine 2: El modelo
Para que funcione nuestro blog necesitamos una manera de guardar los datos. Doctrine 2 proporciona un ORM diseado exactamente para este propsito. El ORM de Doctrine 2 se encuentra en lo alto de una potente Capa de abstraccin de base de datos que nos da la abstraccin de almacenamiento a travs del PDO de PHP. Esto nos permite utilizar una serie de distintos motores de almacenamiento, incluyendo MySQL, PostgreSQL y SQLite. Vamos a utilizar MySQL para nuestro motor de almacenamiento, pero, lo puedes sustituir por cualquier otro motor que desees. Truco Si no ests familiarizado con algn ORM, vamos a explicar su principio bsico. La definicin en Wikipedia dice: La asignacin objeto-relacional (ms conocida por su nombre en ingls, Object-Relational mapping, o sus siglas ORM, O/RM, y O/R mapping) es una tcnica de programacin para convertir datos entre sistemas de tipos incompatibles utilizando lenguajes de programacin orientados a objetos. Esto crea, en efecto, una base de datos de objetos virtual que se puede utilizar dentro del lenguaje de programacin. En la que las habilidades del ORM traducen desde datos de una base de datos relacional como MySQL en objetos PHP que podemos manipular. Esto nos permite encapsular la funcionalidad que necesitamos en una tabla dentro de una clase. Piensa en una tabla de usuarios, probablemente esta tenga campos como username, password, first_name, last_name, email y dob (siglas de day of birth o en Espaol fecha de nacimiento). Con un ORM esta se convierte en una clase con las propiedades username, password, first_name, etc., que nos permite llamar a mtodos tales como getUsername() y setPassword(). Los ORM van mucho ms all de esto, sin embargo, tambin son capaces de recuperar tablas relacionadas para nosotros, ya sea al mismo tiempo que recupera el objeto usuario, o de manera diferida en el futuro. Ahora, consideremos que nuestro usuario tiene algunos amigos con los que est relacionado. Para ello deberamos tener una tabla de amigos, almacenando la clave primaria de la tabla usuario dentro de ella. Usando el ORM ahora podramos hacer una llamada como $user->getFriends() para recuperar objetos de la tabla de amigos. Si eso no es suficiente, el ORM tambin se ocupa de guardarlos por lo tanto puedes crear objetos en PHP, llamar a un mtodo como save() y permitir que el ORM se ocupe de los detalles de en realidad persistir los datos en la base de datos. Debido a

que estamos usando el ORM de Doctrine 2, te familiarizars mucho ms con lo que es un ORM a medida que avancemos a travs de esta gua. Nota Si bien en esta gua utilizaremos el ORM de Doctrine 2, puedes optar por usar la biblioteca Object Document Mapper (ODM o Asignador ObjetoDocumento) de Doctrine 2. Hay una serie de variaciones de esta biblioteca incluyendo implementaciones de MongoDB y CouchDB. Ve la pgina del Proyecto Doctrine para ms informacin. Tambin hay un artculo en el recetario que explica cmo configurar el ODM con Symfony2.

La entidad Blog
Vamos a empezar creando la clase entidad Blog. Ya tuvimos nuestro primer encuentro con las entidades en el captulo anterior cuando creamos la entidad Enquiry. Puesto que el objetivo de una entidad consiste en almacenar datos, el sentido comn nos dicta que debemos usar una para representar una entrada del blog. Al definir una entidad no estamos diciendo que automticamente los datos se asignarn a la base de datos. Lo vimos con nuestra entidad Enquiry en la cual los datos contenidos en la entidad se enviaron por correo electrnico slo al administrador del sitio. Crea un nuevo archivo situado en src/Blogger/BlogBundle/Entity/blog.php y pega el siguiente contenido:
<?php // src/Blogger/BlogBundle/Entity/Blog.php namespace Blogger\BlogBundle\Entity; class Blog { protected $title; protected $author; protected $blog; protected $image; protected $tags; protected $comments; protected $created; protected $updated; }

Como puedes ver, esta es una simple clase PHP. No extiende a ninguna clase y no hay manera de acceder a sus propiedades. Cada una de las propiedades se ha declarado como protegida por lo que no puedes acceder a ellas cuando operas en un objeto de esta clase. Podramos declarar los captadores y definidores de estos atributos nosotros mismos, pero Doctrine 2 proporciona una tarea para hacerlo. Despus de todo, la escritura de mtodos de acceso no es la ms emocionante de las tareas de codificacin. Antes de poder ejecutar esta tarea, le tenemos que informar a Doctrine 2 cmo debe asignar la entidad blog a la base de datos. Tal informacin se especifica en forma de metadatos utilizando las asignaciones de Doctrine 2. Puedes especificar los metadatos en una serie de formatos, incluyendo YAML, PHP, XML y Anotaciones. En esta ocasin usaremos anotaciones. Es importante

sealar que no es necesario persistir todas las propiedades de la entidad, por lo tanto no debers proporcionar metadatos para estas. Esto nos da la flexibilidad de elegir slo las propiedades que necesitamos asigne Doctrine 2 a la base de datos. Remplaza el contenido de la clase entidad blog situada en src/Blogger/BlogBundle/Entity/blog.php con lo siguiente:
<?php // src/Blogger/BlogBundle/Entity/Blog.php namespace Blogger\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="blog") */ class Blog { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @ORM\Column(type="string") */ protected $title; /** * @ORM\Column(type="string", length=100) */ protected $author; /** * @ORM\Column(type="text") */ protected $blog; /** * @ORM\Column(type="string", length="20") */ protected $image; /** * @ORM\Column(type="text") */ protected $tags; protected $comments; /** * @ORM\Column(type="datetime") */ protected $created; /** * @ORM\Column(type="datetime") */ protected $updated;

En primer lugar hemos importado y apodado el espacio de nombres del ORM de Doctrine 2. Este nos permite utilizar anotaciones para describir los metadatos de la entidad. Los metadatos proporcionan informacin sobre cmo se deben asignar las propiedades a la base de datos. Truco Hemos utilizado un pequeo subconjunto de los tipos de asignacin proporcionados por Doctrine 2. Puedes encontrar una lista completa de los tipos de asignacin en el sitio web de Doctrine 2. Ms adelante presentaremos otros tipos de asignacin. Si observaste atentamente el fragmento de cdigo anterior te habrs dado cuenta de que la propiedad $comments no tiene metadatos. Esto es a propsito y se debe a que no necesitamos guardarlo, nicamente proporciona una coleccin de comentarios relacionados con una entrada del blog. Si piensas en esto sin tomar en cuenta la base de datos tiene sentido. Los siguientes fragmentos de cdigo lo demuestran.
// Crea un objeto blog. $blog = new Blog(); $blog->setTitle("symblog - A Symfony2 Tutorial"); $blog->setAuthor("dsyph3r"); $blog->setBlog("symblog is a fully featured blogging website ..."); // Crea un comentario y lo aade a nuestro blog $comment = new Comment(); $comment->setComment("Symfony2 rocks!"); $blog->addComment($comment);

El fragmento anterior muestra el comportamiento normal que te gustara entre un blog y la clase comentario. Internamente, podramos haber implementado el mtodo $blog>addComment() de la siguiente manera.
class Blog { protected $comments = array(); public function addComment(Comment $comment) { $this->comments[] = $comment; } }

El mtodo addComment slo aade un nuevo objeto comentario a la propiedad $comnents del blog. Recuperar los comentarios tambin sera muy sencillo.
class Blog { protected $comments = array(); public function getComments() { return $this->comments; }

Como puedes ver la propiedad $comments es slo una lista de objetos Comentario. Doctrine 2 no cambia cmo funciona esto. Doctrine 2 ser capaz de llenar automticamente la propiedad $comments con objetos relacionados con el objeto blog. Ahora que hemos dicho cmo asigna Doctrine 2 las propiedades a la entidad, podemos generar los mtodos de acceso usando la siguiente orden:

$ php app/console doctrine:generate:entities Blogger

Notars que la entidad Blog se ha actualizado con mtodos de acceso. Cada vez que hagas algn cambio en los metadatos del ORM a las clases de nuestra entidad, puedes ejecutar esta orden para generar cualquier captador adicional. Esta orden no har modificaciones a los mtodos de acceso existentes en la entidad, por lo tanto tus mtodos de acceso existentes nunca se van a remplazar con esta orden. Esto es importante ya que ms tarde puedes personalizar algunos de los mtodos de acceso predeterminados. Truco Aunque hemos utilizado anotaciones en nuestra entidad, es posible convertir la informacin de asignacin a otros formatos de asignacin apoyados usando la tarea doctrine:mapping:convert. Por ejemplo, la siguiente orden convertir las asignaciones en la entidad de arriba al formato YAML.
$ php app/console doctrine:mapping:convert --namespace="Blogger\BlogBundle\Entity\Blog" yaml src/Blogger/BlogBundle/Resources/config/doctrine

Esto crear un archivo ubicado en src/Blogger/BlogBundle/Resources/config/doctrine/Blogger.BlogBundl e.Entity.Blog.orm.yml que contendr las asignaciones de la entidad blog en formato yaml.

La base de datos
Creando la base de datos Si has seguido el captulo 1 de esta gua, deberas haber utilizado el configurador web para establecer la configuracin de la base de datos. Si no, actualiza las opciones database_* en el archivo de parmetros situado en app/parameters.yml. Es tiempo de crear la base de datos usando otra tarea de Doctrine 2. Esta tarea slo crea la base de datos, esta no crea las tablas dentro de la base de datos. Si existe una base de datos con el mismo nombre, la tarea generar un error y la base de datos existente quedar intacta.
$ php app/console doctrine:database:create

Ahora estamos listos para crear la representacin de la entidad Blog en la base de datos. Hay dos maneras en que podemos lograrlo. Podemos utilizar la tarea schema de Doctrine 2 para actualizar la base de datos o podemos usar las ms potentes migraciones de Doctrine 2. Por ahora vamos a utilizar la tarea schema. Veremos las migraciones de Doctrine en el siguiente captulo. Creando la tabla blog Para crear la tabla blog en nuestra base de datos, puedes ejecutar la siguiente tarea de Doctrine.
$ php app/console doctrine:schema:create

Esto ejecutar el cdigo SQL necesario para generar el esquema de base de datos para la entidad blog. Adems le puedes pasar la opcin --dump-sql de la tarea para volcar el SQL en lugar de ejecutarlo contra la base de datos. Si ves tu base de datos notars que la tabla blog se ha creado, con los campos que configuraste en la informacin de asignacin. Truco

Hemos utilizado una serie de tareas de la lnea de ordenes de Symfony2, y en verdadero formato de lnea de ordenes, todas las tareas proporcionan ayuda, especificando la opcin --help. Para ver los detalles de la ayuda para la tarea doctrine:schema:create, ejecuta la siguiente orden:
$ php app/console doctrine:schema:create --help

La informacin de ayuda ser la salida que muestra el uso, y opciones disponibles. La mayora de las tareas vienen con una serie de opciones que puedes configurar para personalizar el funcionamiento de la tarea.

Integrando el modelo con la vista: Mostrando una entrada del blog


Ahora hemos creado la entidad blog, y actualizamos la base de datos para reflejarlo, podemos empezar a integrar el modelo con la vista. Vamos a empezar construyendo la pgina para mostrar nuestro blog.

La ruta para mostrar el Blog


Empecemos creando una ruta para la accin show que mostrar un blog. Un blog se identifica por su ID nico, por lo tanto ese ID tendr que estar presente en la URL. Actualiza el archivo de enrutado BloggerBlogBundle ubicado en src/Blogger/BlogBundle/Resources/config/routing.yml con lo siguiente:
# src/Blogger/BlogBundle/Resources/config/routing.yml BloggerBlogBundle_blog_show: pattern: /{id} defaults: { _controller: BloggerBlogBundle:Blog:show } requirements: _method: GET id: \d+

Debido a que el ID del blog debe estar presente en la URL, hemos especificado un marcador de posicin id. Esto significa que las direcciones URL similares a http://symblog.co.uk/1 y http://symblog.co.uk/my-blog coincidirn con esta ruta. Sin embargo, sabemos que el identificador del blog debe ser un entero (definido de esta manera en las asignaciones de la entidad) por lo tanto podemos agregar una restriccin especificando que esta ruta slo concordar cuando el parmetro id contenga un nmero entero. Esto se logra con el requisito id: \d+ de la ruta. Ahora slo el primer ejemplo de URL anterior coincidira, http://symblog.co.uk/my-blog ya no coincide con esta ruta. Tambin puedes ver que una ruta coincidente ejecutar la accin show del controlador BloggerBlogBundle del Blog. Este controlador an no lo hemos creado.

El controlador de la accin Show


El pegamento entre el modelo y la vista es el controlador, por lo tanto aqu es donde vamos a empezar a crear la pgina show. Podramos aadir la accin show a nuestro controlador Page existente, pero como esta pgina se refiere a la exhibicin de una entidad blog sera ms adecuado crear su propio controlador en el blog. Crea un nuevo archivo situado en src/Blogger/BlogBundle/Controller/BlogController.php con el siguiente contenido:
<?php // src/Blogger/BlogBundle/Controller/BlogController.php

namespace Blogger\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; /** * Controlador del Blog. */ class BlogController extends Controller { /** * Muestra una entrada del blog */ public function showAction($id) { $em = $this->getDoctrine()->getEntityManager(); $blog = $em->getRepository('BloggerBlogBundle:Blog')->find($id); if (!$blog) { throw $this->createNotFoundException('Unable to find Blog post.'); } return $this->render('BloggerBlogBundle:Blog:show.html.twig', array( 'blog' => $blog, ));

} }

Hemos creado un nuevo controlador para la entidad Blog y definimos la accin show. Debido a que especificamos un parmetro id en la regla de enrutado en el BloggerBlogBundle_blog_show, este se pasar como argumento del mtodo showAction. Si hubiramos especificado ms parmetros en la regla de enrutado, tambin se pasaran como argumentos independientes. Truco Las acciones de controlador tambin pasarn un objeto Symfony\Component\HttpFoundation\Request si lo especificas como un parmetro. Este puede ser til cuando se trata con formularios. Ya hemos utilizado un formulario en el captulo 2, pero no utilizamos este mtodo ya que utilizamos un mtodo ayudante de Symfony\Bundle\FrameworkBundle\Controller\Controller as:
// src/Blogger/BlogBundle/Controller/PageController.php public function contactAction() { // .. $request = $this->getRequest(); }

En su lugar lo podramos haber escrito de la siguiente manera:


// src/Blogger/BlogBundle/Controller/PageController.php use Symfony\Component\HttpFoundation\Request; public function contactAction(Request $request) { // .. }

Ambos mtodos consiguen el mismo resultado. Si tu controlador no extendiera la clase ayudante Symfony\Bundle\FrameworkBundle\Controller\Controller no podras utilizar el primer mtodo. Lo siguiente que necesitamos es recuperar la entidad Blog desde la base de datos. En primer lugar, utilizaremos otro mtodo ayudante de la clase Symfony\Bundle\FrameworkBundle\Controller\Controller para obtener el Entity Manager (en adelante: gestor de entidades) de Doctrine 2. El trabajo del gestor de entidades es manejar la persistencia y recuperacin de objetos hacia y desde la base de datos. Por lo tanto, utilizaremos el objeto EntityManager para obtener el repositorio de Doctrine 2 para la entidad BloggerBlogBundle:Blog. La sintaxis especificada aqu es simplemente un atajo que puedes utilizar con Doctrine 2 en lugar de especificar el nombre completo de la entidad, es decir, Blogger\BlogBundle\Entity\Blog. Con el objeto repositorio llamamos al mtodo find() pasndole el argumento $id. Este mtodo recuperar el objeto por medio de su clave primaria. Finalmente comprobamos que se ha encontrado la entidad, y pasamos esta entidad a la vista. Si no se encuentra una entidad lanzamos uns createNotFoundException. La cual en ltima instancia, va a generar una respuesta 404 No se ha encontrado. Truco El objeto repositorio te da acceso a una serie de tiles mtodos ayudantes, incluyendo:
// devuelve entidades en las que 'author' coincide con 'nacho' $em->getRepository('BloggerBlogBundle:Blog')->findBy(array('author' => 'nacho') ); // Devuelve una entidad en la que 'slug' coincide con 'symblog-tutorial' $em->getRepository('BloggerBlogBundle:Blog')->findOneBySlug('symblog-tutorial');

Vamos a crear nuestras propias clases Repositorio personalizadas en el siguiente captulo, cuando requeriremos de consultas ms complejas.

La vista
Ahora que hemos construido la accin show para el controlador Blog nos podemos enfocar en mostrar la entidad Blog. Como especifica la accin show, se debe reproducir la plantilla BloggerBlogBundle:Blog:show.html.twig. Vamos a crear esta plantilla ubicada en src/Blogger/BlogBundle/Resouces/views/Blog/show.html.twig con en el siguiente contenido:
{# src/Blogger/BlogBundle/Resouces/views/Blog/show.html.twig #} {% extends 'BloggerBlogBundle::base.html.twig' %} {% block title %}{{ blog.title }}{% endblock %} {% block body %} <article class="blog"> <header> <div class="date"><time datetime="{{ blog.created| date('c') }}">{{ blog.created|date('l, F j, Y') }}</time></div> <h2>{{ blog.title }}</h2> </header> <img src="{{ asset(['images/', blog.image]|join) }}" alt="{{ blog.title }} image not found" class="large" /> <div> <p>{{ blog.blog }}</p> </div>

</article> {% endblock %}

Como es de esperar empezamos extendiendo el diseo principal de BloggerBlogBundle. A continuacin remplazamos el ttulo de la pgina con el ttulo del blog. Esto ser til para el SEO puesto que el ttulo de la pgina del blog es ms descriptivo que el ttulo establecido por omisin. Por ltimo vamos a sustituir el bloque body para mostrar el contenido de la entidad Blog. Aqu, de nuevo utilizamos la funcin asset para reproducir la imagen del blog. Las imgenes del blog se deben colocar en el directorio web/images. CSS A fin de garantizar que el blog muestre una bella pgina, le tenemos que aadir un poco de estilo. Actualiza la hoja de estilos situada en src/Blogger/BlogBundle/Resouces/public/css/blog.css con lo siguiente:
.date { margin-bottom: 20px; border-bottom: 1px solid #ccc; font-size: 24px; color: #666; line-height: 30px } .blog { margin-bottom: 20px; } .blog img { width: 190px; float: left; padding: 5px; border: 1px solid #ccc; margin: 0 10px 10px 0; } .blog .meta { clear: left; margin-bottom: 20px; } .blog .snippet p.continue { margin-bottom: 0; text-align: right; } .blog .meta { font-style: italic; font-size: 12px; color: #666; } .blog .meta p { margin-bottom: 5px; line-height: 1.2em; } .blog img.large { width: 300px; min-height: 165px; }

Nota Si no ests utilizando el mtodo de enlaces simblicos para hacer referencia a los activos del paquete en el directorio web, ahora debes volver a ejecutar la tarea de instalacin de activos para copiar los cambios en tu CSS.
$ php app/console assets:install web

Debido a que hemos construido el controlador y la vista para la accin show echemos un vistazo a la pgina para ver su apariencia. Apunta tu navegador a http://symblog.dev/app_dev.php/1. No es la pgina que estabas esperando?

Symfony2 gener una respuesta 404 No se ha encontrado. Esto es porque no tenemos datos en nuestra base de datos, por lo tanto no se pudo encontrar alguna entidad coincidente con el id igual a 1. Podras simplemente insertar una fila en la tabla blog de tu base de datos, pero ahora vamos a utilizar un mtodo mucho mejor; Datos de prueba.

Datos de prueba
Podemos usar accesorios para poblar la base de datos con algunas muestras/datos de prueba. Para ello utilizaremos la extensin y paquete Fixtures de Doctrine. La extensin y paquete Fixtures de Doctrine no viene con la edicin estndar de Symfony2, los tenemos que instalar manualmente. Afortunadamente, esta es una tarea muy sencilla. Abre el archivo deps (por dependencias) ubicado en la raz de tu proyecto y aade la extensin fixtures de Doctrine y el paquete de la siguiente manera:
[doctrine-fixtures] git=http://github.com/doctrine/data-fixtures.git [DoctrineFixturesBundle] git=http://github.com/symfony/DoctrineFixturesBundle.git target=/bundles/Symfony/Bundle/DoctrineFixturesBundle

En seguida actualiza tus proveedores para reflejar estos cambios.


$ php bin/vendors install

Esto descargar la versin ms reciente de cada uno de los repositorios de Github y los instalar en el lugar deseado. Nota Si ests usando una mquina que no tiene instalado Git tendrs que descargar e instalar manualmente la extensin y el paquete.

Extensin doctrine-fixtures: Descarga la versin actual de la extensin data-fixtures desde GitHub y expande su contenido en vendor/doctrine-fixtures. DoctrineFixturesBundle: Descarga la versin actual del paquete DoctrineFixturesBundle desde GitHub y la expande su contenido en vendor/bundles/Symfony/Bundle/DoctrineFixturesBundle. A continuacin actualiza el archivo app/autoloader.php para registrar el nuevo espacio de nombres. Dado que DataFixtures tambin est en el espacio de nombres Doctrine\Common esto debe estar por encima de la directiva Doctrine\Common existente puesto que especifica una nueva ruta. Los espacios de nombres son revisados de arriba hacia abajo por lo tanto los espacios de nombres ms especficos se deben registrar antes de los menos especficos.
// app/autoloader.php // ... $loader->registerNamespaces(array( // ... 'Doctrine\\Common\\DataFixtures' fixtures/lib', 'Doctrine\\Common' // ... ));

=> __DIR__.'/../vendor/doctrine=> __DIR__.'/../vendor/doctrine-common/lib',

Ahora vamos a registrar el DoctrineFixturesBundle en el ncleo situado en app/AppKernel.php:


// app/AppKernel.php public function registerBundles() { $bundles = array( // ... new Symfony\Bundle\DoctrineFixturesBundle\DoctrineFixturesBundle(), // ... ); // ... }

Accesorios para el Blog


Ahora estamos listos para definir algunos accesorios para nuestros blogs. Crea un archivo de accesorios en src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php y adele el siguiente contenido:
<?php // src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php namespace Blogger\BlogBundle\DataFixtures\ORM; use Doctrine\Common\DataFixtures\FixtureInterface; use Blogger\BlogBundle\Entity\Blog; class BlogFixtures implements FixtureInterface { public function load($manager) { $blog1 = new Blog(); $blog1->setTitle('A day with Symfony2'); $blog1->setBlog('Lorem ipsum dolor sit amet, consectetur adipiscing eletra electrify denim vel ports.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi ut velocity magna. Etiam vehicula nunc non leo hendrerit

commodo. Vestibulum vulputate mauris eget erat congue dapibus imperdiet justo scelerisque. Nulla consectetur tempus nisl vitae viverra. Cras el mauris eget erat congue dapibus imperdiet justo scelerisque. Nulla consectetur tempus nisl vitae viverra. Cras elementum molestie vestibulum. Morbi id quam nisl. Praesent hendrerit, orci sed elementum lobortis, justo mauris lacinia libero, non facilisis purus ipsum non mi. Aliquam sollicitudin, augue id vestibulum iaculis, sem lectus convallis nunc, vel scelerisque lorem tortor ac nunc. Donec pharetra eleifend enim vel porta.'); $blog1->setImage('beach.jpg'); $blog1->setAuthor('dsyph3r'); $blog1->setTags('symfony2, php, paradise, symblog'); $blog1->setCreated(new \DateTime()); $blog1->setUpdated($blog1->getCreated()); $manager->persist($blog1); $blog2 = new Blog(); $blog2->setTitle('The pool on the roof must have a leak'); $blog2->setBlog('Vestibulum vulputate mauris eget erat congue dapibus imperdiet justo scelerisque. Na. Cras elementum molestie vestibulum. Morbi id quam nisl. Praesent hendrerit, orci sed elementum lobortis.'); $blog2->setImage('pool_leak.jpg'); $blog2->setAuthor('Zero Cool'); $blog2->setTags('pool, leaky, hacked, movie, hacking, symblog'); $blog2->setCreated(new \DateTime("2011-07-23 06:12:33")); $blog2->setUpdated($blog2->getCreated()); $manager->persist($blog2); $blog3 = new Blog(); $blog3->setTitle('Misdirection. What the eyes see and the ears hear, the mind believes'); $blog3->setBlog('Lorem ipsumvehicula nunc non leo hendrerit commodo. Vestibulum vulputate mauris eget erat congue dapibus imperdiet justo scelerisque.'); $blog3->setImage('misdirection.jpg'); $blog3->setAuthor('Gabriel'); $blog3->setTags('misdirection, magic, movie, hacking, symblog'); $blog3->setCreated(new \DateTime("2011-07-16 16:14:06")); $blog3->setUpdated($blog3->getCreated()); $manager->persist($blog3); $blog4 = new Blog(); $blog4->setTitle('The grid - A digital frontier'); $blog4->setBlog('Lorem commodo. Vestibulum vulputate mauris eget erat congue dapibus imperdiet justo scelerisque. Nulla consectetur tempus nisl vitae viverra.'); $blog4->setImage('the_grid.jpg'); $blog4->setAuthor('Kevin Flynn'); $blog4->setTags('grid, daftpunk, movie, symblog'); $blog4->setCreated(new \DateTime("2011-06-02 18:54:12")); $blog4->setUpdated($blog4->getCreated()); $manager->persist($blog4); $blog5 = new Blog(); $blog5->setTitle('You\'re either a one or a zero. Alive or dead'); $blog5->setBlog('Lorem ipsum dolor sit amet, consectetur adipiscing elittibulum vulputate mauris eget erat congue dapibus imperdiet justo scelerisque.'); $blog5->setImage('one_or_zero.jpg'); $blog5->setAuthor('Gary Winston'); $blog5->setTags('binary, one, zero, alive, dead, !trusting, movie, symblog'); $blog5->setCreated(new \DateTime("2011-04-25 15:34:18"));

$blog5->setUpdated($blog5->getCreated()); $manager->persist($blog5); } } $manager->flush();

El archivo de accesorios muestra una serie de caractersticas importantes cuando se utiliza Doctrine 2, incluyendo la forma en que se persisten las entidades en la base de datos. Vamos a ver cmo podemos crear una entrada en el blog.
$blog1 = new Blog(); $blog1->setTitle('A day in paradise - A day with Symfony2'); $blog1->setBlog('Lorem ipsum dolor sit d us imperdiet justo scelerisque. Nulla consectetur...'); $blog1->setImage('beach.jpg'); $blog1->setAuthor('dsyph3r'); $blog1->setTags('symfony2, php, paradise, symblog'); $blog1->setCreated(new \DateTime()); $blog1->setUpdated($this->getCreated()); $manager->persist($blog1); // .. $manager->flush();

Comenzamos creando un objeto blog y establecimos ciertos valores a sus propiedades. En este punto Doctrine 2 no sabe nada del objeto Entidad. Es nicamente cuando hacemos una llamada a $manager->persist($blog1) que se informa a Doctrine 2 que inicie la gestin de este objeto entidad. Aqu, el objeto $manager es una instancia del objeto EntityManager que vimos anteriormente para recuperar entidades desde la base de datos. Es importante sealar que si bien Doctrine 2 ya est al tanto del objeto entidad, an no lo ha guardado en la base de datos. Para ello se requiere una llamada a $manager->flush(). El mtodo flush provoca que Doctrine 2 realmente interacte con la base de datos y lleve a cabo las acciones en todas las entidades que est gestionando. Para un mejor rendimiento deberas agrupar las operaciones de Doctrine 2 y limpiarlas todas en conjunto de una sola vez. As es como lo hicimos en nuestros accesorios. Creamos cada entidad, pidiendo a Doctrine 2 que las gestionara y luego, al final, limpiamos todas las operaciones.

Cargando los accesorios


Ahora estamos listos para cargar los accesorios a la base de datos.
$ php app/console doctrine:fixtures:load

Si echamos un vistazo a la pgina show en http://symblog.dev/app_dev.php/1 deberas ver el blog apropiado.

Intenta cambiar el parmetro id en la URL a 2. Deberas ver la siguiente entrada del blog. Si echas un vistazo a la URL http://symblog.dev/app_dev.php/100 deberas ver que se ha lanzado una excepcin 404 No se ha encontrado. Esperbamos que no hubiera una entidad Blog con un id de 100. Ahora intenta con la URL http://symblog.dev/app_dev.php/symfony2-blog. Por qu no obtuvimos una excepcin 404 No se ha encontrado? Esto se debe a que la accin show nunca se ejecuta. La URL no coincide con ninguna ruta en la aplicacin debido al requisito \d+ que pusimos en la ruta BloggerBlogBundle_blog_show. Es por eso que ves una excepcin No hay ruta para "GET /symfony2-blog".

Marcas de tiempo
Finalmente en este captulo vamos a ver las dos propiedades de fecha y hora en la entidad Blog; created y updated. La funcionalidad para estas dos propiedades comnmente se conoce como el comportamiento Timestampable (en adelante: autofechable). Estas propiedades mantienen la hora y fecha en que se cre el blog y la fecha y hora de la ms reciente actualizacin del blog. Puesto que no queremos tener que configurar manualmente estos campos cada vez que creamos o actualizamos un blog, podemos utilizar dos ayudantes de Doctrine para ello. Doctrine 2 viene con un Sistema de eventos el cual proporciona retrollamadas en el ciclo de vida. Podemos utilizar estos eventos retrollamados para que al registrar nuestras entidades notifiquen los eventos durante la vida til de la entidad. Algunos ejemplos de eventos que podemos notificar incluyen a antes de que se produzca una actualizacin, despus de un guardado exitoso y despus de ocurrida una eliminacin. Con el fin de utilizar las retrollamadas del ciclo de vida de nuestra entidad es necesario registrar la entidad para ello. Esto se hace usando los metadatos de la entidad. Actualiza la entidad blog ubicada en src/Blogger/BlogBundle/Entity/Blog.php con lo siguiente:
<?php // src/Blogger/BlogBundle/Entity/Blog.php

// .. /** * @ORM\Entity * @ORM\Table(name="blog") * @ORM\HasLifecycleCallbacks() */ class Blog { // .. }

Ahora vamos a aadir un mtodo en la entidad Blog para registrar el evento preUpdate. Tambin vamos a aadir un constructor para establecer los valores predeterminado para las propiedades created y updated.
<?php // src/Blogger/BlogBundle/Entity/Blog.php // .. /** * @ORM\Entity * @ORM\Table(name="blog") * @ORM\HasLifecycleCallbacks() */ class Blog { // .. public function __construct() { $this->setCreated(new \DateTime()); $this->setUpdated(new \DateTime()); } /** * @ORM\preUpdate */ public function setUpdatedValue() { $this->setUpdated(new \DateTime()); } // .. }

Registramos la entidad Blog para que sea notificada cuando ocurra el evento preUpdate para establecer el valor de updated. Ahora, cuando se vuelva a ejecutar la tarea para cargar accesorios notars que las propiedades created y updated se ajustan automticamente. Truco Debido a que las propiedades autofechables son un requisito comn para las entidades, hay un paquete disponible que las apoya. El StofDoctrineExtensionsBundle ofrece una serie de tiles extensiones para Doctrine 2 incluyendo Sluggable, autofechable, y ordenable. Ms adelante en la gua, veremos cmo integrar en nuestra aplicacin este paquete. No te reprimas puedes explorar un captulo sobre este tema en el recetario.

Conclusin
Hemos cubierto una serie de conceptos para hacer frente a los modelos de Doctrine 2. Tambin vimos la definicin de accesorios que nos proporciona una forma fcil de obtener datos adecuados para probar mientras desarrollamos nuestra aplicacin. A continuacin vamos a ver cmo extender ms el modelo aadiendo la entidad comentario. Vamos a empezar a construir la pgina inicial y crearemos un Repositorio personalizado para ello. Tambin vamos a introducir el concepto de Migraciones de Doctrine y cmo interactuar con formularios en Doctrine 2 para permitir que se aadan comentarios a un blog.

[Parte 4] El modelo de comentarios: Agregando comentarios, repositorios y migraciones de Doctrine

Descripcin
En este captulo construiremos sobre el modelo del blog que definimos en el captulo anterior. Vamos a crear el modelo para los Comentarios, el cual cmo su nombre indica, se encargar de los comentarios de cada blog. Te presentaremos la creacin de relaciones entre modelos, cmo puede un blog contener muchos Comentarios. Vamos a utilizar el Generador de consultas de Doctrine 2 y las clases Repositorio de Doctrine para recuperar entidades desde la base de datos. Tambin exploraremos el concepto de Migraciones de Doctrine 2 que ofrece una forma programtica para implementar cambios en la base de datos. Al final de este captulo habrs creado el modelo del Comentario y lo habrs vinculado con el modelo del Blog. Adems crearemos la pgina Inicial, y proporcionaremos la posibilidad de que los usuarios enven comentarios para un blog.

La pgina Inicial
Vamos a comenzar este captulo, construyendo la pgina inicial de la aplicacin. Al estilo de un verdadero blogger mostraremos fragmentos de cada entrada del blog, ordenados del ms reciente al ms antiguo. La entrada completa del blog estar disponible a travs de enlaces a la pgina que muestra el blog. Puesto que ya hemos construido la ruta, el controlador y la vista de la pgina, simplemente vamos a actualizarlas.

Recuperando los blogs: Consultando el modelo


Con el fin de mostrar los blogs, los tenemos que recuperar desde la base de datos. Doctrine 2 proporciona el Lenguaje de consulta Doctrine (DQL) y un Generador de consultas para lograr esto (tambin puedes ejecutar SQL crudo a travs de Doctrine 2, pero este mtodo no se recomienda, ya que quita la abstraccin de bases de datos que nos brinda Doctrine 2). Vamos a utilizar el Generador de consultas, ya que nos proporciona una agradable forma orientada a objetos, para generar DQL, que puedes utilizar para consultar la base de datos. Actualicemos la accin index del controlador Page que se encuentra en src/Blogger/BlogBundle/Controller/PageController.php para recuperar los blogs de la base de datos.
// src/Blogger/BlogBundle/Controller/PageController.php class PageController extends Controller { public function indexAction() { $em = $this->getDoctrine()

->getEntityManager(); $blogs = $em->createQueryBuilder() ->select('b') ->from('BloggerBlogBundle:Blog', 'b') ->addOrderBy('b.created', 'DESC') ->getQuery() ->getResult(); return $this->render('BloggerBlogBundle:Page:index.html.twig', array( 'blogs' => $blogs ));

} }

// ..

Empezamos consiguiendo una instancia del QueryBuilder desde el EntityManager. Esto nos permite empezar a construir la consulta con los muchos mtodos que ofrece el Generador de consultas. La lista completa de mtodos disponibles est en la documentacin del Generador de consultas. Un buen lugar para comenzar es con los mtodos ayudantes. Estos son los mtodos que usaremos, como select(), from() y addOrderBy(). Al igual que con las interacciones previas con Doctrine 2, podemos utilizar la notacin abreviada para hacer referencia a la entidad Blog a travs del BloggerBlogBundle:Blog (recuerda que esto hace lo mismo que Blogger\BlogBundle\Entity\Blog). Cuando hayas terminado de especificar los criterios para la consulta, llamamos al mtodo getQuery() el cual devuelve una instancia de DQL. No podremos obtener resultados desde el objeto QueryBuilder, siempre lo tenemos que convertir en una instancia de DQL primero. La instancia de DQL ofrece el mtodo getResult() que devuelve una coleccin de entidades Blog. Ms adelante veremos que la instancia del DQL tiene una serie de mtodos para devolver su resultado, tal como getSingleResult() y getArrayResult(). La vista Ahora tenemos una coleccin de entidades Blog y necesitamos mostrarlas. Sustituye el contenido de la plantilla de la pgina Inicial ubicada en src/Blogger/BlogBundle/Resources/views/Page/index.html.twig con lo siguiente:
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #} {% extends 'BloggerBlogBundle::base.html.twig' %} {% block body %} {% for blog in blogs %} <article class="blog"> <div class="date"><time datetime="{{ blog.created| date('c') }}">{{ blog.created|date('l, F j, Y') }}</time></div> <header> <h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}">{{ blog.title }}</a></h2> </header> <img src="{{ asset(['images/', blog.image]|join) }}" /> <div class="snippet"> <p>{{ blog.blog(500) }}</p> <p class="continue"><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}">Continue reading...</a></p>

</div> <footer class="meta"> <p>Comments: -</p> <p>Posted by <span class="highlight">{{blog.author}}</span> at {{ blog.created|date('h:iA') }}</p> <p>Tags: <span class="highlight">{{ blog.tags }}</span></p> </footer> </article> {% else %} <p>There are no blog entries for symblog</p> {% endfor %} {% endblock %}

Aqu te presentamos una de las estructuras de control de Twig, la estructura for..else..endfor. Si no has utilizado un motor de plantillas antes,probablemente te sea familiar el siguiente fragmento de cdigo PHP.
<?php if (count($blogs)): ?> <?php foreach ($blogs as $blog): ?> <h1><?php echo $blog->getTitle() ?><?h1> <!-- resto del contenido --> <?php endforeach ?> <?php else: ?> <p>There are no blog entries</p> <?php endif ?>

La estructura de control for..else..endfor es una manera mucho ms limpia de lograr esta tarea. La mayora del cdigo en la plantilla de la pgina Inicial atae a la reproduccin de la informacin del blog en HTML. Sin embargo, hay algunas cosas que es necesario tener en cuenta. En primer lugar, usamos la funcin path de Twig para generar las rutas de la pgina show del blog. Puesto que la pgina show del blog requiere que est presente un id de blog en la URL, lo tenemos que pasar como argumento de la funcin path. Esto se puede ver con lo siguiente:
<h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id } ) }}">{{ blog.title }}</a></h2>

En segundo lugar reproducimos el contenido del blog utilizando <p>{{ blog.blog(500 ) }}</p>. El argumento 500 que pasamos, es la longitud mxima de la entrada en el blog que queremos recibir de vuelta desde la funcin. Para que esto funcione tenemos que actualizar el mtodo getBlog que Doctrine 2 gener anteriormente para nosotros. Actualiza el mtodo getBlog en la entidad blog ubicada en src/Blogger/BlogBundle/Entity/Blog.php.
// src/Blogger/BlogBundle/Entity/Blog.php public function getBlog($length = null) { if (false === is_null($length) && $length > 0) return substr($this->blog, 0, $length); else return $this->blog; }

Debido a que el comportamiento habitual del mtodo getBlog debe devolver la entrada completa en el blog, establecemos el parmetro $length para que tenga un valor predeterminado de null. Si se le pasa null, devuelve todo el blog. Ahora bien, si apuntas tu navegador a http://symblog.dev/app_dev.php/ deberas ver la pgina Inicial exhibiendo las entradas ms recientes del blog. Tambin deberas poder navegar a la

entrada completa del blog por cada entrada haciendo clic en el ttulo del blog o en los enlaces continuar leyendo....

A pesar de que puedes consultar por entidades en el controlador, ese no es el mejor lugar para hacerlo. La consulta estar en mejores condiciones fuera del controlador por una serie de razones: 1. Nos gustara volver a utilizar la consulta en otras partes de la aplicacin, sin necesidad de duplicar el cdigo del QueryBuilder. 2. Si duplicamos el cdigo del QueryBuilder, tendramos que hacer varias modificaciones en el futuro si fuera necesario cambiar la consulta. 3. La separacin de la consulta y el controlador nos permitir probar la consulta de forma independiente del controlador. Doctrine 2 proporciona las clases Repositorio para facilitarnos esta tarea.

Repositorios de Doctrine 2
Ya te presentamos las clases Repositorio de Doctrine 2 en el captulo anterior, cuando creamos la pgina show del blog. Utilizamos la implementacin predeterminada de la clase Doctrine\ORM\EntityRepository de Doctrine para recuperar una entidad blog desde la base de datos por medio del mtodo find(). Debido a que queremos crear una consulta personalizada, tenemos que crear un repositorio personalizado. Doctrine 2 te puede ayudar en esta tarea. Actualiza los metadatos de la entidad Blog situados en el archivo en src/Blogger/BlogBundle/Entity/Blog.php.
// src/Blogger/BlogBundle/Entity/Blog.php /** * @ORM\Entity(repositoryClass="Blogger\BlogBundle\Repository\BlogRepository") * @ORM\Table(name="blog") * @ORM\HasLifecycleCallbacks() */

class Blog { // .. }

Puedes ver que hemos especificado la ubicacin del espacio de nombres de la clase BlogRepository con la que esta entidad est asociada. Puesto que ya hemos actualizado los metadatos de Doctrine 2 para la entidad Blog, es necesario volver a ejecutar la tarea doctrine:generate:entities de la siguiente manera:
$ php app/console doctrine:generate:entities Blogger

Doctrine 2 crea la clase intrprete para el BlogRepository situado en src/Blogger/BlogBundle/Repository/BlogRepository.php.


<?php // src/Blogger/BlogBundle/Repository/BlogRepository.php namespace Blogger\BlogBundle\Repository; use Doctrine\ORM\EntityRepository; /** * BlogRepository * * Esta clase fue generada por el ORM de Doctrine. Abajo aade * tu propia personalizacin a los mtodos del repositorio. */ class BlogRepository extends EntityRepository { }

La clase BlogRepository extiende a la clase EntityRepository la cual ofrece el mtodo find() que utilizamos anteriormente. Por ltimo actualiza la clase BlogRepository, moviendo el cdigo del QueryBuilder desde el controlador Page a la clase BlogRepository.
<?php // src/Blogger/BlogBundle/Repository/BlogRepository.php namespace Blogger\BlogBundle\Repository; use Doctrine\ORM\EntityRepository; /** * BlogRepository * * Esta clase fue generada por el ORM de Doctrine. Abajo aade * tu propia personalizacin a los mtodos del repositorio. */ class BlogRepository extends EntityRepository { public function getLatestBlogs($limit = null) { $qb = $this->createQueryBuilder('b') ->select('b') ->addOrderBy('b.created', 'DESC'); if (false === is_null($limit)) $qb->setMaxResults($limit);

return $qb->getQuery() ->getResult(); } }

Hemos creado el mtodo getLatestBlogs el cual devolver las entradas ms recientes del blog, tanto en la misma forma que lo hizo el controlador como el cdigo QueryBuilder. En la clase Repositorio tenemos acceso directo al Generador de consultas a travs del mtodo createQueryBuilder(). Tambin hemos aadido un parmetro $lmit predeterminado para poder limitar la cantidad de resultados a devolver. El restablecimiento de la consulta es muy parecido a lo que es en el controlador. Posiblemente hayas notado que no tenamos necesidad de especificar la entidad a usar a travs del mtodo from(). Eso es porque estamos dentro del BlogRepository el cual est asociado con la entidad blog. Si nos fijamos en la implementacin del mtodo createQueryBuilder en la clase EntityRepository podemos ver que el mtodo from() es llamado para nosotros.
// Doctrine\ORM\EntityRepository public function createQueryBuilder($alias) { return $this->_em->createQueryBuilder() ->select($alias) ->from($this->_entityName, $alias); }

Finalmente vamos a actualizar la accin index del controlador Page para utilizar el BlogRepository.
// src/Blogger/BlogBundle/Controller/PageController.php class PageController extends Controller { public function indexAction() { $em = $this->getDoctrine() ->getEntityManager(); $blogs = $em->getRepository('BloggerBlogBundle:Blog') ->getLatestBlogs(); return $this->render('BloggerBlogBundle:Page:index.html.twig', array( 'blogs' => $blogs ));

} }

// ..

Ahora, al actualizar la pgina esta debe mostrar exactamente lo mismo que antes. Todo lo que hemos hecho es reconstruir el cdigo para que las clases correctas realicen las tareas correctas.

Ms sobre el modelo: Creando la entidad Comentario


Los blogs son slo la mitad de la historia cuando se trata de la comunicacin y debate de ideas. Tambin es necesario permitir a los lectores la posibilidad de comentar las publicaciones del blog. Estos comentarios tambin se tienen que persistir, y relacionar con la entidad Blog puesto que un blog puede tener muchos comentarios. Vamos a comenzar definiendo los fundamentos de la clase entidad Comment. Crea un nuevo

archivo situado en src/Blogger/BlogBundle/Entity/Comment.php con el siguiente contenido:


<?php // src/Blogger/BlogBundle/Entity/Comment.php namespace Blogger\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass="Blogger\BlogBundle\Repository\CommentRepository") * @ORM\Table(name="comment") * @ORM\HasLifecycleCallbacks() */ class Comment { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @ORM\Column(type="string") */ protected $user; /** * @ORM\Column(type="text") */ protected $comment; /** * @ORM\Column(type="boolean") */ protected $approved; /** * @ORM\ManyToOne(targetEntity="Blog", inversedBy="comments") * @ORM\JoinColumn(name="blog_id", referencedColumnName="id") */ protected $blog; /** * @ORM\Column(type="datetime") */ protected $created; /** * @ORM\Column(type="datetime") */ protected $updated; public function __construct() { $this->setCreated(new \DateTime()); $this->setUpdated(new \DateTime()); $this->setApproved(true);

} /** * @ORM\preUpdate */ public function setUpdatedValue() { $this->setUpdated(new \DateTime()); } }

La mayor parte de lo que ves aqu, ya lo hemos cubierto en el captulo anterior, sin embargo, hemos utilizado metadatos para establecer un enlace con la entidad blog. Puesto que un comentario es para un blog, hemos creado un enlace en la entidad Comment a la entidad blog a la que pertenece. Lo hicimos especificando un enlace ManyToOne destinado a la entidad blog. Tambin especificamos que la inversa de este enlace estar disponible a travs de Comments. Para crear este inversa, es necesario actualizar la entidad blog para informar a Doctrine 2 que un blog puede tener muchos comentarios. Actualiza la entidad blog ubicada en src/Blogger/BlogBundle/Entity/Blog.php para aadir esta asignacin.
<?php // src/Blogger/BlogBundle/Entity/Blog.php namespace Blogger\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; /** * @ORM\Entity(repositoryClass="Blogger\BlogBundle\Repository\BlogRepository") * @ORM\Table(name="blog") * @ORM\HasLifecycleCallbacks() */ class Blog { // .. /** * @ORM\OneToMany(targetEntity="Comment", mappedBy="blog") */ protected $comments; // .. public function __construct() { $this->comments = new ArrayCollection(); $this->setCreated(new \DateTime()); $this->setUpdated(new \DateTime());

} }

// ..

Hay unos cuantos cambios que debemos resaltar aqu. En primer lugar, aadimos metadatos a la propiedad $comments. Recuerda que en el captulo anterior, no aadimos metadatos para esta propiedad, ya que no queramos que Doctrine 2 los persistiera. Esto sigue siendo cierto, sin embargo, s queremos que Doctrine 2 pueda llenar esta propiedad con las entidades Comentario

correspondientes. Eso es lo que se logra con los metadatos. En segundo lugar, Doctrine 2 requiere que la propiedad $comments predeterminada sea un objeto ArrayCollection. Esto lo hacemos en el constructor. Tambin, toma en cuenta la declaracin use para importar la clase ArrayCollection. Puesto que ya hemos creado la entidad Comentario, y actualizado la entidad Blog, permitamos que Doctrine 2 genere los mtodos de acceso. Ejecuta la siguiente tarea de Doctrine 2 como antes para alcanzar este objetivo:
$ php app/console doctrine:generate:entities Blogger

Ahora, ambas entidades deben estar actualizadas con los mtodos de acceso correctos. Tambin notars que se ha creado la clase CommentRepository en src/Blogger/BlogBundle/Repository/CommentRepository.php como lo especificamos en los metadatos. Finalmente necesitamos actualizar la base de datos para reflejar los cambios en nuestras entidades. Podramos utilizar la tarea doctrine:schema:update de la siguiente manera para hacerlo, pero, mejor vamos a presentarte las Migraciones de Doctrine 2.
$ php app/console doctrine:schema:update --force

Migraciones de Doctrine 2
La extensin Migraciones de Doctrine 2 y el paquete no vienen con la Edicin estndar de Symfony2, tenemos que instalarlas manualmente como lo hicimos con la extensin y el paquete Fixtures. Abre el archivo deps ubicado en el directorio raz de tu proyecto y aade la extensin y el paquete Migraciones de Doctrine 2 de la siguiente manera:
[doctrine-migrations] git=http://github.com/doctrine/migrations.git [DoctrineMigrationsBundle] git=http://github.com/symfony/DoctrineMigrationsBundle.git target=/bundles/Symfony/Bundle/DoctrineMigrationsBundle

En seguida actualiza tus proveedores para reflejar estos cambios.


$ php bin/vendors install

Esto descargar la versin ms reciente de cada uno de los repositorios desde Github y los instalar en el lugar solicitado. Nota Si ests usando una mquina que no tiene instalado Git tendrs que descargar e instalar manualmente la extensin y el paquete. Extension doctrine-migrations: Descarga la versin actual del paquete migrations de GitHub y expande su contenido en: vendor/doctrine-migrations. DoctrineMigrationsBundle: Descarga la versin actual del paquete DoctrineMigrationsBundle de GitHub y expande su contenido en: vendor/bundles/Symfony/Bundle/DoctrineMigrationsBundle. A continuacin actualiza el archivo app/autoloader.php para registrar el nuevo espacio de nombres. Debido a que las migraciones de Doctrine 2 tambin estn en el espacio de nombres Doctrine\DBAL la debes colocar por encima de la configuracin actual del Doctrine\DBAL existente puesto que esta especifica una nueva ruta. Los espacios de nombres son revisados de

arriba hacia abajo por lo tanto los espacios de nombres ms especficos se deben registrar antes de los menos especficos.
// app/autoloader.php // ... $loader->registerNamespaces(array( // ... 'Doctrine\\DBAL\\Migrations' => __DIR__.'/../vendor/doctrine-migrations/lib', 'Doctrine\\DBAL' => __DIR__.'/../vendor/doctrine-dbal/lib', // ... ));

Ahora vamos a registrar el paquete en el ncleo situado en app/AppKernel.php.


// app/AppKernel.php public function registerBundles() { $bundles = array( // ... new Symfony\Bundle\DoctrineMigrationsBundle\DoctrineMigrationsBundle(), // ... ); // ... }

Advertencia La biblioteca de Migraciones de Doctrine 2 todava se encuentra en estado alfa por lo tanto su uso en servidores de produccin se debe desalentar, por el momento. Ahora estamos listos para actualizar la base de datos para reflejar los cambios a la entidad. Se trata de un proceso de 2 pasos. En primer lugar tenemos que conseguir que las Migraciones de Doctrine 2 resuelvan las diferencias entre las entidades y el esquema de la base de datos actual. Esto se hace con la tarea doctrine:migrations:diff. En segundo lugar necesitamos realizar efectivamente la migracin basndonos en las diferencias creadas anteriormente. Esto se hace con la tarea doctrine:migrations:migrate. Ejecuta las 2 siguientes ordenes para actualizar el esquema de la base de datos.
$ php app/console doctrine:migrations:diff $ php app/console doctrine:migrations:migrate

Tu base de datos ahora refleja los ltimos cambios de la entidad y contiene la tabla de comentarios. Nota Tambin deberas notar una nueva tabla en la base de datos llamada migration_versions. Esta almacena los nmeros de versin de cada migracin para que la tarea de migracin sea capaz de ver qu versin es la base de datos actual. Truco Las migraciones de Doctrine 2 son una gran manera de actualizar la base de datos de produccin, los cambios se pueden hacer mediante programacin. Esto significa que puedes integrar esta tarea en un guin para actualizar automticamente la base de datos cuando despliegues una nueva versin de tu aplicacin. Las migraciones de Doctrine 2 tambin nos permiten revertir los cambios creados ya que cada migracin tiene los mtodos up y down. Para revertir a una versin anterior es necesario especificar el nmero de versin a que te gustara regresar usando la siguiente tarea:
$ php app/console doctrine:migrations:migrate 20110806183439

Datos de prueba: Revisados


Ahora que ya hemos creado la entidad Comment, vamos a aadirle algunos accesorios. Siempre es buena idea aadir algunos accesorios cada vez que creas una entidad. Sabemos que un comentario debe tener una entidad Blog relacionada, de esta manera lo establecimos en los metadatos de su configuracin, para ello al crear accesorios para las entidades Comentario tendremos que especificar la entidad Blog a la que pertenecen. Ya creamos los accesorios para la entidad Blog por lo tanto simplemente podramos actualizar ese archivo para agregar las entidades Comentario. Esto puede ser manejable por ahora, pero, qu suceder despus cuando agreguemos usuarios, categoras del blog, y un montn de otras entidades a nuestro paquete? Una mejor manera sera crear un nuevo archivo para los accesorios de la entidad Comentario. El problema con este enfoque es: cmo podemos acceder a las entidades Blog desde los accesorios blog? Afortunadamente esto se puede conseguir fcilmente estableciendo referencias a objetos en un archivo de accesorios donde estos pueden acceder a otros accesorios. Actualiza la entidad Blog DataFixtures ubicada en src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php con lo siguiente. Los cambios a destacar aqu son la extensin de la clase AbstractFixture y la implementacin de OrderedFixtureInterface. Tambin ten en cuenta las dos nuevas declaraciones use para importar esas clases:
<?php // src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php namespace Blogger\BlogBundle\DataFixtures\ORM; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Blogger\BlogBundle\Entity\Blog; class BlogFixtures extends AbstractFixture implements OrderedFixtureInterface { public function load($manager) { // .. $manager->flush(); $this->addReference('blog-1', $this->addReference('blog-2', $this->addReference('blog-3', $this->addReference('blog-4', $this->addReference('blog-5', $blog1); $blog2); $blog3); $blog4); $blog5);

public function getOrder() { return 1; }

Aadimos las referencias a las entidades Blog a travs del mtodo AddReference(). Este primer parmetro es un identificador de referencia que puedes utilizar ms tarde al recuperar el objeto. Finalmente debemos implementar el mtodo getOrder() para especificar el orden de carga de los accesorios. Los blogs se deben cargar antes que los comentarios por lo tanto devolver 1.

Datos de prueba Comentario


Ahora estamos listos para definir algunos accesorios para nuestra entidad Comentario. Crea un archivo de accesorios en src/Blogger/BlogBundle/DataFixtures/ORM/CommentFixtures.php y adele el siguiente contenido:
<?php // src/Blogger/BlogBundle/DataFixtures/ORM/CommentFixtures.php namespace Blogger\BlogBundle\DataFixtures\ORM; use use use use Doctrine\Common\DataFixtures\AbstractFixture; Doctrine\Common\DataFixtures\OrderedFixtureInterface; Blogger\BlogBundle\Entity\Comment; Blogger\BlogBundle\Entity\Blog;

class CommentFixtures extends AbstractFixture implements OrderedFixtureInterface { public function load($manager) { $comment = new Comment(); $comment->setUser('symfony'); $comment->setComment('To make a long story short. You can\'t go wrong by choosing Symfony! And no one has ever been fired for using Symfony.'); $comment->setBlog($manager->merge($this->getReference('blog-1'))); $manager->persist($comment); $comment = new Comment(); $comment->setUser('David'); $comment->setComment('To make a long story short. Choosing a framework must not be taken lightly; it is a long-term commitment. Make sure that you make the right selection!'); $comment->setBlog($manager->merge($this->getReference('blog-1'))); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Dade'); $comment->setComment('Anything else, mom? You want me to mow the lawn? Oops! I forgot, New York, No grass.'); $comment->setBlog($manager->merge($this->getReference('blog-2'))); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Kate'); $comment->setComment('Are you challenging me? '); $comment->setBlog($manager->merge($this->getReference('blog-2'))); $comment->setCreated(new \DateTime("2011-07-23 06:15:20")); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Dade'); $comment->setComment('Name your stakes.'); $comment->setBlog($manager->merge($this->getReference('blog-2'))); $comment->setCreated(new \DateTime("2011-07-23 06:18:35")); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Kate'); $comment->setComment('If I win, you become my slave.'); $comment->setBlog($manager->merge($this->getReference('blog-2'))); $comment->setCreated(new \DateTime("2011-07-23 06:22:53"));

$manager->persist($comment); $comment = new Comment(); $comment->setUser('Dade'); $comment->setComment('Your SLAVE?'); $comment->setBlog($manager->merge($this->getReference('blog-2'))); $comment->setCreated(new \DateTime("2011-07-23 06:25:15")); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Kate'); $comment->setComment('You wish! You\'ll do shitwork, scan, crack copyrights...'); $comment->setBlog($manager->merge($this->getReference('blog-2'))); $comment->setCreated(new \DateTime("2011-07-23 06:46:08")); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Dade'); $comment->setComment('And if I win?'); $comment->setBlog($manager->merge($this->getReference('blog-2'))); $comment->setCreated(new \DateTime("2011-07-23 10:22:46")); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Kate'); $comment->setComment('Make it my first-born!'); $comment->setBlog($manager->merge($this->getReference('blog-2'))); $comment->setCreated(new \DateTime("2011-07-23 11:08:08")); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Dade'); $comment->setComment('Make it our first-date!'); $comment->setBlog($manager->merge($this->getReference('blog-2'))); $comment->setCreated(new \DateTime("2011-07-24 18:56:01")); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Kate'); $comment->setComment('I don\'t DO dates. But I don\'t lose either, so you\'re on!'); $comment->setBlog($manager->merge($this->getReference('blog-2'))); $comment->setCreated(new \DateTime("2011-07-25 22:28:42")); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Stanley'); $comment->setComment('It\'s not gonna end like this.'); $comment->setBlog($manager->merge($this->getReference('blog-3'))); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Gabriel'); $comment->setComment('Oh, come on, Stan. Not everything ends the way you think it should. Besides, audiences love happy endings.'); $comment->setBlog($manager->merge($this->getReference('blog-3'))); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Mile'); $comment->setComment('Doesn\'t Bill Gates have something like that?');

$comment->setBlog($manager->merge($this->getReference('blog-5'))); $manager->persist($comment); $comment = new Comment(); $comment->setUser('Gary'); $comment->setComment('Bill Who?'); $comment->setBlog($manager->merge($this->getReference('blog-5'))); $manager->persist($comment); } $manager->flush();

public function getOrder() { return 2; }

Al igual que con las modificaciones que hicimos a la clase BlogFixtures, la clase CommentFixtures tambin extiende a la clase AbstractFixture e implementa la OrderedFixtureInterface. Esto significa que tambin debes implementar el mtodo getOrder(). Esta vez fijamos el valor de retorno a 2, para garantizar que estos accesorios se cargarn despus de los accesorios blog. Tambin podemos ver cmo se estn utilizando las referencias a las entidades Blog creadas anteriormente.
$comment->setBlog($manager->merge($this->getReference('blog-2')));

Ahora estamos listos para cargar los accesorios a la base de datos.


$ php app/console doctrine:fixtures:load

Mostrando comentarios
Ahora podemos mostrar los comentarios relacionados con cada entrada del blog. Empecemos actualizando el CommentRepository con un mtodo para recuperar los comentarios aprobados ms recientes de un blog.

Repositorio de comentarios
Abre la clase CommentRepository situada en src/Blogger/BlogBundle/Repository/CommentRepository.php y reemplaza su contenido con lo siguiente:
<?php // src/Blogger/BlogBundle/Repository/CommentRepository.php namespace Blogger\BlogBundle\Repository; use Doctrine\ORM\EntityRepository; /** * CommentRepository * * Esta clase fue generada por el ORM de Doctrine. Abajo aade * tu propia personalizacin a los mtodos del repositorio. */ class CommentRepository extends EntityRepository

{ public function getCommentsForBlog($blogId, $approved = true) { $qb = $this->createQueryBuilder('c') ->select('c') ->where('c.blog = :blog_id') ->addOrderBy('c.created') ->setParameter('blog_id', $blogId); if (false === is_null($approved)) $qb->andWhere('c.approved = :approved') ->setParameter('approved', $approved); return $qb->getQuery() ->getResult(); } }

El mtodo que debemos crear, recuperar los comentarios de un blog. Para ello tenemos que aadir una clusula where a nuestra consulta. La clusula where utiliza un parmetro nombrado que se ajusta con el mtodo setParameter(). Siempre debes usar parmetros en lugar de establecer los valores directamente en la consulta, tal como:
->where('c.blog = ' . blogId)

En este ejemplo el valor de $blogId no ser desinfectado y podra dejar la consulta abierta a un ataque de inyeccin SQL.

Controlador del Blog


A continuacin necesitamos actualizar la accin show del controlador Blog para recuperar los comentarios del blog. Actualiza el controlador del Blog que se encuentra en src/Blogger/BlogBundle/Controller/BlogController.php con lo siguiente:
// src/Blogger/BlogBundle/Controller/BlogController.php public function showAction($id) { // .. if (!$blog) { throw $this->createNotFoundException('Unable to find Blog post.'); } $comments = $em->getRepository('BloggerBlogBundle:Comment') ->getCommentsForBlog($blog->getId()); return $this->render('BloggerBlogBundle:Blog:show.html.twig', array( 'blog' => $blog, 'comments' => $comments )); }

Usamos el mtodo new en el CommentRepository para recuperar los comentarios aprobados del blog. Tambin pasamos la coleccin $comments a la plantilla.

La plantilla show del Blog


Ahora que tenemos una lista de comentarios del blog, podemos actualizar la plantilla show del

blog para mostrar los comentarios. Simplemente, podramos colocar la presentacin de los comentarios directamente en la plantilla show del blog, pero, debido a que los comentarios tienen su propia entidad, sera mucho mejor separar tal presentacin en otra plantilla, e incluir esa plantilla. Esto nos permitira volver a utilizar la plantilla para reproducir los comentarios en otras partes de la aplicacin. Actualiza la plantilla show del blog ubicada en src/Blogger/BlogBundle/Resources/public/views/Blog/show.html.twig con lo siguiente:
{# src/Blogger/BlogBundle/Resources/public/views/Blog/show.html.twig #} {# .. #} {% block body %} {# .. #} <section class="comments" id="comments"> <section class="previous-comments"> <h3>Comments</h3> {% include 'BloggerBlogBundle:Comment:index.html.twig' with { 'comments': comments } %} </section> </section> {% endblock %}

Como puedes ver, usamos una nueva etiqueta de Twig, la etiqueta include. Esta incluir el contenido de la plantilla especificada por BloggerBlogBundle:Comment:index.html.twig. Adems le podemos pasar cualquier cantidad de argumentos a la plantilla. En este caso, le tenemos que pasar una coleccin de entidades Comentario para que las reproduzca.

Plantilla para mostrar Comentarios


La BloggerBlogBundle:Comment:index.html.twig que estamos incluyendo an no existe, por lo tanto la tenemos que crear. Dado que esta slo es una plantilla, no es necesario crear una ruta o un controlador para ella, solo necesitamos el archivo de plantilla. Crea un nuevo archivo situado en src/Blogger/BlogBundle/Resources/public/views/Comment/index.html.t wig con el siguiente contenido:
{# src/Blogger/BlogBundle/Resources/public/views/Comment/index.html.twig #} {% for comment in comments %} <article class="comment {{ cycle(['odd', 'even'], loop.index0) }}" id="comment-{{ comment.id }}"> <header> <p><span class="highlight">{{ comment.user }}</span> commented <time datetime="{{ comment.created|date('c') }}">{{ comment.created|date('l, F j, Y') }}</time></p> </header> <p>{{ comment.comment }}</p> </article> {% else %} <p>There are no comments for this post. Be the first to comment...</p> {% endfor %}

Como puedes ver iteramos sobre una coleccin de entidades Comentario y mostramos los comentarios. Tambin presentamos una de las otras agradables funciones de Twig, la funcin cycle. Esta funcin se mover entre los valores de la matriz que se le pasa conforme avanza cada

iteracin del bucle. El valor de la iteracin actual del bucle se obtiene a travs de la variable especial loop.index0. Esta mantiene un recuento de las iteraciones del bucle, comenzando en 0. Hay una serie de otras variables especiales disponibles cuando estamos dentro de un bloque de cdigo de bucle. Tambin puedes notar que establecimos un ID HTML para el elemento artculo. Esto, ms adelante, nos permitir crear vnculos permanentes para crear ms comentarios.

CSS para mostrar comentarios


Por ltimo vamos a aadir un poco de CSS para mantener la elegancia en los comentarios. Actualiza la hoja de estilos situada en src/Blogger/BlogBundle/Resorces/public/css/blog.css con lo siguiente:
/** src/Blogger/BlogBundle/Resorces/public/css/blog.css **/ .comments { clear: both; } .comments .odd { background: #eee; } .comments .comment { padding: 20px; } .comments .comment p { margin-bottom: 0; } .comments h3 { background: #eee; padding: 10px; font-size: 20px; margin-bottom: 20px; clear: both; } .comments .previous-comments { margin-bottom: 20px; }

Si ahora echas un vistazo a una pgina que muestra un blog, por ejemplo, http://symblog.dev/app_dev.php/2 deberas ver los comentarios del blog.

Aadiendo comentarios
En la ltima parte de este captulo aadiremos la funcionalidad para que los usuarios agreguen comentarios a las publicaciones del blog. Esto ser posible a travs de un formulario en la pgina show del blog. Ya te presentamos la creacin de formularios en Symfony2 cuando creamos el formulario de contacto. En lugar de crear manualmente el formulario de comentarios, le puedes

permitir a Symfony2 que lo haga por nosotros. Ejecuta la siguiente tarea para generar la clase CommentType para la entidad Comentario.
$ php app/console generate:doctrine:form BloggerBlogBundle:Comment

De nuevo, aqu notars el uso de la versin dura para especificar una entidad Comentario. Truco Posiblemente habrs notado que tambin est disponible la tarea doctrine:generate:form. Esta es la misma tarea solo que el espacio de nombres es diferente. La tarea para generar el formulario ha creado la clase CommentType situada en src/Blogger/BlogBundle/Form/CommentType.php.
<?php // src/Blogger/BlogBundle/Form/CommentType.php namespace Blogger\BlogBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class CommentType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder ->add('user') ->add('comment') ->add('approved') ->add('created') ->add('updated') ->add('blog') ; } public function getName() { return 'blogger_blogbundle_commenttype'; } }

Ya hemos explorado lo que est pasando aqu cuando generamos la clase EnquiryType anterior. Ahora, podramos empezar a personalizar este tipo, pero, primero vamos a mostrar el formulario.

Mostrando el formulario de comentarios


Nuestro primer objetivo es permitir a nuestros usuarios que agreguen comentarios desde la misma pgina del blog, podramos crear el formulario en la accin show del controlador Blog y reproducir el formulario directamente en la plantilla show. Sin embargo, sera mucho mejor separar este cdigo como lo hicimos con la visualizacin de los comentarios. La diferencia entre mostrar los comentarios y mostrar el formulario de comentarios es que necesitamos procesar el formulario, por lo tanto esta vez requerimos de un controlador. Esto introduce un mtodo ligeramente diferente al anterior en el que slo incluimos una plantilla.

Enrutando
Tenemos que crear una nueva ruta para manejar el procesamiento de los formularios presentados.

Aade una nueva ruta al archivo de enrutado ubicado en src/Blogger/BlogBundle/Resources/config/routing.yml.


BloggerBlogBundle_comment_create: pattern: /comment/{blog_id} defaults: { _controller: BloggerBlogBundle:Comment:create } requirements: _method: POST blog_id: \d+

El controlador
A continuacin, tenemos que crear el nuevo controlador Comment al que hemos hecho referencia anteriormente. Crea un archivo situado en src/Blogger/BlogBundle/Controller/CommentController.php con el siguiente contenido:
<?php // src/Blogger/BlogBundle/Controller/CommentController.php namespace Blogger\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Blogger\BlogBundle\Entity\Comment; use Blogger\BlogBundle\Form\CommentType; /** * Comment controller. */ class CommentController extends Controller { public function newAction($blog_id) { $blog = $this->getBlog($blog_id); $comment = new Comment(); $comment->setBlog($blog); $form = $this->createForm(new CommentType(), $comment); return $this->render('BloggerBlogBundle:Comment:form.html.twig', array( 'comment' => $comment, 'form' => $form->createView() ));

public function createAction($blog_id) { $blog = $this->getBlog($blog_id); $comment = new Comment(); $comment->setBlog($blog); $request = $this->getRequest(); $form = $this->createForm(new CommentType(), $comment); $form->bindRequest($request); if ($form->isValid()) { // TODO: Persistir la entidad comentario return $this->redirect($this>generateUrl('BloggerBlogBundle_blog_show', array( 'id' => $comment->getBlog()->getId())) .

'#comment-' . $comment->getId() } array( )); } protected function getBlog($blog_id) { $em = $this->getDoctrine() ->getEntityManager(); $blog = $em->getRepository('BloggerBlogBundle:Blog')->find($blog_id); if (!$blog) { throw $this->createNotFoundException('Unable to find Blog post.'); } return $blog; } } );

return $this->render('BloggerBlogBundle:Comment:create.html.twig', 'comment' => $comment, 'form' => $form->createView()

Creamos dos acciones en el controlador Comment, una para new y otra para create. La accin new tiene que ver con mostrar el formulario de comentarios, la accin create tiene que ver con el procesamiento al presentar el formulario de comentarios. Si bien esto puede parecer un gran plato de cdigo, no hay nada nuevo aqu, ya hemos cubierto todo en el captulo 2 cuando creamos el formulario de contacto. No obstante, antes de seguir adelante asegrate de que entiendes completamente lo que est sucediendo en el controlador Comment.

Validando el formulario
No queremos que los usuarios puedan enviar comentarios para los blogs con valores usuario o comentario en blanco. Para lograrlo nos remontaremos a los validadores que te presentamos en la parte 2 cuando creamos el formulario de consulta. Actualiza la entidad Comentario ubicada en src/Blogger/BlogBundle/Entity/Comment.php con lo siguiente:
<?php // src/Blogger/BlogBundle/Entity/Comment.php // .. use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; // .. class Comment { // .. public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('user', new NotBlank(array( 'message' => 'You must enter your name' ))); $metadata->addPropertyConstraint('comment', new NotBlank(array(

} }

'message' => 'You must enter a comment' )));

// ..

Las restricciones garantizan que tanto el usuario como el comentario no deben estar en blanco. Tambin hemos creado la opcin mensaje tanto para las restricciones como para redefinir los predeterminados. Recuerda agregar los espacios de nombres ClassMetadata y NotBlank como se muestra arriba.

La vista
A continuacin necesitamos crear dos plantillas para las acciones new y create del controlador. En primer lugar crea un nuevo archivo situado en src/Blogger/BlogBundle/Resources/public/views/Comment/form.html.tw ig con el siguiente contenido:
{# src/Blogger/BlogBundle/Resources/public/views/Comment/form.html.twig #} <form action="{{ path('BloggerBlogBundle_comment_create', { 'blog_id' : comment.blog.id } ) }}" method="post" {{ form_enctype(form) }} class="blogger"> {{ form_widget(form) }} <p> <input type="submit" value="Submit"> </p> </form>

El propsito de esta plantilla es muy simple, slo reproduce el formulario de comentarios. Adems notars que el mtodo accin del formulario es POST a la nueva ruta que hemos creado BloggerBlogBundle_comment_create. A continuacin vamos a aadir la plantilla para la vista create. Crea un nuevo archivo situado en src/Blogger/BlogBundle/Resources/public/views/Comment/create.html. twig con el siguiente contenido:
{% extends 'BloggerBlogBundle::base.html.twig' %} {% block title %}Add Comment{% endblock%} {% block body %} <h1>Add comment for blog post "{{ comment.blog.title }}"</h1> {% include 'BloggerBlogBundle:Comment:form.html.twig' with { 'form': form } %} {% endblock %}

Como la accin create del controlador Comment trata con el procesamiento del formulario, tambin necesitamos poder visualizarlo, puesto que podra haber errores en el formulario. Reutilizamos el BloggerBlogBundle:Comment:form.html.twig para reproducir el formulario real y evitar la duplicidad de cdigo. Ahora vamos a actualizar la plantilla show del blog para reproducir el formulario aadir del blog. Actualiza la plantilla ubicada en src/Blogger/BlogBundle/Resources/public/views/Blog/show.html.twig con lo siguiente:
{# src/Blogger/BlogBundle/Resources/public/views/Blog/show.html.twig #}

{# .. #} {% block body %} {# .. #} <section class="comments" id="comments"> {# .. #} <h3>Add Comment</h3> {% render 'BloggerBlogBundle:Comment:new' with { 'blog_id': blog.id } %} </section> {% endblock %}

Aqu utilizamos otra nueva etiqueta de Twig, la etiqueta render. Esta etiqueta reproducir el contenido de un controlador en la plantilla. En nuestro caso, reproduce el contenido de la accin new del controlador BloggerBlogBundle:Comment:new. Si ahora le echas un vistazo a una de las pginas show del blog, como http://symblog.dev/app_dev.php/2 vers que Symfony2 lanza una excepcin.

Esta excepcin es lanzada por la plantilla BloggerBlogBundle BloggerBlogBundle:Blog:show.html.twig. Si vemos de cerca la lnea 25 de la plantilla BloggerBlogBundle:Blog:show.html.twig podemos ver que la siguiente lnea muestra que el problema existe realmente en el proceso de integracin del controlador BloggerBlogBundle:Comment:create.
{% render 'BloggerBlogBundle:Comment:create' with { 'blog_id': blog.id } %}

Si nos fijamos un poco ms en el mensaje de excepcin, este nos da algo ms de informacin sobre la naturaleza del por qu se produjo la excepcin. Las entidades pasadas al campo de eleccin deben tener definido un mtodo __toString() Esto nos est diciendo que un campo de eleccin que estamos tratando de pintar no tiene establecido un mtodo __toString() para la entidad asociada con el campo de eleccin. Un campo de eleccin es un elemento del formulario que le da al usuario una serie de opciones, como un elemento select (desplegable). Tal vez ests preguntndote dnde diablos estamos pintando un campo de eleccin en el formulario de comentarios? Si una vez ms nos fijamos en la plantilla del formulario de comentarios te dars cuenta de que reproducimos el formulario usando la funcin {{ form_widget(form) }} de Twig. Esta funcin devuelve el formulario completo en su forma bsica. Por lo tanto vamos a volver a la clase dnde creamos el formulario, la clase CommentType. Podemos ver que se aade una serie de campos al formulario a travs del objeto FormBuilder. En particular, estamos agregando un campo blog.

Si recuerdas el captulo 2, nos habl de cmo el FormBuilder trata de adivinar el tipo de campo a producir basndose en los metadatos relacionados con el campo. A medida que configuramos una relacin entre los entidades Comentario y Blog, el FormBuilder ha adivinado que el comentario debe ser un campo choice, el cual permite al usuario especificar la entrada en el blog adjuntndola al comentario. Es por eso que tenemos el campo choice en el formulario, y el porqu Symfony2 est lanzando la excepcin. Podemos solucionar este problema implementado el mtodo __toString() en la entidad Blog.
// src/Blogger/BlogBundle/Entity/Blog.php public function __toString() { return $this->getTitle(); }

Truco Los mensajes de error en Symfony2 son muy detallados describiendo el problema que se ha producido. Siempre lee los mensajes de error, ya que por lo general facilitan bastante el proceso de depuracin. Los mensajes de error tambin proporcionan una traza completa para que puedas ver los pasos que se estaban siguiendo al producirse el error. Ahora, al actualizar la pgina deberas ver aparecer el formulario de comentarios. Tambin te dars cuenta que se han pintado algunos campos no deseados tales como approved, created, updated y blog. Esto se debe a que no personalizamos la clase CommentType generada anteriormente. Truco Todos los campos reproducidos parece que son el tipo de campo correcto. El campos user es un campo text, el campo comment es un campo textarea, los 2 campos DateTime son una serie de campos select que te permiten seleccionarlos para precisar la hora, etc. Esto se debe a la habilidad del FormBuilder para adivinar el tipo de la propiedad y el campo que necesita reproducir. Este es capaz de hacerlo basndose en los metadatos que le proporciones. Puesto que hemos suministrado metadatos bastante especficos para entidad Comentario, el FormBuilder es capaz de hacer conjeturas precisas de los tipos de campo. Ahora actualizaremos esta clase ubicada en src/Blogger/BlogBundle/Form/CommentType.php para producir nicamente los campos que necesitamos:
<?php // src/Blogger/BlogBundle/Form/CommentType.php // .. class CommentType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder ->add('user') ->add('comment') ; } // .. }

Ahora, cuando actualices la pgina nicamente se emiten los campos usuario y comentario. Si envas ahora el formulario, el comentario en realidad no se guardar en la base de datos. Esto es

porque el controlador del formulario no hace nada con la entidad Comentario si el formulario supera la validacin. Entonces, cmo persistimos la entidad Comentario a la base de datos? Ya has visto cmo hacerlo cuando creamos los DataFixtures. Actualiza la accin create del controlador Comentario para persistir la entidad Comentario a la base de datos.
<?php // src/Blogger/BlogBundle/Controller/CommentController.php // .. class CommentController extends Controller { public function createAction($blog_id) { // .. if ($form->isValid()) { $em = $this->getDoctrine() ->getEntityManager(); $em->persist($comment); $em->flush(); return $this->redirect($this>generateUrl('BloggerBlogBundle_blog_show', array( 'id' => $comment->getBlog()->getId())) . '#comment-' . $comment->getId() ); } } } // ..

La persistencia de la entidad Comentario es tan simple como una llamada a persist() y otra a flush(). Recuerda que el formulario slo trata con objetos PHP, y Doctrine 2 gestiona y persiste esos objetos. No hay conexin directa entre la presentacin de un formulario, y la persistencia a la base de datos de la informacin presentada. Ahora deberas poder aadir comentarios a las entradas del blog.

Conclusin
Hemos hecho buenos progresos en este captulo. Nuestro sitio web de blogs est empezando a funcionar ms conforme a nuestras expectativas. Ahora tenemos listos los elementos bsicos, la pgina Inicial y la entidad comentarios. Ahora un usuario puede enviar comentarios a los blogs y leer los comentarios dejados por otros usuarios. Hemos visto cmo crear accesorios a los cuales se puede hacer referencia a travs de mltiples archivos y utilizar las Migraciones de Doctrine 2 para mantener en lnea el esquema de la base de datos con los cambios en las entidades. A continuacin vamos a ver la construccin de la barra lateral para incluir una nube de etiquetas y comentarios recientes. Tambin extenderemos Twig creando nuestros propios filtros personalizados. Por ltimo vamos a ver el uso de la biblioteca de activos Assetic para que nos ayude a gestionar nuestros activos.

[Parte 5] Personalizando la vista: extensiones Twig, la barra lateral y Assetic

Descripcin
En este captulo continuaremos construyendo la interfaz de usuario para symblog. Vamos a modificar la pgina inicial para mostrar informacin acerca de los comentarios de un blog publicado y abordaremos el SEO aadiendo el ttulo del blog a la URL. Tambin vamos a comenzar a trabajar en la barra lateral para agregar 2 componentes comunes en sitios de blog; La nube de etiquetas y comentarios recientes. Vamos a explorar los diferentes entornos con que contamos en Symfony2 y aprenderemos a manejar symblog en el entorno de produccin. El motor de plantillas Twig ser ampliado para proporcionar un nuevo filtro, e introduciremos Assetic para gestionar los archivos de activos del sitio web. Al final de este captulo habremos integrado los comentarios a la pgina principal, tendremos una nube de etiquetas y el componente de comentarios recientes en la barra

lateral y habremos utilizado Assetic para gestionar los archivos de nuestros activos web. Tambin habremos visto cmo ejecutar symblog en el entorno de produccin.

La pgina inicial Blogs y Comentarios


Hasta ahora, la pgina inicial muestra las entradas ms recientes del blog, pero no proporciona ninguna informacin respecto a los comentarios de los blogs. Ahora que hemos construido la entidad Comentario podemos volver a la pgina inicial y proporcionarle esta informacin. Puesto que hemos establecido la relacin entre las entidades Blog y Comentario sabemos que Doctrine 2 ser capaz de recuperar los comentarios de un blog (recuerda que hemos aadido un miembro $comments a la entidad Blog). Actualicemos la plantilla de la vista de la pgina inicial situada en src/Blogger/BlogBundle/Resources/views/Page/index.html.twig con lo siguiente:
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #} {# .. #} <footer class="meta"> <p>Comments: {{ blog.comments|length }}</p> <p>Posted by <span class="highlight">{{ blog.author }}</span> at {{ blog.created|date('h:iA') }}</p> <p>Tags: <span class="highlight">{{ blog.tags }}</span></p> </footer> {# .. #}

Hemos utilizado el captador comments para recuperar los comentarios del blog y luego depuramos la coleccin con el filtro length de Twig. Si echas un vistazo a la pgina inicial va http://symblog.dev/app_dev.php/ vers que ahora exhibe el nmero de comentarios de cada blog. Como se explic anteriormente, ya informamos a Doctrine 2 que el miembro $comments de la entidad Blog est relacionado a la entidad Comment. Habamos logrado esto en el captulo anterior con los siguientes metadatos en la entidad Blog ubicada en src/Blogger/BlogBundle/Entity/Blog.php.
// src/Blogger/BlogBundle/Entity/Blog.php /** * @ORM\OneToMany(targetEntity="Comment", mappedBy="blog") */ protected $comments;

Por lo tanto, sabemos que Doctrine 2 est consciente de la relacin entre blogs y comentarios, pero cmo poblamos el miembro $comments con las entidades Comments relacionadas? Si recuerdas de nuevo el mtodo BlogRepository que hemos creado (mostrado a continuacin) obtiene la pgina inicial con los blogs sin haber hecho ninguna seleccin para recuperar las entidades Comments relacionadas.
// src/Blogger/BlogBundle/Repository/BlogRepository.php public function getLatestBlogs($limit = null) { $qb = $this->createQueryBuilder('b') ->select('b') ->addOrderBy('b.created', 'DESC');

if (false === is_null($limit)) $qb->setMaxResults($limit); return $qb->getQuery() ->getResult(); }

Sin embargo, Doctrine 2 utiliza un proceso llamado carga diferida donde las entidades Comment se recuperan de la base de datos hasta cuando sean necesarias, en nuestro caso, en cuanto invocamos a {{ blog.comments|length }}. Podemos demostrar este proceso utilizando la barra de depuracin web. Ya hemos comenzado a explorar los fundamentos de la barra de depuracin web y ahora es tiempo de introducir una de sus caractersticas ms tiles, el generador de perfiles de Doctrine 2. Puedes acceder al generador de perfiles de Doctrine 2 haciendo clic en el ltimo icono de la barra de depuracin web. El nmero al lado de este icono indica la cantidad de consultas que se ejecutaron en la base de datos para la peticin HTTP actual.

Si haces clic en el icono de Doctrine 2 se te presentar informacin relacionada a las consultas que Doctrine 2 ejecut en la base de datos para la peticin HTTP actual.

Como puedes ver en la captura de pantalla anterior, hay una serie de consultas que se ejecutan para una peticin a la pgina principal. La segunda consulta ejecutada recupera de la base de datos las entidades Blog y se ejecuta como resultado del mtodo getLatestBlogs() en la clase BlogRepository. Siguiendo esta consulta te dars cuenta de una serie de consultas que obtienen los comentarios desde la base de datos, un blog a la vez. Podemos ver esto a causa de la WHERE t0.blog_id = ? en cada una de las consultas, donde la ? se sustituye por el valor del parmetro (el id del blog) en la siguiente lnea. Cada una de estas consultas son el resultado de las llamadas a {{ blog.comments }} en la plantilla de la pgina inicial. Cada vez que se ejecuta

esta funcin, Doctrine 2 tiene que cargar de manera diferida la entidad Comentario relacionada con la entidad Blog. Si bien la carga diferida es muy eficiente recuperando entidades relacionadas desde la base de datos, no siempre es el camino ms eficaz para lograr esta tarea. Doctrine 2 ofrece la posibilidad de unir las entidades relacionadas entre s al consultar la base de datos. De esta manera podemos extraer desde la base de datos las entidades Blog y Comentarios relacionadas en una nica consulta. Actualiza el cdigo del QueryBuilder situado en src/Blogger/BlogBundle/Repository/BlogRepository.php para unirlo con los comentarios.
// src/Blogger/BlogBundle/Repository/BlogRepository.php public function getLatestBlogs($limit = null) { $qb = $this->createQueryBuilder('b') ->select('b, c') ->leftJoin('b.comments', 'c') ->addOrderBy('b.created', 'DESC'); if (false === is_null($limit)) $qb->setMaxResults($limit); return $qb->getQuery() ->getResult(); }

Si ahora actualizas la pgina web y examinas la salida de Doctrine 2 en la barra de depuracin web notars que el nmero de consultas se ha reducido. Tambin puedes ver que la tabla de comentarios se ha unido a la tabla blog. La carga diferida y la unin de entidades relacionadas, ambos son conceptos muy poderosos, pero se tienen que usar correctamente. Tienes que encontrar el balance correcto entre los dos para que tu aplicacin se ejecute con la mayor eficiencia posible. Al principio puede parecer grandioso unir todas las entidades relacionadas para que nunca tengas que cargar de manera diferida y el conteo de consultas a la base de datos siempre se mantenga bajo. Sin embargo, es importante recordar que mientras ms informacin recuperes de la base de datos, ms procesamiento tiene que hacer Doctrine 2 para hidratar los objetos entidad. Ms datos tambin significa utilizar ms memoria del servidor para almacenar los objetos entidad. Antes de continuar hagamos una pequea adicin a la plantilla de la pgina inicial para agregar el nmero de comentarios que acabamos de aadir. Actualiza la plantilla de la pgina inicial ubicada en src/Blogger/BlogBundle/Resources/views/Page/index.html.twig aadindole un enlace para mostrar los comentarios del blog.
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #} {# .. #} <footer class="meta"> <p>Comments: <a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}#comments">{{ blog.comments|length }}</a></p> <p>Posted by <span class="highlight">{{ blog.author }}</span> at {{ blog.created|date('h:iA') }}</p> <p>Tags: <span class="highlight">{{ blog.tags }}</span></p> </footer> {# .. #}

La barra lateral
Actualmente la barra lateral de symblog se est viento un tanto vaca. Vamos a actualizarla con dos componentes de blog comunes, una nube de etiquetas y una lista con los comentarios recientes.

Nube de etiquetas
La nube de etiquetas muestra las etiquetas de cada blog enfatizando en ciertas formas audaces para mostrar las etiquetas ms comunes. Para lograr esto, necesitamos una manera de recuperar todas las etiquetas de todos los blogs. Para ello, vamos a crear algunos nuevos mtodos en la clase BlogRepository. Actualiza la clase BlogRepository situada en src/Blogger/BlogBundle/Repository/BlogRepository.php con lo siguiente:
// src/Blogger/BlogBundle/Repository/BlogRepository.php public function getTags() { $blogTags = $this->createQueryBuilder('b') ->select('b.tags') ->getQuery() ->getResult(); $tags = array(); foreach ($blogTags as $blogTag) { $tags = array_merge(explode(",", $blogTag['tags']), $tags); } foreach ($tags as &$tag) { $tag = trim($tag); } } return $tags;

public function getTagWeights($tags) { $tagWeights = array(); if (empty($tags)) return $tagWeights; foreach ($tags as $tag) { $tagWeights[$tag] = (isset($tagWeights[$tag])) ? $tagWeights[$tag] + 1 : 1; } // Revuelve las etiquetas uksort($tagWeights, function() { return rand() > rand(); }); $max = max($tagWeights); // un peso mximo de 5 $multiplier = ($max > 5) ? 5 / $max : 1; foreach ($tagWeights as &$tag) { $tag = ceil($tag * $multiplier); }

return $tagWeights;

Dado que las etiquetas se almacenan en la base de datos como valores separados por comas (CSV) necesitamos una manera de dividirlo y devolverlo como una matriz. Esto se logra mediante el mtodo getTags(). El mtodo getTagWeights() es capaz de utilizar una matriz de etiquetas para calcular el peso de cada etiqueta en base a su popularidad dentro de la matriz. Las etiquetas tambin se barajan para determinar su aleatoriedad de exhibicin en la pgina. Ahora que somos de capaces generar la nube de etiquetas, tenemos que mostrarla. Crea una nueva accin en el PageController que se encuentra en src/Blogger/BlogBundle/Controller/PageController.php para manejar la barra lateral.
// src/Blogger/BlogBundle/Controller/PageController.php public function sidebarAction() { $em = $this->getDoctrine() ->getEntityManager(); $tags = $em->getRepository('BloggerBlogBundle:Blog') ->getTags(); $tagWeights = $em->getRepository('BloggerBlogBundle:Blog') ->getTagWeights($tags); return $this->render('BloggerBlogBundle:Page:sidebar.html.twig', array( 'tags' => $tagWeights )); }

La accin es muy simple, esta utiliza los dos nuevos mtodos del BlogRepository para generar la nube de etiquetas, y la pasa a la vista. Ahora vamos a crear esa vista situada en src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig.
{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #} <section class="section"> <header> <h3>Tag Cloud</h3> </header> <p class="tags"> {% for tag, weight in tags %} <span class="weight-{{ weight }}">{{ tag }}</span> {% else %} <p>There are no tags</p> {% endfor %} </p> </section>

La plantilla tambin es muy simple. Esta slo itera en las distintas etiquetas ajustando una clase para el peso de la etiqueta. El bucle for introduce la forma de acceder a los pares clave y valor de la matriz, donde tag es la clave y weight es el valor. Hay una serie de variaciones sobre el uso del bucle for provistas en la documentacin de Twig. Si vuelves a mirar en la plantilla del diseo principal de BloggerBlogBundle, ubicada en `src/Blogger/BlogBundle/Resources/views/layout.html.twig te dars cuenta de que pusimos un marcador de posicin para el bloque de la barra lateral. Vamos a sustituirlo ahora

reproduciendo la nueva accin sidebar. Recuerda del captulo anterior que el mtodo render de Twig reproducir el contenido de una accin del controlador, en este caso la accin sidebar del controlador Page.
{# src/Blogger/BlogBundle/Resources/views/base.html.twig #} {# .. #} {% block sidebar %} {% render "BloggerBlogBundle:Page:sidebar" %} {% endblock %}

Por ltimo vamos a aadir el CSS necesario para la nube de etiquetas. Aade una nueva hoja de estilos situada en src/Blogger/BlogBundle/Resources/public/css/sidebar.css.
.sidebar .section { margin-bottom: 20px; } .sidebar h3 { line-height: 1.2em; font-size: 20px; margin-bottom: 10px; fontweight: normal; background: #eee; padding: 5px; } .sidebar p { line-height: 1.5em; margin-bottom: 20px; } .sidebar ul { list-style: none } .sidebar ul li { line-height: 1.5em } .sidebar .small { font-size: 12px; } .sidebar .comment p { margin-bottom: 5px; } .sidebar .comment { margin-bottom: 10px; padding-bottom: 10px; } .sidebar .tags { font-weight: bold; } .sidebar .tags span { color: #000; font-size: 12px; } .sidebar .tags .weight-1 { font-size: 12px; } .sidebar .tags .weight-2 { font-size: 15px; } .sidebar .tags .weight-3 { font-size: 18px; } .sidebar .tags .weight-4 { font-size: 21px; } .sidebar .tags .weight-5 { font-size: 24px; }

Debido a que hemos aadido una nueva hoja de estilos necesitamos incluirla. Actualiza la plantilla del diseo principal de BloggerBlogBundle, ubicada en src/Blogger/BlogBundle/Resources/views/layout.html.twig con lo siguiente:
{# src/Blogger/BlogBundle/Resources/views/base.html.twig #} {# .. #} {% block stylesheets %} {{ parent() }} <link href="{{ asset('bundles/bloggerblog/css/blog.css') }}" type="text/css" rel="stylesheet" /> <link href="{{ asset('bundles/bloggerblog/css/sidebar.css') }}" type="text/css" rel="stylesheet" /> {% endblock %} {# .. #}

Nota Si no ests usando el mtodo de enlace simblico para hacer referencia a los activos de tu paquete en el directorio web ahora debes volver a ejecutar la tarea de instalacin de activos para copiar el nuevo archivo CSS.
$ php app/console assets:install web

Si ahora actualizas la pgina de symblog en tu navegador vers la nube de etiquetas en la barra lateral. A fin de obtener las etiquetas para reproducirlas con diferentes pesos, posiblemente tengas

que actualizar los accesorios del blog para que algunas etiquetas se utilicen ms que otras.

Comentarios recientes
Ahora la nube de etiquetas est en su lugar, tambin agregaremos el componente de comentarios recientes a la barra lateral. Primero necesitamos una manera de recuperar los comentarios ms recientes de los blogs. Para ello vamos a aadir un nuevo mtodo al CommentRepository situado en src/Blogger/BlogBundle/Repository/CommentRepository.php.
<?php // src/Blogger/BlogBundle/Repository/CommentRepository.php public function getLatestComments($limit = 10) { $qb = $this->createQueryBuilder('c') ->select('c') ->addOrderBy('c.id', 'DESC'); if (false === is_null($limit)) $qb->setMaxResults($limit); return $qb->getQuery() ->getResult();

A continuacin actualiza la accin de la barra lateral situada en src/Blogger/BlogBundle/Controller/PageController.php para recuperar los comentarios ms recientes y pasarlos a la vista.
// src/Blogger/BlogBundle/Controller/PageController.php public function sidebarAction() { // .. $commentLimit = $this->container ->getParameter('blogger_blog.comments.latest_comment_

limit'); $latestComments = $em->getRepository('BloggerBlogBundle:Comment') ->getLatestComments($commentLimit); return $this->render('BloggerBlogBundle:Page:sidebar.html.twig', array( 'latestComments' => $latestComments, 'tags' => $tagWeights )); }

Notars que hemos utilizado un nuevo parmetro denominado blogger_blog.comments.latest_comment_limit para limitar el nmero de comentarios recuperados. Para crear este parmetro actualiza tu archivo de configuracin ubicado en src/Blogger/BlogBundle/Resources/config/config.yml con lo siguiente:
# src/Blogger/BlogBundle/Resources/config/config.yml parameters: # .. # Blogger mximo de comentarios recientes blogger_blog.comments.latest_comment_limit: 10

Por ltimo, debemos reproducir los comentarios recientes en la plantilla de la barra lateral. Actualiza la plantilla ubicada en src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig con lo siguiente:
{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #} {# .. #} <section class="section"> <header> <h3>Latest Comments</h3> </header> {% for comment in latestComments %} <article class="comment"> <header> <p class="small"><span class="highlight">{{ comment.user }}</span> commented on <a href="{{ path('BloggerBlogBundle_blog_show', { 'id': comment.blog.id }) }}#comment-{{ comment.id }}"> {{ comment.blog.title }} </a> [<em><time datetime="{{ comment.created| date('c') }}">{{ comment.created|date('Y-m-d h:iA') }}</time></em>] </p> </header> <p>{{ comment.comment }}</p> </p> </article> {% else %} <p>There are no recent comments</p> {% endfor %} </section>

Si ahora actualizas la pgina de symblog en tu navegador vers que se muestran los comentarios recientes en la barra lateral bajo la nube de etiquetas.

Extensiones Twig
Hasta ahora hemos estado mostrando las fechas de los comentarios del blog en un formato de fecha estndar, tal como 04/21/2011. Un enfoque mucho mejor sera mostrar las fechas de los comentarios en trminos de cunto tiempo hace que fue publicado el comentario, tal como publicado hace 3 horas. Podramos aadir un mtodo a la entidad Comentario para lograrlo y cambiar las plantillas para utilizar este mtodo en lugar de {{ comment.created | date('Ymd h: iA') }}. Debido a que posiblemente desees utilizar esta funcionalidad en otro lugar tendra ms sentido ponerla fuera de la entidad Comentario. Puesto que la transformacin de fechas es una tarea especfica para la capa de la vista, la debemos implementar utilizando el motor de plantillas Twig. Twig nos proporciona esta habilidad, proveyendo una interfaz Extensin. Podemos utilizar la Interfaz extensin en Twig para extender la funcionalidad predeterminada que ofrece. Vamos a crear una nueva extensin de filtro Twig que podamos utilizar de la siguiente manera.
{{ comment.created|created_ago }}

Esta devolver la fecha de creacin del comentario en un formato como publicado hace 2 das.

La extensin
Crea un archivo para la extensin de Twig situado en src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogExtension.php y actualzalo con el siguiente contenido:
<?php

// src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogExtension.php namespace Blogger\BlogBundle\Twig\Extensions; class BloggerBlogExtension extends \Twig_Extension { public function getFilters() { return array( 'created_ago' => new \Twig_Filter_Method($this, 'createdAgo'), ); } public function createdAgo(\DateTime $dateTime) { $delta = time() - $dateTime->getTimestamp(); if ($delta < 0) throw new \Exception("createdAgo is unable to handle dates in the future"); $duration = ""; if ($delta < 60) { // Segundos $time = $delta; $duration = $time . " second" . (($time > 1) ? "s" : "") . " ago"; } else if ($delta <= 3600) { // Mins $time = floor($delta / 60); $duration = $time . " minute" . (($time > 1) ? "s" : "") . " ago"; } else if ($delta <= 86400) { // Hours $time = floor($delta / 3600); $duration = $time . " hour" . (($time > 1) ? "s" : "") . " ago"; } else { // Days $time = floor($delta / 86400); $duration = $time . " day" . (($time > 1) ? "s" : "") . " ago"; } return $duration; } public function getName() { return 'blogger_blog_extension'; } }

La creacin de la extensin es bastante simple. Redefinimos el mtodo getFilters() para devolver cualquier cantidad de filtros que deses estn disponibles. En este caso, estamos creando el filtro created_ago. Entonces registramos este filtro para usar el mtodo createdAgo, el cual simplemente transforma un objeto DateTime en una cadena que representa el lapso de tiempo transcurrido desde que el valor fue almacenado en el objeto DateTime.

Registrando la extensin
Para que la extensin de Twig est disponible tenemos que actualizar el archivo de servicios que se encuentra en src/Blogger/BlogBundle/Resources/config/services.yml con lo siguiente:
services: blogger_blog.twig.extension: class: Blogger\BlogBundle\Twig\Extensions\BloggerBlogExtension tags: - { name: twig.extension }

Puedes ver que este es el registro de un nuevo servicio usando la clase BloggerBlogExtension de la extensin Twig que acabamos de crear.

Actualizando la vista
El nuevo filtro de Twig est listo para utilizarlo. Actualicemos la lista de comentarios recientes en la barra lateral para usar el filtro created_ago. Actualiza la plantilla de la barra lateral situada en src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig con lo siguiente:
{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #} {# .. #} <section class="section"> <header> <h3>Latest Comments</h3> </header> {% for comment in latestComments %} {# .. #} <em><time datetime="{{ comment.created|date('c') }}">{{ comment.created| created_ago }}</time></em> {# .. #} {% endfor %} </section>

Si ahora diriges tu navegador a http://symblog.dev/app_dev.php/ vers que las fechas de los comentarios recientes utilizan el filtro Twig para pintar el lapso transcurrido desde que fue publicado el comentario. Tambin debemos actualizar los comentarios que aparecen en la pgina show del blog para mostrarte cmo se usa ah el nuevo filtro. Remplaza el contenido en la plantilla ubicada en src/Blogger/BlogBundle/Resources/views/Comment/index.html.twig con lo siguiente:
{# src/Blogger/BlogBundle/Resources/views/Comment/index.html.twig #} {% for comment in comments %} <article class="comment {{ cycle(['odd', 'even'], loop.index0) }}" id="comment-{{ comment.id }}"> <header> <p><span class="highlight">{{ comment.user }}</span> commented <time datetime="{{ comment.created|date('c') }}">{{ comment.created| created_ago }}</time></p> </header> <p>{{ comment.comment }}</p> </article> {% else %}

<p>There are no comments for this post. Be the first to comment...</p> {% endfor %}

Truco Hay una serie de tiles extensiones de Twig disponibles a travs de la biblioteca Twig-Extensions en GitHub. Si creas una extensin til envala a travs de una peticin de extraccin a este depsito y posiblemente sea incluida para que otras personas la usen.

Slugifying la URL
Actualmente la URL de cada entrada del blog slo muestra el id del blog. Si bien esto es perfectamente aceptable desde el punto de vista funcional, no es el ideal para SEO. Por ejemplo, la URL http://symblog.dev/1 no da ninguna informacin sobre el contenido del blog, algo como http://symblog.dev/1/a-day-with-symfony2 sera mucho mejor. Para lograr esto se slugify el ttulo del blog y lo utilizamos como parte de esta URL. Slugifying el ttulo se eliminarn todos los caracteres no ASCII y los reemplazar con un -.

Actualizando el enrutado
Para comenzar vamos a modificar la regla de enrutado para mostrar la pgina del blog agregando el componente slug. Actualiza la regla de enrutado ubicada en src/Blogger/BlogBundle/Resources/config/routing.yml para que tenga esta apariencia:
# src/Blogger/BlogBundle/Resources/config/routing.yml BloggerBlogBundle_blog_show: pattern: /{id}/{slug} defaults: { _controller: BloggerBlogBundle:Blog:show } requirements: _method: GET id: \d+

El controlador
Como ocurre con el id de componentes existente, el nuevo componente slug se pasa a la accin del controlador como un argumento, por lo tanto vamos a actualizar el controlador que se encuentra en src/Blogger/BlogBundle/Controller/BlogController.php para reflejar esto:
// src/Blogger/BlogBundle/Controller/BlogController.php public function showAction($id, $slug) { // .. }

Truco El orden en que se pasan los argumentos a la accin del controlador no importa, slo lo es su nombre. Symfony2 es capaz de hacer coincidir los argumentos de enrutado con la lista de parmetros por nosotros. A pesar de que todava no hemos utilizado los valores predeterminados del los componentes vale la pena mencionarlos aqu. Si aadimos otro componente a la regla de enrutado podemos especificar un valor predeterminado para que el componente de los valores por defecto `` `` opcin.
BloggerBlogBundle_blog_show:

pattern: /{id}/{slug}/{comments} defaults: { _controller: BloggerBlogBundle:Blog:show, comments: true } requirements: _method: GET id: \d+ public function showAction($id, $slug, $comments) { // .. }

Usando este mtodo, una peticin a http://symblog.dev/1/symfony2-blog resultara en establecer $comments a true en el showAction.

Slugificando el ttulo
Debido a que deseamos generar la bala de el ttulo del blog, que automticamente genera el valor de desecho. We could simply perform this operation at run time on the title field but instead we will store the slug in the Blog entity and persist it to the database.

Updating the Blog entity


Lets add a new member to the Blog entity to store the slug. Update the Blog entity located at src/Blogger/BlogBundle/Entity/Blog.php
// src/Blogger/BlogBundle/Entity/Blog.php class Blog { // .. /** * @ORM\Column(type="string") */ protected $slug; // .. }

Now generate the accessors for the new $slug member. As before run the following task.
$ php app/console doctrine:generate:entities Blogger

Next, lets update the database schema.


$ php app/console doctrine:migrations:diff $ php app/console doctrine:migrations:migrate

To generate the slug value we will use the slugify method from the symfony 1 Jobeet tutorial. Add the slugify method to the the Blog entity located at src/Blogger/BlogBundle/Entity/Blog.php
// src/Blogger/BlogBundle/Entity/Blog.php public function slugify($text) { // replace non letter or digits by $text = preg_replace('#[^\\pL\d]+#u', '-', $text); // trim

$text = trim($text, '-'); // transliterate if (function_exists('iconv')) { $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text); } // lowercase $text = strtolower($text); // remove unwanted characters $text = preg_replace('#[^-\w]+#', '', $text); if (empty($text)) { return 'n-a'; } } return $text;

As we want to auto generate the slug from the title we can generate the slug when the value of the title is set. For this we can update the setTitle accessor to also set the value of the slug. Actualiza la entidad blog ubicada en src/Blogger/BlogBundle/Entity/Blog.php con lo siguiente:
// src/Blogger/BlogBundle/Entity/Blog.php public function setTitle($title) { $this->title = $title; $this->setSlug($this->title); }

Next update the setSlug method to slugify the slug before it is set.
// src/Blogger/BlogBundle/Entity/Blog.php public function setSlug($slug) { $this->slug = $this->slugify($slug); }

Now reload the data fixtures to generate the blog slugs.


$ php app/console doctrine:fixtures:load

Updating the generated routes


Finally we need to update the existing calls for generating routes to the blog show page. There are a number of locations this needs to be updated. Open the homepage template located at src/Blogger/BlogBundle/Resources/views/Page/index.html.twig and replace its contents with the following. There have been 3 edits to the generation of the BloggerBlogBundle_blog_show route in this template. The edits simply pass in the blog slug to the Twig path function.

{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #} {% extends 'BloggerBlogBundle::base.html.twig' %} {% block body %} {% for blog in blogs %} <article class="blog"> <div class="date"><time datetime="{{ blog.created| date('c') }}">{{ blog.created|date('l, F j, Y') }}</time></div> <header> <h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id, 'slug': blog.slug }) }}">{{ blog.title }}</a></h2> </header> <img src="{{ asset(['images/', blog.image]|join) }}" /> <div class="snippet"> <p>{{ blog.blog(500) }}</p> <p class="continue"><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id, 'slug': blog.slug }) }}">Continue reading...</a></p> </div> <footer class="meta"> <p>Comments: <a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id, 'slug': blog.slug }) }}#comments">{{ blog.comments| length }}</a></p> <p>Posted by <span class="highlight">{{ blog.author }}</span> at {{ blog.created|date('h:iA') }}</p> <p>Tags: <span class="highlight">{{ blog.tags }}</span></p> </footer> </article> {% else %} <p>There are no blog entries for symblog</p> {% endfor %} {% endblock %}

Also, one update needs to be made to the Latest Comments section of the sidebar template located at src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig.
{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #} {# .. #} <a href="{{ path('BloggerBlogBundle_blog_show', { 'id': comment.blog.id, 'slug': comment.blog.slug }) }}#comment-{{ comment.id }}"> {{ comment.blog.title }} </a> {# .. #}

Finally the createAction of the CommentController needs to be updated when redirecting to the blog show page on a successful comment posting. Update the CommentController located at src/Blogger/BlogBundle/Controller/CommentController.php with the following.
// src/Blogger/BlogBundle/Controller/CommentController.php public function createAction($blog_id) { // ..

if ($form->isValid()) { // .. array( return $this->redirect($this->generateUrl('BloggerBlogBundle_blog_show', 'id' => $comment->getBlog()->getId(), 'slug' => $comment->getBlog()->getSlug())) . '#comment-' . $comment->getId()

); } } // ..

Now if you navigate to the symblog homepage at http://symblog.dev/app_dev.php/ and click one of the blog titles you will see the blog slug has been appended to the end of the URL.

Entornos
Environments are a very powerful, yet simple feature provided by Symfony2. You may not be aware, but you have been using environments from part 1 of this tutorial. With environments we can configure various aspects of Symfony2 and the application to run differently depending on the specific needs during the applications life cycle. By default Symfony2 comes configured with 3 environments: 1. dev - Development 2. test - Test 3. prod - Production The purpose of these environments is self explanatory, but what about these environments would be configured differently for their individual needs. Cuando desarrolles tu aplicacin es til tener en pantalla la barra de depuracin web mostrndote las excepciones y errores descriptivos, mientras que en produccin no deseas nada de esto. In fact, having this information displayed would be a security risk as a lot of details regarding the internals of the application and the server would be exposed. In production it would be better to display customised error pages with simplified messages, while quietly logging this information to text files. It would also be useful to have the caching layer enabled to ensure the application is running at its best. Having the caching layer enabled in the development environment would be a pain as you would need to empty the cache each time you made changes to config files, etc. The other environment is the test environment. This is used when running tests on the application such as unit or functional test. We havent covered testing yet, but rest assured it will be covered in depth in the coming chapters.

Front Controllers
So far through this tutorial we have been using the development environment only. We have been specifying to run in the development environment by using the app_dev.php front controller when making request to symblog, eg http://symblog.dev/app_dev.php/about. If we have a look at the front controller located at web/app_dev.php you will see the following line:
$kernel = new AppKernel('dev', true);

This line is what kick starts Symfony2 going. It instantiates a new instance of the Symfony2 AppKernel and sets the environment to dev.

In contrast, if we look at the front controller for the production environment located at web/app.php we see the following:
$kernel = new AppKernel('prod', false);

You can see the prod environment is passed into the AppKernel in this instance. The test environment is not supposed to be run via the web browser which is why there is no app_test.php front controller.

Configuration Settings
We have seen above how the front controllers are utilised to change the environment the application runs under. Now we will explore how the various settings are modified while running under each environment. If you have a look at the files in in app/config you will see a number of config.yml files. Specifically there is one main one, called config.yml and 3 others all suffixed with the name of an environment; config_dev.yml, config_test.yml and config_prod.yml. Each of these files is loaded depending on the current environment. If we explore the config_dev.yml file you will see the following lines at the top.
imports: - { resource: config.yml }

The imports directive will cause the config.yml file to be included into this file. The same imports directive can be found at the top of the other 2 environment config files, config_test.yml and config_prod.yml. By including a common set of config settings defined in config.yml we are able to override specific settings for each environment. Podemos ver en el archivo de configuracin del entorno desarrollo ubicado en app/config/config_dev.yml las siguientes lneas configurando el uso de la barra de depuracin web.
# app/config/config_dev.yml web_profiler: toolbar: true

Este ajuste est ausente en el archivo de configuracin del entorno produccin, puesto que no queremos mostrar la barra de depuracin web.

Running in Production
For those of you eager to see your site running in the production environment now is the time. First we need to clear the cache using one of the Symfony2 tasks.
$ php app/console cache:clear --env=prod

Now point your browser to http://symblog.dev/. Notice the app_dev.php front controller is missing. Nota For those of you using the Dynamic Virtual Hosts configuration as linked to in part 1, you will need to add the following to the .htaccess file located at web/.htaccess.
<IfModule mod_rewrite.c> RewriteBase / # .. </IfModule>

Notars que la pgina se ve ms o menos igual, pero algunas importantes caractersticas sustanciales son diferentes. Ahora se ha ido la barra de depuracin web y ya no se muestra el mensaje de excepcin detallado, trata de ir a http://symblog.dev/999.

The detailed exception message has be replaced by a simplified message informing the user of the problem. These exception screens can be customised to match the look and feel of your application. We will explore this in later chapters. Further youll notice the app/logs/prod.log file is filling up with logs regarding the execution of the application. This is a useful point of call when you have issues with the application in production as errors and exceptions wont come to screen any more. Truco How did the request to http://symblog.dev/ end up being routed through the file app.php? Im sure your all used to creating files such as index.html and index.php that act as the sites index, but how would app.php become this. This is thanks to a RewriteRule in the file web/.htaccess
RewriteRule ^(.*)$ app.php [QSA,L]

We can see that this line has a regular expression that matches any text, denoted by ^(.*)$ and passes this to app.php. You maybe on an Apache server that doesnt have the mod_rewrite.c enable. If this is the case you can simply add app.php to the URL such as http://symblog.dev/app.php/. While we have covered the basics of the production environment, we have not covered many other production related tasks such as customising error pages, and deployment to the production server using tools such as capifony. These topics will be covered in later chapters.

Creating New Environments


Finally its worth noting that you can setup your own environments easily in Symfony2. For example, you may want a staging environment that would run on the production server, but output some of the debugging information such as exceptions. This would allow the platform to be tested manually on the actual production server as production and development configurations of servers can differ. While creating a new environment is a simple task, it is outside the scope of this tutorial. There is an excellent article in the Symfony2 cookbook that covers this.

Assetic
The Symfony2 Standard Distribution is bundled with a library for assets management called Assetic. The library was developed by Kris Wallsmith and was inspired by the Python library webassets. Assetic deals with 2 parts of asset management, the assets such as images, stylesheets and JavaScript and the filters that can be applied to these assets. These filters are able to perform useful tasks such as minifying your CSS and JavaScript, passing CoffeeScript files through the

CoffeeScript compiler, and combining asset files together to reduce the number of HTTP request made to the server. Currently we have been using the Twig asset function to include assets into the template as follows.
<link href="{{ asset('bundles/bloggerblog/css/blog.css') }}" type="text/css" rel="stylesheet" />

The calls to the asset function will be replaced by Assetic.

Assets
The Assetic library describes an asset as follows: An Assetic asset is something with filterable content that can be loaded and dumped. An asset also includes metadata, some of which can be manipulated and some of which is immutable. Put simply, the assets are the resources the application uses such as stylesheets and images. Stylesheets Lets begin by replacing the current calls to asset for the stylesheets in the BloggerBlogBundle main layout template. Update the content of the template located at src/Blogger/BlogBundle/Resources/views/layout.html.twig with the following.
{# src/Blogger/BlogBundle/Resources/views/base.html.twig #} {# .. #} {% block stylesheets %} {{ parent () }} {% stylesheets '@BloggerBlogBundle/Resources/public/css/*' %} <link href="{{ asset_url }}" rel="stylesheet" media="screen" /> {% endstylesheets %} {% endblock %} {# .. #}

We have replaced the 2 previous links for CSS files with some Assetic functionality. Using stylesheets from Assetic we have specified that all CSS files in the location src/Blogger/BlogBundle/Resources/public/css should be combined into 1 file and then output. Combining files is a very simple but effective way to optimise your website frontend by reducing the number of files needed. Less files means less HTTP requests to the server. While we used the * to specify all files in the css directory we could have simply listed each file individually as follows.
{# src/Blogger/BlogBundle/Resources/views/base.html.twig #} {# .. #} {% block stylesheets %} {{ parent () }} {% stylesheets '@BloggerBlogBundle/Resources/public/css/blog.css'

'@BloggerBlogBundle/Resources/public/css/sidebar.css' <link href="{{ asset_url }}" rel="stylesheet" media="screen" /> {% endstylesheets %} {% endblock %} {# .. #} %}

The end result in both cases is the same. The first option using the * ensures that when new CSS files are added to the directory, they will always be included in the combined CSS file by Assetic. This may not be the desired functionality for your website, so use either method above to suit your needs. If you have a look at the HTML output via http://symblog.dev/app_dev.php/ you will see the CSS has been included something like this (Notice we are running back in the development environment again).
<link href="/app_dev.php/css/d8f44a4_part_1_blog_1.css" rel="stylesheet" media="screen" /> <link href="/app_dev.php/css/d8f44a4_part_1_sidebar_2.css" rel="stylesheet" media="screen" />

Firstly you maybe wondering why there are 2 files. Above it was stated that Assetic would combine the files into 1 CSS file. This is because we are running symblog in the development environment. We can ask Assetic to run in non-debug mode by setting the debug flag to false as follows.
{# src/Blogger/BlogBundle/Resources/views/base.html.twig #} {# .. #} {% stylesheets '@BloggerBlogBundle/Resources/public/css/*' debug=false %} <link href="{{ asset_url }}" rel="stylesheet" media="screen" /> {% endstylesheets %} {# .. #}

Now if you look at the rendered HTML you will see something like this.
<link href="/app_dev.php/css/3c7da45.css" rel="stylesheet" media="screen" />

Si ves el contenido de este archivo notars que los dos archivos CSS, blog.css y sidebar.css se han combinado en un solo archivo. The filename given to the generated CSS file is randomly generated by Assetic. If you would like to control the name given to the generated file use the output option as follows.
{% stylesheets '@BloggerBlogBundle/Resources/public/css/*' output='css/blogger.css' %} <link href="{{ asset_url }}" rel="stylesheet" media="screen" /> {% endstylesheets %}

Before you continue remove the debug flag from the previous snippet as we want to resume default behavior on the assets. We also need to update the applications base template located at

app/Resources/views/base.html.twig.
{# app/Resources/views/base.html.twig #} {# .. #} {% block stylesheets %} <link href='http://fonts.googleapis.com/css?family=Irish+Grover' rel='stylesheet' type='text/css'> <link href='http://fonts.googleapis.com/css?family=La+Belle+Aurore' rel='stylesheet' type='text/css'> {% stylesheets 'css/*' %} <link href="{{ asset_url }}" rel="stylesheet" media="screen" /> {% endstylesheets %} {% endblock %} {# .. #}

JavaScripts While we currently dont have any JavaScipt files in our application, its usage in Assetic is much the same as using stylesheets.
{% javascripts '@BloggerBlogBundle/Resources/public/js/*' %} <script type="text/javascript" src="{{ asset_url }}"></script> {% endjavascripts %}

Filtros
The real power in Assetic comes from the filters. Filters can be applied to assets or collections of assets. There are a large number of filters provided within the core of the library including the following common filters: 1. 2. 3. 4. 5. CssMinFilter: minifies CSS JpegoptimFilter: optimize your JPEGs Yui\CssCompressorFilter: compresses CSS using the YUI compressor Yui\JsCompressorFilter: compresses JavaScript using the YUI compressor CoffeeScriptFilter: compiles CoffeeScript into JavaScript

There is a full list of available filters in the Assetic Readme. Many of these filters pass the actual task onto another program or library, such as YUI Compressor, so you may need to install/configure the appropriate libraries to use some of the filters. Download the YUI Compressor, extract the archive and copy the file located in the build directory to app/Resources/java/yuicompressor-2.4.6.jar. This assumes you downloaded the 2.4.6 version of the YUI Compressor. If not change your version number accordingly. Next we will configure an Assetic filter to minify the CSS using the YUI Compressor. Update the application config located at app/config/config.yml with the following.
# app/config/config.yml # ..

assetic: filters: yui_css: jar: %kernel.root_dir%/Resources/java/yuicompressor-2.4.6.jar # ..

We have configured a filter called yui_css that will use the YUI Compressor Java executable we placed in the applications resources directory. In order to use the filter you need to specify which assets you want the filter applied to. Update the template located at src/Blogger/BlogBundle/Resources/views/layout.html.twig to apply the yui_css filter.
{# src/Blogger/BlogBundle/Resources/views/base.html.twig #} {# .. #} {% stylesheets '@BloggerBlogBundle/Resources/public/css/*' output='css/blogger.css' filter='yui_css' %} <link href="{{ asset_url }}" rel="stylesheet" media="screen" /> {% endstylesheets %} {# .. #}

Now if you refresh the symblog website and view the files output by Assetic you will notice they have been minified. While minification is great for production servers, it can make debugging difficult, especially when JavaScript is minified. We can disable the minification when running in the development environment by prefixing the filter with a ? as follows.
{% stylesheets '@BloggerBlogBundle/Resources/public/css/*' output='css/blogger.css' filter='?yui_css' %} <link href="{{ asset_url }}" rel="stylesheet" media="screen" /> {% endstylesheets %}

Dumping the assets for production


In production we can dump the asset files using Assetic so they become actual resources on disk ready to be served by the web server. The process of creating the assets through Assetic with every page request can be quite slow, especially when filters are being applied to the assets. Dumping the assets for production ensures that Assetic is not used to serve the assets and instead the preprocessed asset files are served directly by the web server. Run the following task to create dump the asset files.
$ app/console --env=prod assetic:dump

You will notice a number of CSS files were created at web/css including the combined blogger.css file. Now if run the symblog website in the production environment via http://symblog.dev/ the files will be being served directly from this folder. Nota If you dump the asset files to disk and want to revert back to the development environment, you will need to clean up the created asset files in web/ to allow Assetic to recreate them.

Additional Reading
We have only scratched the surface at what Assetic can perform. There are more resources available online especially in the Symfony2 cookbook including: How to Use Assetic for Asset Management How to Minify JavaScripts and Stylesheets with YUI Compressor How to Use Assetic For Image Optimization with Twig Functions How to Apply an Assetic Filter to a Specific File Extension There are also a number of great article written by Richard Miller including: Symfony2: Using CoffeeScript with Assetic Symfony2: A Few Assetic Notes Symfony2: Assetic Twig Functions Truco Its worth mentioning here that Richard Miller has a collection of excellent articles regarding a number of Symfony2 areas on his site including Dependency Injection, Services and the above mentioned Assetic guides. Just search for posts tagged with symfony2

Conclusin
We have covered a number of new areas with regards to Symfony2 including the Symfony2 environments and how to use the Assetic asset library. We also made improvements to the homepage and added some components to the sidebar. In the next chapter we will move on to testing. We will explore both unit and functional testing using PHPUnit. Vamos a ver cmo Symfony2 viene con una serie de clases para ayudarte a escribir pruebas funcionales que simulan peticiones web, nos permiten llenar formularios y hacer clic en enlaces y luego inspeccionar la respuesta devuelta.

[Parte 6] Probando: Unidades y funcionales con PHPUnit

Descripcin
So far we have explored a good amount of ground looking at a number of core concepts with regards to Symfony2 development. Before we continue adding features its time to introduce testing. We will look at how to test individual functions with unit testing and how to ensure multiple components are working correctly together with functional testing. The PHP testing library PHPUnit will be covered as this library is at the centre of the Symfony2 tests. As testing is a large topic it will also be covered in later chapters. By the end of this chapter you will have written a number of tests covering both unit and functional testing. You will have simulated browser requests, populated forms with data, and checked responses to ensure the website pages are outputting correctly. You will also have checked how much coverage your tests have on your applications code base.

Testing in Symfony2
PHPUnit has become the de facto standard for writing tests in PHP, so learning it will benefit you in all your PHP projects. Lets also not forget that most of the topics covered in this chapter are language independent and so can be transferred to other languages you.

Truco If you are planning on writing your own Open Source Symfony2 bundles, you are much more likely to receive interest if your bundle is well tested (and documented). Have a look at the existing Symfony2 bundles available at Symfony2Bundles.

Unit Testing
Unit testing is concerned with ensuring individual units of code function correctly when used in isolation. In an Object Oriented code base like Symfony2, a unit would be a class and its methods. For example, we could write tests for the Blog and Comment Entity classes. When writing unit tests, the test cases should be written independently of other test cases, i.e., the result of test case B should not depend on the result of test case A. It is useful when unit testing to be able to create mock objects that allow you to easily unit test functions that have external dependencies. Mocking allows you to simulate a function call instead of actually executing it. An example of this would be unit testing a class that wraps up an external API. The API class may use a transport layer for communicating with the external API. We could mock the request method of the transport layer to return the results we specify, rather than actually hitting the external API. Unit testing does not test that the components of an application function correctly together, this is covered by the next topic, functional testing.

Functional Testing
Functional testing checks the integration of different components within the application, such as routing, controllers, and views. Functional tests are similar to the manual tests you would run yourself in the browser such as requesting the homepage, clicking a blog link and checking the correct blog is shown. Functional testing provides you with the ability to automate this process. Symfony2 comes complete with a number of useful classes that assist in functional testing including a Client that is able to requests pages and submit forms and DOM Crawler that we can use to traverse the Response from the client. Truco There are a number of software development process that are driven by testing. These include processes such as Test Driven Development (TDD) and Behavioral Driven Development (BDD). While these are out side the scope of this tutorial you should be aware of the library written by everzet that facilitates BDD called Behat. There is also a Symfony2 BehatBundle available to easily integrate Behat into your Symfony2 project.

PHPUnit
As stated above, Symfony2 tests are written using PHPUnit. You will need to install PHPUnit in order to run these tests and the tests from this chapter. For detailed installation instructions refer to the official documentation on the PHPUnit website. To run the tests in Symfony2 you need to install PHPUnit 3.5.11 or later. PHPUnit is a very large testing library, so references to the official documentation will be made where additional reading can be found.

Aserciones
Writing tests is concerened with checking that the actual test result is equal to the expected test result. There are a number of assertion methods available in PHPUnit to assist you with this task. Some of the common assertion methods you will use are listed below.
// Check 1 === 1 is true $this->assertTrue(1 === 1);

// Check 1 === 2 is false $this->assertFalse(1 === 2); // Check 'Hello' equals 'Hello' $this->assertEquals('Hello', 'Hello'); // Check array has key 'language' $this->assertArrayHasKey('language', array('language' => 'php', 'size' => '1024')); // Check array contains value 'php' $this->assertContains('php', array('php', 'ruby', 'c++', 'JavaScript'));

A full list of assertions is available in the PHPUnit documentation.

Running Symfony2 Tests


Before we begin writing some tests, lets look at how we run tests in Symfony2. PHPUnit can be set to execute using a configuration file. In our Symfony2 project this file is located at app/phpunit.xml.dist. As this file is suffixed with .dist, you need to copy its contents into a file called app/phpunit.xml. Truco If you are using a VCS such as Git, you should add the new app/phpunit.xml file to the VCS ignore list. If you have a look at the contents of the PHPUnit configuration file you will see the following.
<!-- app/phpunit.xml --> <testsuites> <testsuite name="Project Test Suite"> <directory>../src/*/*Bundle/Tests</directory> <directory>../src/*/Bundle/*Bundle/Tests</directory> </testsuite> </testsuites>

The following settings configure some directories that are part of our test suite. When running PHPUnit it will look in the above directories for tests to run. You can also pass additional command line arguments to PHPUnit to run tests in specific directories, instead of the test suite tests. You will see how to achieve this later. You will also notice the configuration is specifying the bootstrap file located at app/bootstrap.php.cache. This file is used by PHPUnit to get the testing environment setup.
<!-- app/phpunit.xml --> <phpunit bootstrap = "bootstrap.php.cache" >

Truco For more information regarding configuring PHPUnit with an XML file see the PHPUnit documentation.

Running the Current Tests


As we used one of the Symfony2 generator tasks to create the BloggerBlogBundle back in chapter 1, it also created a controller test for the DefaultController class. We can execute this test by running the following command from the root directory of the project. The -c option specifies that PHPUnit should load its configuration from the app directory.
$ phpunit -c app

Once the testing has completed you should be notified that the tests failed. If you look at the DefaultControllerTest class located at src/Blogger/BlogBundle/Tests/Controller/DefaultControllerTest.php you will see the following content.
<?php // src/Blogger/BlogBundle/Tests/Controller/DefaultControllerTest.php namespace Blogger\BlogBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class DefaultControllerTest extends WebTestCase { public function testIndex() { $client = static::createClient(); $impulsor = $cliente->request('GET', '/hola/Fabien'); $this->assertTrue($crawler->filter('html:contains("Hello Fabien")')>count() > 0); } }

This is a functional test for the DefaultController class that Symfony2 generated. If you remember back to chapter 1, this Controller had an action that handled requests to /hello/ {name}. The fact that we removed this controller is why the above test is failing. Try going to the URL http://symblog.dev/app_dev.php/hello/Fabien in your browser. You should be informed that the route could not be found. As the above test makes a request to the same URL, it will also get the same response, hence why the test fails. Functional testing is a large part of this chapter and will be covered in detail later. As the DefaultController class has been removed, you can also remove this test class. Delete the DefaultControllerTest class located at src/Blogger/BlogBundle/Tests/Controller/DefaultControllerTest.php.

Unit Testing
As explained previously, unit testing is concerned with testing individual units of your application in isolation. When writing unit tests it is recommend that you replicate the Bundle structure under the Tests folder. For example, if you wanted to test the Blog entity class located at src/Blogger/BlogBundle/Entity/Blog.php the test file would reside at src/Blogger/BlogBundle/Tests/Entity/BlogTest.php. An example folder layout would be as follows.
src/Blogger/BlogBundle/ Entity/ Blog.php

Comment.php Controller/ PageController.php Twig/ Extensions/ BloggerBlogExtension.php Tests/ Entity/ BlogTest.php CommentTest.php Controller/ PageControllerTest.php Twig/ Extensions/ BloggerBlogExtensionTest.php

Notice that each of the Test files are suffixed with Test.

Testing the Blog Entity - Slugify method


We begin by testing the slugify method in the Blog entity. Lets write some tests to ensure this method is working correctly. Create a new file located at src/Blogger/BlogBundle/Tests/Entity/BlogTest.php and add the following.
<?php // src/Blogger/BlogBundle/Tests/Entity/BlogTest.php namespace Blogger\BlogBundle\Tests\Entity; use Blogger\BlogBundle\Entity\Blog; class BlogTest extends \PHPUnit_Framework_TestCase { }

We have created a test class for the Blog entity. Notice the location of the file complies with the folder structure mentioned above. The BlogTest class extends the base PHPUnit class PHPUnit_Framework_TestCase. All tests you write for PHPUnit will be a child of this class. Youll remember from previous chapters that the \ must be placed in front of the PHPUnit_Framework_TestCase class name as the class is declared in the PHP public namespace. Now we have the skeleton class for our Blog entity tests, lets write a test case. Test cases in PHPUnit are methods of the Test class prefixed with test, such as testSlugify(). Update the BlogTest located at src/Blogger/BlogBundle/Tests/Entity/BlogTest.php with the following.
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php // .. class BlogTest extends \PHPUnit_Framework_TestCase { public function testSlugify() { $blog = new Blog(); $this->assertEquals('hello-world', $blog->slugify('Hello World')); }

This is a very simple test case. It instantiates a new Blog entity and runs an assertEquals() on the result of the slugify method. The assertEquals() method takes 2 mandatory arguments, the expected result and the actual result. An optional 3rd argument can be passed in to specify a message to display when the test case fails. Lets run our new unit test. Run the following on the command line.
$ phpunit -c app

You should see the following output.


PHPUnit 3.5.11 por Sebastian Bergmann. . Time: 1 second, Memory: 4.25Mb OK (1 test, 1 assertion)

The output from PHPUnit is very simple, Its start by displaying some information about PHPUnit and the outputs a number of . for each test it runs, in our case we are only running 1 test so only 1 . is output. The last statement informs us of the result of the tests. For our BlogTest we only ran 1 test with 1 assertion. If you have color output on your command line you will also see the last line displayed in green showing everything executed OK. Lets update the testSlugify() method to see what happens when the tests fails.
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php // .. public function testSlugify() { $blog = new Blog(); $this->assertEquals('hello-world', $blog->slugify('Hello World')); $this->assertEquals('a day with symfony2', $blog->slugify('A Day With Symfony2')); }

Re run the unit tests as before. The following output will be displayed
PHPUnit 3.5.11 por Sebastian Bergmann. F Time: 0 seconds, Memory: 4.25Mb There was 1 failure: 1) Blogger\BlogBundle\Tests\Entity\BlogTest::testSlugify Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -a day with symfony2 +a-day-with-symfony2 / var/www/html/symblog/symblog/src/Blogger/BlogBundle/Tests/Entity/BlogTest.php:15

FAILURES! Tests: 1, Assertions: 2, Failures: 1.

The output is a bit more involved this time. We can see the . for the run tests is replaced by a F. This tells us the test failed. You will also see the E character output if your test contains Errors. Next PHPUnit notifies us in detail of the failures, in this case, the 1 failure. We can see the Blogger\BlogBundle\Tests\Entity\BlogTest::testSlugify method failed because the Expected and the Actual values were different. If you have color output on your command line you will also see the last line displayed in red showing there were failures in your tests. Correct the testSlugify() method so the tests execute successfully.
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php // .. public function testSlugify() { $blog = new Blog(); $this->assertEquals('hello-world', $blog->slugify('Hello World')); $this->assertEquals('a-day-with-symfony2', $blog->slugify('A Day With Symfony2')); }

Before moving on add some more test for slugify() method.


// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php // .. public function testSlugify() { $blog = new Blog(); $this->assertEquals('hello-world', $blog->slugify('Hello World')); $this->assertEquals('a-day-with-symfony2', $blog->slugify('A Day With Symfony2')); $this->assertEquals('hello-world', $blog->slugify('Hello world')); $this->assertEquals('symblog', $blog->slugify('symblog ')); $this->assertEquals('symblog', $blog->slugify(' symblog')); }

Now we have tested the Blog entity slugify method, we need to ensure the Blog $slug member is correctly set when the $title member of the Blog is updated. Add the following methods to the BlogTest file located at src/Blogger/BlogBundle/Tests/Entity/BlogTest.php.
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php // .. public function testSetSlug() { $blog = new Blog(); $blog->setSlug('Symfony2 Blog'); $this->assertEquals('symfony2-blog', $blog->getSlug());

public function testSetTitle() { $blog = new Blog(); $blog->setTitle('Hello World'); $this->assertEquals('hello-world', $blog->getSlug());

We begin by testing the setSlug method to ensure the $slug member is correctly slugified when updated. Next we check the $slug member is correctly updated when the setTitle method is called on the Blog entity. Run the tests to verify the Blog entity is functioning correctly.

Testing the Twig extension


In the previous chapter we created a Twig extension to convert a \DateTime instance into a string detailing the duration since a time period. Create a new test file located at src/Blogger/BlogBundle/Tests/Twig/Extensions/BloggerBlogExtensionT est.php and update with the following content.
<?php // src/Blogger/BlogBundle/Tests/Twig/Extensions/BloggerBlogExtensionTest.php namespace Blogger\BlogBundle\Tests\Twig\Extensions; use Blogger\BlogBundle\Twig\Extensions\BloggerBlogExtension; class BloggerBlogExtensionTest extends \PHPUnit_Framework_TestCase { public function testCreatedAgo() { $blog = new BloggerBlogExtension(); ); $this->assertEquals("0 seconds ago", $blog->createdAgo(new \DateTime())

$this->assertEquals("34 seconds ago", $blog->createdAgo($this>getDateTime(-34))); $this->assertEquals("1 minute ago", $blog->createdAgo($this>getDateTime(-60))); $this->assertEquals("2 minutes ago", $blog->createdAgo($this>getDateTime(-120))); $this->assertEquals("1 hour ago", $blog->createdAgo($this->getDateTime(3600))); $this->assertEquals("1 hour ago", $blog->createdAgo($this->getDateTime(3601))); $this->assertEquals("2 hours ago", $blog->createdAgo($this>getDateTime(-7200))); // Cannot create time in the future $this->setExpectedException('\Exception'); $blog->createdAgo($this->getDateTime(60)); } protected function getDateTime($delta) { return new \DateTime(date("Y-m-d H:i:s", time()+$delta)); } }

The class is setup much the same as before, creating a method testCreatedAgo() to test the Twig Extension. We introduce another PHPUnit method in this test case, the setExpectedException() method. This method should be called before executing a method you expect to throw an exception. We know that the createdAgo method of the Twig extension cannot handle dates in the future and will throw an \Exception. The getDateTime() method is simply a helper method for creating a \DateTime instance. Notice it is not prefixed with test so PHPUnit will not try to execute it as a test case. Open up the command line and run the tests for this file. We could simply run the test as before, but we can also tell PHPUnit to run tests for a specific folder (and its sub folders) or a file. Run the following command.
$ phpunit -c app src/Blogger/BlogBundle/Tests/Twig/Extensions/BloggerBlogExtensionTest.php

This will run the tests for the BloggerBlogExtensionTest file only. PHPUnit will inform us that the tests failed. The output is shown below.
1) Blogger\BlogBundle\Tests\Twig\Extension\BloggerBlogExtensionTest::testCreatedAgo Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -0 seconds ago +0 second ago / var/www/html/symblog/symblog/src/Blogger/BlogBundle/Tests/Twig/Extensions/Blogge rBlogExtensionTest.php:14

We were expecting the first assertion to return 0 seconds ago but it didnt, the word second was not plural. Lets update the Twig Extension located at src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php to correct this.
<?php // src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php namespace Blogger\BlogBundle\Twig\Extensions; class BloggerBlogExtension extends \Twig_Extension { // .. public function createdAgo(\DateTime $dateTime) { // .. if ($delta < 60) { // Segundos $time = $delta; $duration = $time . " second" . (($time === 0 || $time > 1) ? "s" : "") . " ago"; } // .. } // .. }

Re run the PHPUnit tests. You should see the first assertion passing correctly, but our test case still

fails. Lets examine the next output.


1) Blogger\BlogBundle\Tests\Twig\Extension\BloggerBlogExtensionTest::testCreatedAgo Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -1 hour ago +60 minutes ago / var/www/html/symblog/symblog/src/Blogger/BlogBundle/Tests/Twig/Extensions/Blogge rBlogExtensionTest.php:18

We can see now that the 5th assertion is failing (notice the 18 at the end of the output, this gives us the line number in the file where the assertion failed). Looking at the test case we can see that the Twig Extension has functioned incorrectly. 1 hour ago should have been returned, but instead 60 minutes ago was. If we examine the code in the BloggerBlogExtension Twig extension we can see the reason. We compare the time to be inclusive, i.e., we use <= rather than <. We can also see this is the case when checking for hours. Update the Twig extension located at src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php to correct this.
<?php // src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php namespace Blogger\BlogBundle\Twig\Extensions; class BloggerBlogExtension extends \Twig_Extension { // .. public function createdAgo(\DateTime $dateTime) { // .. else if ($delta < 3600) { // Mins $time = floor($delta / 60); $duration = $time . " minute" . (($time > 1) ? "s" : "") . " ago"; } else if ($delta < 86400) { // Hours $time = floor($delta / 3600); $duration = $time . " hour" . (($time > 1) ? "s" : "") . " ago"; } // .. } } // ..

Now re run all our tests using the following command.


$ phpunit -c app

This runs all our tests, and shows all tests pass successfully. Although we have only written a small

number of unit tests you should be getting a feel for how powerful and important testing is when writing code. While the above errors were minor, they were still errors. Testing also helps any future functionality added to the project breaking previous features. This concludes the unit testing for now. We will see more unit testing in the following chapters. Try adding some of your own unit tests to test functionality that has been missed.

Functional Testing
Now we have written some unit tests, lets move on to testing multiple components together. The first section of the functional testing will involve simulating browser requests to tests the generated responses.

Testing the About page


We begin testing the PageController class for the about page. As the about page is very simple, this is a good place to start. Create a new file located at src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php and add the following content.
<?php // src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php namespace Blogger\BlogBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class PageControllerTest extends WebTestCase { public function testAbout() { $client = static::createClient(); $crawler = $client->request('GET', '/about'); $this->assertEquals(1, $crawler->filter('h1:contains("About symblog")')>count()); } }

We have already seen a Controller test very similar to this when we briefly looked at the DefaultControllerTest class. This is testing the about page of symblog, checking the string About symblog is present in the generated HTML, specifically within the H1 tag. The PageControllerTest class doesnt extend the \PHPUnit_Framework_TestCase as we saw with the unit testing examples, it instead extends the class WebTestCase. This class is part of the Symfony2 FrameworkBundle. As explained before PHPUnit test classes must extend the \PHPUnit_Framework_TestCase, but when extra or common functionality is required across multiple Test cases it is useful to encapsulate this in its own class and have your Test classes extend this. The WebTestCase does exactly this, it provides a number of useful method for running functional tests in Symfony2. Have a look at the WebTestCase file located at vendor/symfony/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase .php, you will see that this class is in fact extending the \PHPUnit_Framework_TestCase class.
// vendor/symfony/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php

abstract class WebTestCase extends \PHPUnit_Framework_TestCase { // .. }

If you look at the createClient() method in the WebTestCase class you can see it creates an instance of the Symfony2 Kernel. Following the methods through you will also notice that the environment is set to test (unless overridden as one of the arguments to createClient() ). This is the test environment we spoke about in the previous chapter. Looking back at our test class we can see the createClient() method is called to get the test up and running. We then call the request() method on the client to simulate a browser HTTP GET request to the url /about (this would be just like you visiting http://symblog.dev/about in your browser). The request gives us a Crawler object back, which contains the Response. The Crawler class is very useful as it lets us traverse the returned HTML. We use the Crawler instance to check that the H1 tag in the response HTML contains the words About symblog. Youll notice that even though we are extending the class WebTestCase we still use the assert method as before (remember the PageControllerTest class is still is child of the \PHPUnit_Framework_TestCase class). Lets run the PageControllerTest using the following command. When writing tests its useful to only execute the tests for the file you are currently working on. As your test suite gets large, running tests can be a time consuming tasks.
$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

You should be greeted with the message OK (1 test, 1 assertion) letting us know that 1 test (the testAboutIndex()) ran, with 1 assertion (the assertEquals()). Try changing the About symblog string to Contact and then re run the test. The test will now fail as Contact wont be found, causing asertEquals to equate to false.
1) Blogger\BlogBundle\Tests\Controller\PageControllerTest::testAboutIndex Failed asserting that <boolean:false> is true.

Revert the string back to About symblog before moving on. The Crawler instance used allows you to traverse either HTML or XML documents (which means the Crawler will only work with responses that return HTML or XML). We can use the Crawler to traverse the generated response using methods such as filter(), first(), last(), and parents(). If you have used jQuery before you should feel right at home with the Crawler class. A full list of supported Crawler traversal methods can be found in the Testing chapter of the Symfony2 book. We will explore more of the Crawler features as we continue.

Homepage
While the test for the about page was simple, it has outlined the basic principles of functional testing the website pages. 1. Create the client 2. Request a page 3. Check the response This is a simple overview of the process, in fact there are a number of other steps we could also do such as clicking links and populating and submitting forms. Lets create a method to test the homepage. We know the homepage is available via the URL / and

that is should display the latest blog posts. Add a new method testIndex() to the PageControllerTest class located at src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php as shown below.
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php public function testIndex() { $client = static::createClient(); $impulsor = $cliente->request('GET', '/'); // Check there are some blog entries on the page $this->assertTrue($crawler->filter('article.blog')->count() > 0); }

You can see the same steps are taken as with the tests for the about page. Run the test to ensure everything is working as expected.
$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

Lets now take the testing a bit further. Part of functional testing involves being able to replicate what a user would do on the site. In order for users to move between pages on your website they click links. Lets simulate this action now to test the links to the show blog page work correctly when the blog title is clicked. Update the testIndex() method in the PageControllerTest class with the following.
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php public function testIndex() { // .. // Find the first link, get the title, ensure this is loaded on the next page $blogLink $blogTitle $crawler = $crawler->filter('article.blog h2 a')->first(); = $blogLink->text(); = $client->click($blogLink->link());

// Check the h2 has the blog title in it $this->assertEquals(1, $crawler->filter('h2:contains("' . $blogTitle .'")')>count()); }

The first thing we do it use the Crawler to extract the text within the first blog title link. This is done using the filter article.blog h2 a. This filter is used return the a tag within the H2 tag of the article.blog article. To understand this better, have a look at the markup used on the homepage for displaying blogs.
<article class="blog"> <div class="date"><time datetime="2011-09-05T21:06:19+01:00">Monday, September 5, 2011</time></div> <header> <h2><a href="/app_dev.php/1/a-day-with-symfony2">A day with Symfony2</a></h2> </header> <!-- .. --> </article>

<article class="blog"> <div class="date"><time datetime="2011-09-05T21:06:19+01:00">Monday, September 5, 2011</time></div> <header> <h2><a href="/app_dev.php/2/the-pool-on-the-roof-must-have-a-leak">The pool on the roof must have a leak</a></h2> </header> <!-- .. --> </article>

You can see the filter article.blog h2 a structure in place in the homepage markup. Youll also notice that there is more than one <article class="blog"> in the markup, meaning the Crawler filter will return a collection. As we only want the first link, we use the first() method on the collection. Finally we use the text() method to extract the link text, in this case it will be the text A day with Symfony2. Next, the blog title link is clicked to navigate to the blog show page. The client click() method takes a link object and returns the Response in a Crawler instance. You should by now be noticing that the Crawler object is a key part to functional testing. The Crawler object now contains the Response for the blog show page. We need to test that the link we navigated took us to the right page. We can use the $blogTitle value we retrieved earlier to check this against the title in the Response. Run the tests to ensure that navigation between the homepage and the blog show pages is working correctly.
$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

Now you have an understanding of how to navigate through the website pages when functional testing, lets move onto testing forms.

Testing the Contact Page


Users of symblog are able to submit contact enquiries by completing the form on the contact page http://symblog.dev/contact. Lets test that submissions of this form work correctly. First we need to outline what should happen when the form is successfully submitted (successfully submitted in this case means there are no errors present in the form). 1. 2. 3. 4. 5. Navigate to contact page Populate contact form with values Submit form Check email was sent to symblog Check response to client contains notification of successful contact

So far we have explored enough to be able to complete steps 1 and 5 only. We will now look at how to test the 3 middle steps. Add a new method testContact() to the PageControllerTest class located at src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php.
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php public function testContact() { $client = static::createClient(); $crawler = $client->request('GET', '/contact');

$this->assertEquals(1, $crawler->filter('h1:contains("Contact symblog")')>count()); // Select based on button value, or id or name for buttons $form = $crawler->selectButton('Submit')->form(); $form['blogger_blogbundle_enquirytype[name]'] $form['blogger_blogbundle_enquirytype[email]'] $form['blogger_blogbundle_enquirytype[subject]'] $form['blogger_blogbundle_enquirytype[body]'] be at least 50 characters long as there is a validation entity'; $crawler = $client->submit($form); $this->assertEquals(1, $crawler->filter('.blogger-notice:contains("Your contact enquiry was successfully sent. Thank you!")')->count()); } = 'name'; = 'email@email.com'; = 'Subject'; = 'The comment body must constrain on the Enquiry

We begin in the usual fashion, making a request to the /contact URL, and checking the page contains the correct H1 title. Next we use the Crawler to select the form submit button. The reason we select the button and not the form is that a form may contain multiple buttons that we may want to click independently. From the selected button we are able to retrieve the form. We are able to set the form values using the array subscript notation []. Finally the form is passed to the client submit() method to actually submit the form. As usual, we receive a Crawler instance back. Using the Crawler response we check to ensure the flash message is present in the returned response. Run the test to check everything is functioning correctly.
$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

The tests failed. We are given the following output from PHPUnit.
1) Blogger\BlogBundle\Tests\Controller\PageControllerTest::testContact Failed asserting that <integer:0> matches expected <integer:1>. / var/www/html/symblog/symblog/src/Blogger/BlogBundle/Tests/Controller/PageControl lerTest.php:53 FAILURES! Tests: 3, Assertions: 5, Failures: 1.

The output is informing us that the flash message could not be found in the response from the form submit. This is because when in the test environment, redirects are not followed. When the form is successfully validated in the PageController class a redirect happens. This redirect is not being followed; We need to explicitly say that the redirect should be followed. The reason redirects are not followed is simple, you may want to check the current response first. We will demonstrate this soon to check the email was sent. Update the PageControllerTest class to set the client to follow the redirect.
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php public function testContact() { // .. $crawler = $client->submit($form); // Need to follow redirect

$crawler = $client->followRedirect(); $this->assertEquals(1, $crawler->filter('.blogger-notice:contains("Your contact enquiry was successfully sent. Thank you!")')->count()); }

No when you run the PHPUnit tests they should pass. Lets now look at the final step of checking the contact form submission process, step 4, checking an email was sent to symblog. We already know that emails will not be delivered in the test environment due to the following configuration.
# app/config/config_test.yml swiftmailer: disable_delivery: true

We can test the emails were sent using the information gathered by the web profiler. This is where the importance of the client not following redirects comes in. The check on the profiler needs to be done before the redirect happens, as the information in the profiler will be lost. Update the testContact() message with the following.
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php public function testContact() { // .. $crawler = $client->submit($form); // Check email has been sent if ($profile = $client->getProfile()) { $swiftMailerProfiler = $profile->getCollector('swiftmailer'); // Only 1 message should have been sent $this->assertEquals(1, $swiftMailerProfiler->getMessageCount()); // Get the first message $messages = $swiftMailerProfiler->getMessages(); $message = array_shift($messages); $symblogEmail = $client->getContainer()>getParameter('blogger_blog.emails.contact_email'); // Check message is being sent to correct address $this->assertArrayHasKey($symblogEmail, $message->getTo()); } // Need to follow redirect $crawler = $client->followRedirect(); $this->assertTrue($crawler->filter('.blogger-notice:contains("Your contact enquiry was successfully sent. Thank you!")')->count() > 0); }

After the form submit we check to see if the profiler is available as it may have been disabled by a configuration setting for the current environment. Truco Remember tests dont have to be run in the test environment, they could be run on the production environment where things like the profiler wont be available.

If we are able to get the profiler we make a request to retrieve the swiftmailer collector. The swiftmailer collector works behind the scenes to gather information about how the emailing service is used. We can use this to get information regarding which emails have been sent. Next we use the getMessageCount() method to check that 1 email was sent. This maybe enough to ensure that at least an email is going to be sent, but it doesnt verify that the email will be sent to the correct location. It could be very embarrassing or even damaging for emails to be sent to the wrong email address. To check this isnt the case we verify the email to address is correct. Now re run the tests to check everything is working correctly.
$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

Testing Adding Blog Comments


Lets now use the knowledge we have gained from the previous tests on the contact page to test the process of submitting a blog comment. Again we outline what should happen when the form is successfully submitted. 1. 2. 3. 4. 5. Navigate to a blog page Populate comment form with values Submit form Check new comment is added to end of blog comment list Also check sidebar latest comments to ensure comment is at top of list

Create a new file located at src/Blogger/BlogBundle/Tests/Controller/BlogControllerTest.php and add in the following.


<?php // src/Blogger/BlogBundle/Tests/Controller/BlogControllerTest.php namespace Blogger\BlogBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class BlogControllerTest extends WebTestCase { public function testAddBlogComment() { $client = static::createClient(); $crawler = $client->request('GET', '/1/a-day-with-symfony'); $this->assertEquals(1, $crawler->filter('h2:contains("A day with Symfony2")')->count()); // Select based on button value, or id or name for buttons $form = $crawler->selectButton('Submit')->form(); $crawler = $client->submit($form, array( 'blogger_blogbundle_commenttype[user]' 'blogger_blogbundle_commenttype[comment]' )); // Need to follow redirect $crawler = $client->followRedirect(); // Check comment is now displaying on page, as the last entry. This ensure comments => 'name', => 'comment',

// are posted in order of oldest to newest $articleCrawler = $crawler->filter('section .previous-comments article')->last(); $this->assertEquals('name', $articleCrawler->filter('header span.highlight')->text()); $this->assertEquals('comment', $articleCrawler->filter('p')->last()>text()); // Check the sidebar to ensure latest comments are display and there is 10 of them $this->assertEquals(10, $crawler->filter('aside.sidebar section')>last() ); >last() >text() } } $this->assertEquals('name', $crawler->filter('aside.sidebar section')->filter('article')->first() ->filter('header span.highlight')); ->filter('article')->count()

We jump straight in this time with the entire test. Before we begin dissecting the code, run the tests for this file to ensure everything is working correctly.
$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/BlogControllerTest.php

PHPUnit should inform you that the 1 test was executed successfully. Looking at the code for the testAddBlogComment() we can see things begin in the usual format, creating a client, requesting a page and checking the page we are on is correct. We then proceed to get the add comment form, and submit the form. The way we populate the form values is slightly different than the previous version. This time we use the 2nd argument of the client submit() method to pass in the values for the form. Truco We could also use the Object Oriented interface to set the values of the form fields. Some examples are shown below.
// Tick a checkbox $form['show_emal']->tick(); // Select an option or a radio $form['gender']->select('Male');

After submitting the form, we request the client should follow the redirect so we can check the response. We use the Crawler again to get the last blog comment, which should be the one we just submitted. Finally we check the latest comments in the sidebar to check the comment is also the first one in the list.

Blog Repository
The last part of the functional testing we will explore in this chapter is testing a Doctrine 2 repository. Create a new file located at src/Blogger/BlogBundle/Tests/Repository/BlogRepositoryTest.php and

add the following content.


<?php // src/Blogger/BlogBundle/Tests/Repository/BlogRepositoryTest.php namespace Blogger\BlogBundle\Tests\Repository; use Blogger\BlogBundle\Repository\BlogRepository; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class BlogRepositoryTest extends WebTestCase { /** * @var \Blogger\BlogBundle\Repository\BlogRepository */ private $blogRepository; public function setUp() { $kernel = static::createKernel(); $kernel->boot(); $this->blogRepository = $kernel->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('BloggerBlogBundle:Blog') } public function testGetTags() { $tags = $this->blogRepository->getTags(); $this->assertTrue(count($tags) > 1); $this->assertContains('symblog', $tags); } public function testGetTagWeights() { $tagsWeight = $this->blogRepository->getTagWeights( array('php', 'code', 'code', 'symblog', 'blog') ); $this->assertTrue(count($tagsWeight) > 1); // Test case where count is over max weight of 5 $tagsWeight = $this->blogRepository->getTagWeights( array_fill(0, 10, 'php') ); $this->assertTrue(count($tagsWeight) >= 1); // Test case with multiple counts over max weight of 5 $tagsWeight = $this->blogRepository->getTagWeights( array_merge(array_fill(0, 10, 'php'), array_fill(0, 2, 'html'), array_fill(0, 6, 'js')) ); $this->assertEquals(5, $tagsWeight['php']); $this->assertEquals(3, $tagsWeight['js']); $this->assertEquals(1, $tagsWeight['html']); // Test empty case $tagsWeight = $this->blogRepository->getTagWeights(array());

} }

$this->assertEmpty($tagsWeight);

As we want to perform tests that require a valid connection to the database we extend the WebTestCase again as this allows us to bootstrap the Symfony2 Kernel. Run this test for this file using the following command.
$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Repository/BlogRepositoryTest.php

Cobertura de cdigo
Before we move on lets quickly touch on code coverage. Code coverage gives us an insight into which parts of the code are executed when the tests are run. Using this we can see the parts of our code that have no tests run on them, and determine if we need to write test for them. To output the code coverage analysis for your application run the following
$ phpunit --coverage-html ./phpunit-report -c app/

This will output the code coverage analysis to the folder phpunit-report. Open the index.html file in your browser to see the analysis output. See the Code Coverage Analysis chapter in the PHPUnit documentation for more information.

Conclusin
We have covered a number of key areas with regards to testing. We have explored both unit and functional testing to ensure our website is functioning correctly. We have seen how to simulate browser requests and how to use the Symfony2 Crawler class to check the Response from these requests. Next we will look at the Symfony2 security component, and more specifically how to use it for user management. Tambin integraremos el FOSUserBundle y lo dejaremos listo para que trabajemos en la seccin de administracin de symblog.