Symblog 5h144s

  • ed by: Luis Olivera
  • 0
  • 0
  • November 2019
  • PDF

This document was ed by and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this report form. Report 3b7i


Overview 3e4r5l

& View Symblog as PDF for free.

More details w3441

  • Words: 33,132
  • Pages: 113


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 versión 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 extrañas directivas de Twig. Ahora vamos a examinar estas directivas de Twig. Empecemos enfocándonos en el HEAD del documento. Veamos de cerca el título: {% block title %}symblog{% endblock %} 3s1e2y symblog

La primer cosa que notamos es esa extraña 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 documentación de Twig. El bloque Twig que definimos en el título hace dos cosas; Establece el identificador del bloque para el título, 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 página para mostrar un blog deseamos que el título de la página refleje el título del blog. Lo podemos lograr extendiendo la plantilla y reemplazando el bloque del título. {% extends '::base.html.twig' %} {% block title %}The blog title goes here{% endblock %}

En el ejemplo anterior hemos extendido la plantilla base de la aplicación que por primera vez definió el bloque título. Notarás 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 aplicación definidas a nivel de app/Resources/views/. A continuación definimos otro bloque de título y pusimos un cierto contenido, en este caso el título del blog. Puesto que la plantilla principal ya contiene un bloque título, este lo sustituye por el nuestro. El título ahora reproduce ‘El título del blog va aquí - symblog’. Esta funcionalidad proporcionada por Twig se utiliza profusamente en la creación de plantillas. En el bloque stylesheet introducimos la siguiente etiqueta de Twig, la etiqueta {{, o la etiqueta que Dice algo.

Esta etiqueta se utiliza para imprimir el valor de una variable o expresión. En el ejemplo anterior esta imprime el valor devuelto por la función asset, el cual nos proporciona una forma portátil para vincular nuestros elementos de la aplicación, tales como CSS, JavaScript e imágenes. La etiqueta {{ también 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 documentación 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 diseño principal que necesitamos listo para personalizarlo. A continuación vamos a añadir algunos estilos. Crea una hoja de estilos en web/css/screen.css y añade 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 creación del diseño 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 aplicación, 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 páginas de nuestro blog tiene sentido llevar a cabo la personalización en este nivel. Puedes preguntarte ¿por qué no sólo ponemos la personalización en la plantilla de la aplicación, ya que estará presente en todas las páginas? Esto es simple, la aplicación no sabe nada sobre el paquete y tampoco debería. 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 página? Una vez más esto es simple, tendríamos que duplicar la barra lateral cada vez que agreguemos una página. Además, esta plantilla de nivel 2 nos da la flexibilidad para que en el futuro agreguemos otras personalizaciones que heredarán todas las plantillas hijas. Por ejemplo, posiblemente queramos cambiar los derechos de autor del pie de página en todas las páginas, este sería un gran lugar para hacerlo. Plantillas de página — Nivel 3¶ Finalmente estamos listos para el diseño del controlador. Estos diseños comúnmente se relacionan con un controlador de acción, es decir, la acción show blog debe tener una plantilla show blog. Vamos a empezar creando el controlador de la página inicial y su plantilla. Ya que se trata de la primera página que estamos creando, es necesario crear el controlador. Crea el controlador en src/Blogger/BlogBundle/Controller/PageController.php con el siguiente contenido: render('BloggerBlogBundle:Page:index.html.twig'); } }

Ahora crea la plantilla para esta acción. Como puedes ver en la acción del controlador vamos a reproducir la plantilla de la página í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 página inicial. Actualiza la configuración 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/.

Deberías ver el diseño básico del blog, con el contenido principal y la barra lateral reflejando los bloques que hemos sustituido en las plantillas correspondientes.

La página Sobre¶ La tarea final de esta parte de la guía es crear una página estática para la página sobre. Esta te mostrará la manera de vincular las páginas y, además, cumplir el criterio de la herencia de tres niveles que hemos adoptado.

La ruta¶ Al crear una nueva página, una de las primeras tareas debería ser crear su ruta. Abre el archivo de enrutado del BloggerBlogBundle ubicado en src/Blogger/BlogBundle/Resources/config/routing.yml y añade 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 continuación abre el controlador de la Página que se encuentra en src/Blogger/BlogBundle/Controller/PageController.php y agrega la acción para procesar la página 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 %}

About symblog 5j4x4z

<article>

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

tellus sed tortor adipiscing fringilla. Donec nulla mauris, mollis

egestas

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.

{% endblock %}

La página sobre no es nada espectacular. Su única acción es reproducir un archivo de plantilla con un insípido contenido. Sin embargo, sí nos lleva a la siguiente tarea.

Enlazando páginas¶ Ahora casi tenemos lista la página. Echa un vistazo a http://symblog.dev/app_dev.php/about para ver esto. En su estado actual no hay forma de que un de tu blog vea la página 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 ecuación de enrutado. Puede coincidir rutas, como hemos visto, y también puede generar las URL de esas rutas. Siempre debes usar las funciones de enrutado que proporciona Symfony2. En tu aplicación nunca deberías tener la tentación de poner lo siguiente: redirect("/"); ?>

Tal vez te estés preguntando qué es lo incorrecto con este enfoque, tal vez la forma en que siempre se enlazan sus páginas. 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 algún momento, quieres cambiar la ubicación de la página de o tendrías 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 todavía, pero que has estado utilizando. El controlador frontal app_dev.php nos da a nuestra aplicación en el entorno dev. Si sustituyes app_dev.php con app.php vas a ejecutar la aplicación en el entorno prod. La importancia de estos entornos se explica más adelante en la guía, 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 páginas, es con los métodos path y url proporcionados por Twig. Ambos son muy similares, excepto que el método url nos proporcionará direcciones URL absolutas. Permite actualizar la plantilla de la aplicación principal que se encuentra en app/Resources/views/base.html.twig para enlazar la página sobre y la página inicial. {% block navigation %} {% endblock %}

Ahora actualiza tu navegador para ver que los enlaces de las páginas Inicio y Sobre trabajan como se esperaba. Si ves el código fuente de las páginas te darás cuenta de que el enlace se ha prefijado con /app_dev.php/. Este es el controlador frontal explicado más arriba, y como puedes ver al usar path lo ha mantenido. Finalmente actualicemos los enlaces del logotipo para que redirijan a la página inicial. Actualiza la plantilla ubicada en app/Resources/views/base.html.twig.

{% block blog_title %}symblog{% endblock %} s242h

{% block blog_tagline %}creating a b Symfony2{% endblock %} 39c5g



Conclusión¶ Hemos cubierto los aspectos básicos respecto a una aplicación de Symfony2 incluyendo cierta personalización de la aplicación y ahora está funcionando. Comenzamos a explorar los conceptos fundamentales detrás de una aplicación Symfony2, incluyendo el enrutado y el motor de plantillas Twig. A continuación veremos cómo se crea la página de o. Esta página es un poco más complicada que la página Sobre, ya que permite a los s interactuar con un formulario web para enviar consultas. El siguiente capítulo introducirá conceptos más avanzados como la validación y formularios.

[Parte 2] — Página de o: Validadores, formularios y correo electrónico¶

Descripción¶ Ahora que tenemos en su lugar las plantillas HTML básicas, es hora de hacer una de las páginas funcionales. Vamos a empezar con una de las páginas más simples; La página de o. Al final de este capítulo tendrás una página de o que permite a los s enviar sus consultas al del sitio. Estas consultas serán enviadas por correo electrónico al del sitio. En este capítulo cubriremos las siguientes áreas: 1. Validadores 2. Formularios 3. Ajuste de los valores de configuración del paquete

Página de o¶ Enrutando¶ Al igual que con la página sobre creada en el capítulo anterior, vamos a comenzar definiendo la ruta de la página de o. Abre el archivo de enrutado del BloggerBlogBundle ubicado

en src/Blogger/BlogBundle/Resources/config/routing.yml y añade la siguiente regla de enrutado. # src/Blogger/BlogBundle/Resources/config/routing.yml BloggerBlogBundle_: pattern: / defaults: { _controller: BloggerBlogBundle:Page: } requirements: _method: GET

No hay nada nuevo aquí, la regla coincide con el patrón /, para el método GET del protocolo HTTP y ejecuta la acción del controlador Page en el BloggerBlogBundle.

Controlador¶ A continuación vamos a añadir la acción para la página o al controlador Page en el BloggerBlogBundle situado en src/Blogger/BlogBundle/Controller/PageController.php. // src/Blogger/BlogBundle/Controller/PageController.php // .. public function Action() { return $this->render('BloggerBlogBundle:Page:.html.twig'); } // ..

Por ahora la acción es muy simple, sólo reproduce la vista de la página o. Más tarde volveremos al controlador.

La vista¶ Crea la vista de la página o en src/Blogger/BlogBundle/Resources/views/Page/.html.twig y añade el siguiente contenido. {# src/Blogger/BlogBundle/Resources/views/Page/.html.twig #} {% extends 'BloggerBlogBundle::base.html.twig' %} {% block title %}{% endblock%} {% block body %}

symblog 14e2z

Want to symblog?

{% endblock %}

Esta plantilla también es muy simple. Extiende la plantilla del diseño de BloggerBlogBundle, remplazando el bloque de título para establecer un título personalizado y define algún contenido para el bloque body.

Enlazando la página¶ Por último tenemos que actualizar el enlace en la plantilla de la aplicación ubicada en app/Resources/views/base.html.twig para enlazar la página de o.

{% block navigation %} {% endblock %}

Si diriges tu navegador a http://symblog.dev/app_dev.php/ y haces clic en el enlace de o en la barra de navegación, deberías ver una página de o muy básica. Ahora que hemos configurado la página correctamente, es hora de empezar a trabajar en el formulario de o. 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 cómo vamos a manejar los datos de la consulta de o.

La entidad o¶ Vamos a empezar creando una clase que representa una consulta de o de un . Queremos capturar información básica 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: 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 sólo define algunas de las propiedades protegidas y métodos para acceder a ellas. No hay nada aquí que defina cómo validar las propiedades, o cómo se relacionan las propiedades con los elementos de formulario. Volveremos a esto más adelante. Nota Vamos a desviarnos un poco para hablar rápidamente 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 automática del estándar 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 automática y correctamente. ¿Cómo hace el cargador automático 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 automático en app/autoloader.php // app/autoloader.php $loader->NamespaceFallbacks(array( __DIR__.'/../src', ));

Esta expresión 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 automático de Symfony2 buscará los archivos necesarios en el directorio src. La carga automática 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. También puedes verificar el espacio de nombres que se ha registrado en el cargador automático como se muestra arriba. Nunca debes ceder a la tentación de corregir esto usando las directivas PHP require o include.

Formularios¶ A continuación 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 código

fuente del componente Form está disponible en Github. Vamos a empezar creando una clase AbstractType que representa el formulario de la consulta. Podríamos 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 través de la aplicación. También nos evita saturar el controlador. Después de todo, se supone que el controlador es simple. Su propósito 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: add('name'); $builder->add('email', 'email'); $builder->add('subject'); $builder->add('body', 'textarea'); }

}

public function getName() { return ''; }

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 definición de campos basándose en los metadatos con que cuenta el campo. Debido a que nuestra entidad Enquiry es tan simple aún no hemos definido los metadatos para el FormBuilder los cuales por omisión tienen el tipo de campo de entrada de texto. Esto es conveniente para la mayoría 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 método 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 acción o para usarlas. Remplaza el contenido de la acción o ubicada en src/Blogger/BlogBundle/Controller/PageController.php con lo siguiente: // src/Blogger/BlogBundle/Controller/PageController.php public function Action() { $enquiry = new Enquiry(); $form = $this->createForm(new EnquiryType(), $enquiry);

$request = $this->getRequest(); if ($request->getMethod() == 'POST') { $form->bindRequest($request); if ($form->isValid()) { // realiza alguna acción, como enviar un correo electrónico // Redirige - Esto es importante para prevenir que el reenvíe

// el formulario si actualiza la página return $this->redirect($this>generateUrl('BloggerBlogBundle_')); } } return $this->render('BloggerBlogBundle:Page:.html.twig', array( 'form' => $form->createView() )); }

Empezamos creando una instancia de la entidad Enquiry. Esta entidad representa los datos de una consulta de o. A continuación, creamos el formulario real. Especificamos el EnquiryType que creamos antes, y le pasamos nuestro objeto entidad Enquiry. El método createForm es capaz de utilizar estos dos indicios para crear una representación del formulario. Ya que esta acción del controlador tratará de mostrar y procesar el formulario presentado, tenemos que verificar el método HTTP. Los formularios presentados se suelen enviar a través del método POST, y nuestro formulario no será la excepción. Si el método de la petición 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 representación de lo que el envió. A continuación hacemos una comprobación para ver si el formulario es válido. Como no hemos especificado ningún validador en este el punto, el formulario siempre será válido. Finalmente especificamos la plantilla a reproducir. Ten en cuenta que ahora le estamos pasando a la plantilla una representación 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.
Reproduciendo el formulario¶ Gracias a los métodos de reproducción 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 personalización que requieras. Para demostrar el poder de los métodos de Twig puedes utilizar el siguiente fragmento de código para reproducir el formulario completo.
{{ form_widget(form) }}


Si bien esto es muy útil para formularios simples y prototipos, tiene sus limitaciones cuando necesitas personalización extendida, que a menudo es el caso con los formularios. Para nuestro formulario de o, vamos a optar por un término medio. Reemplaza el código de la plantilla ubicada en src/Blogger/BlogBundle/Resources/views/Page/.html.twig con el siguiente. {# src/Blogger/BlogBundle/Resources/views/Page/.html.twig #} {% extends 'BloggerBlogBundle::base.html.twig' %} {% block title %}{% endblock%} {% block body %}

symblog 14e2z

Want to symblog?

{{ form_errors(form) }} {{ {{ {{ {{

form_row(form.nombre) }} form_row(form.email) }} form_row(form.subject) }} form_row(form.body) }}

{{ form_rest(form) }}
{% endblock %}

Como puedes ver, utilizamos cuatro nuevos métodos de Twig para reproducir el formulario. El primer método 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 ningún uso para este método, pero es buena práctica 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 método form_errors reproducirá cualquier error del formulario en caso de que la validación haya fallado. El tercer método 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 gráfico real del campo. Por último utilizamos el método form_rest. El cual siempre es una apuesta segura para utilizar el método 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 falsificación de petición en sitios cruzados (CSRF) se explica con detalle en el capítulo Formularios del libro de Symfony2.

Estilizando el formulario¶ Si ves el formulario de o en http://symblog.dev/app_dev.php/ te darás cuenta de que no se ve tan atractivo. Le vamos a añadir algo de estilo para mejorar ese aspecto. Puesto que los estilos son específicos 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 aplicación que queremos utilizar esta hoja de estilos. Podríamos importar la hoja de estilos en la plantilla de o, pero, debido a que más tarde o más temprano, otras plantillas también utilizarán esta hoja de estilos, tiene sentido importarla en el diseño del BloggerBlogBundle que creamos en el capítulo 1. Abre el diseño de src/Blogger/BlogBundle/Resources/views/layout.html.twig y sustitúyelo con el siguiente contenido: {# src/Blogger/BlogBundle/Resources/views/base.html.twig #} {% extends '::base.html.twig' %} {% block stylesheets %} {{ parent() }} {% 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 método 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 añadir nuestra nueva hoja de estilos. Después de todo, no deseamos reemplazar el estilo existente. A fin de que la función asset vincule correctamente los recursos, necesitamos copiar o vincular los recursos en el paquete al directorio web de la aplicación. Esto lo puedes hacer con la siguiente orden: $ php app/console assets:install web --symlink

Nota Si estás usando un sistema operativo que no es compatible con enlaces simbólicos, tal como Windows, tendrás que olvidarte de la opción de enlace simbólicos de la siguiente manera. php app/console assets:install web

Este método en realidad va a copiar los recursos desde los directorios public de todos los paquetes al directorio web de la aplicación. Puesto que los archivos se copian en realidad, será necesario ejecutar esta tarea cada vez que realices un cambio a un recurso público en alguno de tus paquetes. Ahora bien, si actualizas la página del formulario de o estará bellamente decorada.

Truco Si bien la función 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 edición estándar de Symfony2. Esta biblioteca proporciona una gestión de activos más allá de las capacidades estándar de Symfony2. Assetic nos permite ejecutar filtros en los activos para combinarlos automáticamente, minifyzarlos y comprimirlos con gzip.

También puede ejecutar filtros de compresión en imágenes. Además 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 capítulos posteriores.

Fallo en la presentación¶ Si no pudiste reprimir tus ganas de enviar el formulario serás recibido con un error de Symfony2.

Este error nos está diciendo que no hay una ruta que coincida con / para el método POST de HTTP. La ruta sólo acepta peticiones GET y HEAD. Esto se debe a que configuramos la ruta con el requisito del método GET. Actualicemos la ruta de o ubicada en src/Blogger/BlogBundle/Resources/config/routing.yml para permitir también las peticiones POST. # src/Blogger/BlogBundle/Resources/config/routing.yml BloggerBlogBundle_: pattern: / defaults: { _controller: BloggerBlogBundle:Page: } requirements: _method: GET|POST

Truco Tal vez estés preguntándote por qué la ruta permitiría el método HEAD cuando sólo hemos especificado GET. Esto es porque HEAD es una petición GET, pero sólo se devuelven las cabeceras HTTP. Ahora, cuando envíes el formulario deberá funcionar como se esperaba, aunque en realidad no esperes que haga mucho todavía. La página sólo te regresará al formulario de o.

Validadores¶ Los validadores de Symfony2 nos permiten realizar la tarea de validación de datos. La validación es una tarea común cuando se trata de datos de formularios. Asimismo, la validación se debe realizar en los datos antes de guardarlos en una base de datos. El validador de Symfony2 nos permite separar nuestra lógica de validación 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 validación para un objeto. Vamos a empezar actualizando la entidad Enquiry ubicada en src/Blogger/BlogBundle/Entity/Enquiry.php para especificar algunos validadores.

Asegúrate de añadir las 5 nuevas declaraciones use en la parte superior del archivo:
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 método estático loadValidatorMetadata. Este nos proporciona un objeto ClassMetadata. Podemos utilizar este objeto para establecer restricciones a las propiedades de nuestra entidad. La primera declaración aplica la restricción NotBlank a la propiedad name. El validador NotBlank es tan simple como suena, sólo devolverá true si el valor a comprobar no está vacío. A continuación configuramos la validación 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 válido. En la propiedad subject deseamos establecer una restricción 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 validación. También es posible crear restricciones de validación personalizadas. Ahora, cuando envíes el formulario de o, los datos presentados se pasan a través de las restricciones de validación. Inténtalo escribiendo una dirección de correo electrónico incorrecta. Deberías ver un mensaje de error informándote que la dirección de correo electrónico no es válida. 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 electrónico 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 estás utilizando un navegador compatible con HTML5 (lo cual es lo más probable) se te

informará con los mensajes de HTML5 los cuales fuerzan determinadas restricciones. Esta es la validación del lado del cliente y Symfony2 establecerá las restricciones HTML5 apropiadas basándose en los metadatos de tu entidad. Esto lo puedes ver en el elemento de correo electrónico. La salida HTML es:

Este ha utilizado uno de los nuevos tipos de campo de entrada HTML5, email y ha establecido el atributo necesario. La validación 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 validación del lado del cliente. Siempre debes validar los datos presentados en el servidor, debido a que es bastante fácil para un malintencionado eludir la validación del lado del cliente.

Enviando correo electrónico¶ Aunque nuestro formulario de o permitirá a los s enviar consultas, realmente nada sucede con ellos todavía. Actualicemos el controlador para enviar un correo electrónico al del blog. Symfony2 viene con la biblioteca Swift Mailer completa para enviar mensajes de correo electrónico. Swift Mailer es una biblioteca muy potente; Sólo arañaremos 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 edición estándar de Symfony2, sin embargo tenemos que configurar algunos parámetros relacionados a los métodos de envío, y las credenciales. Abre el archivo de parámetros situado en app/parameters.yml y encuentra los ajustes con el prefijo mailer_. mailer_transport="smtp" mailer_host="localhost" mailer_="" mailer_=""

Swift Mailer proporciona una serie de métodos para enviar correos electrónicos, incluyendo el uso de un servidor SMTP, utilizando una instalación local de sendmail, o incluso con una cuenta de GMail. Por simplicidad vamos a utilizar una cuenta de GMail. Actualiza los parámetros con lo siguiente, sustituyendo tu nombre de y contraseña cuando sea necesario. mailer_transport="gmail" mailer_encryption="ssl" mailer_auth_mode="" mailer_host="smtp.gmail.com" mailer_="your_name" mailer_="your_"

Advertencia Ten cuidado si estás usando un sistema de control de versiones (CVS) como Git para tu proyecto, especialmente si es repositorio de público, puesto que tu nombre de y contraseña de Gmail serán enviados al repositorio, y estarán 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 común a este problema es añadir un sufijo al nombre del archivo que contiene información sensible, tal como app/parameters.yml con .dist. A continuación, proporcionar los parámetros predeterminados para la configuración de este archivo y agregar el archivo real, es decir, app/parameters.yml a la lista de ignorados por tu CVS. A continuación, puedes desplegar el

archivo *.dist con tu proyecto y permitir al desarrollador a eliminar la extensión .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 Action() { // .. if ($form->isValid()) { $message = \Swift_Message::newInstance() ->setSubject(' enquiry from symblog') ->setFrom('[email protected]') ->setBody($this>renderView('BloggerBlogBundle:Page:Email.txt.twig', array('enquiry' => $enquiry))); $this->get('mailer')->send($message); $this->get('session')->setFlash('blogger-notice', 'Your enquiry was successfully sent. Thank you!'); // Redirige - Esto es importante para prevenir que el reenvíe // el formulario si actualiza la página return $this->redirect($this->generateUrl('BloggerBlogBundle_')); } // .. }

Cuando utilizas la biblioteca Swift Mailer para crear una instancia de Swift_Message, la cual puedes enviar como correo electrónico. 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 están en un espacio de nombres con una \. Si no colocas este prefijo antes de la clase Swift_Message, PHP debería buscar la clase en el espacio de nombres actual, que en este ejemplo es Blogger\BlogBundle\Controller, provocando que se lance un error. También hemos establecido un mensaje flash en la sesión. Los mensajes flash son mensajes que persisten durante exactamente una petición. Después de eso, Symfony2 los limpia automáticamente. El mensaje flash se mostrará en la plantilla de o para informar al que se ha enviado la consulta. Como los mensajes flash sólo persisten durante exactamente una petición, son perfectos para notificar al del éxito de las acciones anteriores. Para mostrar el mensaje flash tenemos que actualizar la plantilla de o situada en src/Blogger/BlogBundle/Resources/views/Page/.html.twig. Actualiza el contenido de la plantilla con lo siguiente: {# src/Blogger/BlogBundle/Resources/views/Page/.html.twig #} {# resto de la plantilla ... #}

symblog 14e2z

{% if app.session.hasFlash('blogger-notice') %}
{{ app.session.flash('blogger-notice') }}
{% endif %}

Want to symblog?

{# resto de la plantilla ... #}

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

Registrando el correo electrónico del ¶ Symfony2 ofrece un sistema de configuración que podemos utilizar para definir nuestros propios valores. Vamos a utilizar este sistema para establecer la dirección de correo electrónico del del sitio web en lugar de la codificación fija del controlador de arriba. De esa forma puedes volver a usar este valor en otros lugares, sin duplicar tu código. Además, cuando tu blog ha generado mucho tráfico las consultas son demasiadas para que puedas procesarlas actualizando fácilmente la dirección de correo electrónico 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: # dirección de correo electrónico para o del Blogger blogger_blog.emails._email: [email protected]

Al definir parámetros es una buena práctica romper el nombre del parámetro en una serie de componentes. La primera parte debe ser una versión en minúsculas 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 parámetro puede contener cualquier número de partes separadas por un carácter de . (punto). Esto nos permite agrupar lógicamente los parámetros. A fin de que la aplicación Symfony2 utilice los nuevos parámetros, tenemos que importar los ajustes en el archivo de configuración principal de la aplicación 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 importación existente - { resource: @BloggerBlogBundle/Resources/config/config.yml }

La ruta de importación es la ubicación física del archivo en el disco. La directiva @BloggerBlogBundle se resolverá en la ruta del BloggerBlogBundle que es src/Blogger/BlogBundle. Finalmente actualicemos la acción o para utilizar el parámetro. // src/Blogger/BlogBundle/Controller/PageController.php public function Action()

{ // .. if ($form->isValid()) { $message = \Swift_Message::newInstance() ->setSubject(' enquiry from symblog') ->setFrom('[email protected]') ->setTo($this->container>getParameter('blogger_blog.emails._email')) ->setBody($this>renderView('BloggerBlogBundle:Page:Email.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 configuración de la aplicación, fácilmente puedes reemplazar cualquiera de los parámetros importados en la aplicación. Por ejemplo, añadiendo lo siguiente en la parte inferior de app/config/config.yml para redefinir el valor establecido para el parámetro. # app/config/config.yml parameters: # dirección de correo electrónico para o del Blogger blogger_blog.emails._email: [email protected]

Esta personalización nos permite proporcionar parámetros predeterminados razonables para valores del paquete que la aplicación puede sustituir. Nota Si bien es fácil crear parámetros de configuración para el paquete utilizando este método, Symfony2 también proporciona un método en el que expones la configuración semántica de un paquete. Vamos a explorar este método más adelante en la guía.

Creando la plantilla para un correo electrónico¶ El cuerpo del correo electrónico se determina en una plantilla para reproducirla. Crea esa plantilla en src/Blogger/BlogBundle/Resources/view/Page/Email.txt.twig y agrégale lo siguiente: {# src/Blogger/BlogBundle/Resources/view/Page/Email.txt.twig #} A 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 electrónico es sólo la consulta que envía el . Posiblemente también hayas notado que la extensión de esta plantilla es diferente a las otras plantillas que hemos creado. Esta utiliza la extensión .txt.twig. La primera parte de la extensión .txt especifica el formato del archivo a generar. Los formatos más comunes aquí son,

.txt, .html, .css, .js, .xml y .json. La última parte de la extensión especifica el motor de plantillas a usar, en este caso, Twig. Una extensión de .php debería usar PHP para reproducir la plantilla. Ahora, cuando envíes una consulta, se enviará un correo electrónico a la dirección indicada en el parámetro blogger_blog.emails._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 acción en el entorno test. Por omisión, la edición estándar de Symfony2 configura el Swift Mailer para no enviar correos electrónicos cuando se ejecuta en el entorno test. Esto se establece en el archivo de configuración de pruebas ubicado en app/config/config_test.yml. # app/config/config_test.yml swiftmailer: disable_delivery: true

Tal vez sería útil duplicar esta funcionalidad en el entorno dev. Después de todo, durante el desarrollo, no deseas enviar accidentalmente un correo electrónico a la dirección de correo electrónico incorrecta. Para lograr esto, añade la configuración anterior al archivo de configuración dev ubicado en app/config/config_dev.yml. Tal vez te estés preguntando ¿ahora cómo puedo probar el envío de los correos electrónicos y específicamente su contenido, en vista de que ya no serán entregados a una dirección de correo electrónico real? Symfony2 tiene una solución para esto a través de la barra de depuración web. Cuando se le envía un correo electrónico aparecerá un icono de notificación de correo electrónico en la barra de depuración web, el cual tiene toda la información sobre el correo electrónico que Swift Mailer habría entregado.

Si realizas una redirección después de enviar un correo electrónico, al igual que lo hicimos con el formulario de o, tendrás que establecer en true la opción intercept_redirects en app/config/config_dev.yml para ver la notificación de correo electrónico en la barra de depuración web. En su lugar, podríamos haber configurado Swift Mailer para enviar todos los correos electrónicos a una dirección de correo electrónico específica en el entorno dev colocando la siguiente configuración en el archivo de configuración dev ubicado en app/config/config_dev.yml. # app/config/config_dev.yml swiftmailer: delivery_address: [email protected]

Conclusión¶ Hemos demostrado los conceptos detrás de la creación de una de las partes más fundamental de cualquier sitio web; los formularios. Symfony2 viene completo con una excelente biblioteca para validación de formularios y nos permite separar la lógica de validación del formulario para poder utilizarlo en otras partes de la aplicación (tal como el modelo). Además introdujimos los parámetros de configuración personalizados que puede leer nuestra aplicación.

A continuación vamos a ver una gran parte de esta guía, el Modelo. Introduciremos Doctrine 2 y lo utilizaremos para definir el modelo del blog. También vamos a crear la página para mostrar el blog y explorar el concepto de rios.

[Parte 3] — El modelo del Blog: Usando Doctrine 2 y rios¶

Descripción¶ En este capítulo comenzaremos a explorar el modelo del blog. Implementaremos el modelo con el Object Relation Mapper (ORM o Asignador Objeto↔Relacional) Doctrine 2. Doctrine 2 nos proporciona la persistencia de nuestros objetos PHP. También proporciona un dialecto SQL llamado Doctrine Query Language (DQL o lenguaje de consulta doctrine). Además de Doctrine 2, también introduciremos el concepto de datos de prueba. Los datos de prueba (en adelante: rios) son un mecanismo para llenar nuestras bases de datos de desarrollo y probar con datos de prueba adecuados. Al final de este capítulo habrás definido el modelo del blog, actualizando la base de datos para reflejar el nuevo modelo, y creado algunos rios. También habremos construido las bases para la página show del blog.

Doctrine 2: El modelo¶ Para que funcione nuestro blog necesitamos una manera de guardar los datos. Doctrine 2 proporciona un ORM diseñado exactamente para este propósito. El ORM de Doctrine 2 se encuentra en lo alto de una potente Capa de abstracción de base de datos que nos da la abstracción de almacenamiento a través 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 estás familiarizado con algún ORM, vamos a explicar su principio básico. La definición en Wikipedia dice: “La asignación objeto-relacional (más conocida por su nombre en inglés, Object-Relational mapping, o sus siglas ORM, O/RM, y O/R mapping) es una técnica de programación para convertir datos entre sistemas de tipos incompatibles utilizando lenguajes de programación orientados a objetos”. Esto crea, en efecto, una “base de datos de objetos virtual” que se puede utilizar dentro del lenguaje de programación. 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 s, probablemente esta tenga campos como name, , first_name, last_name, email y dob (siglas de day of birth o en Español “fecha de nacimiento”). Con un ORM esta se convierte en una clase con las propiedades name, , first_name, etc., que nos permite llamar a métodos tales como getname() y set(). Los ORM van mucho más allá de esto, sin embargo, también son capaces de recuperar tablas relacionadas para nosotros, ya sea al mismo tiempo que recupera el objeto , o de manera diferida en el futuro. Ahora, consideremos que nuestro tiene algunos amigos con los que está relacionado. Para ello deberíamos tener una tabla de amigos, almacenando la clave primaria de la tabla dentro de ella. Usando el ORM ahora podríamos hacer una llamada como $->getFriends() para recuperar objetos de la tabla de amigos. Si eso no es suficiente, el ORM también se ocupa de guardarlos por lo tanto puedes crear objetos en PHP, llamar a un método 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 familiarizarás mucho más con lo que es un ORM a medida que avancemos a través de esta guía. Nota Si bien en esta guía utilizaremos el ORM de Doctrine 2, puedes optar por usar la biblioteca Object Document Mapper (ODM o Asignador Objeto↔Documento) de Doctrine 2. Hay una serie de variaciones de esta biblioteca incluyendo implementaciones de MongoDB y CouchDB. Ve la página del Proyecto Doctrine para más información. También hay un artículo en el recetario que explica cómo 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 capítulo anterior cuando creamos la entidad Enquiry. Puesto que el objetivo de una entidad consiste en almacenar datos, el sentido común nos dicta que debemos usar una para representar una entrada del blog. Al definir una entidad no estamos diciendo que automáticamente los datos se asignarán a la base de datos. Lo vimos con nuestra entidad Enquiry en la cual los datos contenidos en la entidad se enviaron por correo electrónico sólo al del sitio. Crea un nuevo archivo situado en src/Blogger/BlogBundle/Entity/blog.php y pega el siguiente contenido:
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. Podríamos declarar los captadores y definidores de estos atributos nosotros mismos, pero Doctrine 2 proporciona una tarea para hacerlo. Después de todo, la escritura de métodos de no es la más emocionante de las tareas de codificación. Antes de poder ejecutar esta tarea, le tenemos que informar a Doctrine 2 cómo debe asignar la entidad blog a la base de datos. Tal información 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 ocasión usaremos anotaciones. Es importante

señalar que no es necesario persistir todas las propiedades de la entidad, por lo tanto no deberás proporcionar metadatos para estas. Esto nos da la flexibilidad de elegir sólo 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:
}

/** * @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 información sobre cómo se deben asignar las propiedades a la base de datos. Truco Hemos utilizado un pequeño subconjunto de los tipos de asignación proporcionados por Doctrine 2. Puedes encontrar una lista completa de los tipos de asignación en el sitio web de Doctrine 2. Más adelante presentaremos otros tipos de asignación. Si observaste atentamente el fragmento de código anterior te habrás dado cuenta de que la propiedad $comments no tiene metadatos. Esto es a propósito y se debe a que no necesitamos guardarlo, únicamente proporciona una colección 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 código 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 añade a nuestro blog $comment = new Comment(); $comment->setComment("Symfony2 rocks!"); $blog->addComment($comment);

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

El método addComment sólo añade un nuevo objeto comentario a la propiedad $comnents del blog. Recuperar los comentarios también sería muy sencillo. class Blog { protected $comments = array();

}

public function getComments() { return $this->comments; }

Como puedes ver la propiedad $comments es sólo una lista de objetos Comentario. Doctrine 2 no cambia cómo funciona esto. Doctrine 2 será capaz de llenar automáticamente la propiedad $comments con objetos relacionados con el objeto blog. Ahora que hemos dicho cómo asigna Doctrine 2 las propiedades a la entidad, podemos generar los métodos de usando la siguiente orden:

$ php app/console doctrine:generate:entities Blogger

Notarás que la entidad Blog se ha actualizado con métodos de . Cada vez que hagas algún 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 métodos de existentes en la entidad, por lo tanto tus métodos de existentes nunca se van a remplazar con esta orden. Esto es importante ya que más tarde puedes personalizar algunos de los métodos de predeterminados. Truco Aunque hemos utilizado anotaciones en nuestra entidad, es posible convertir la información de asignación a otros formatos de asignación 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 capítulo 1 de esta guía, deberías haber utilizado el configurador web para establecer la configuración de la base de datos. Si no, actualiza las opciones database_* en el archivo de parámetros situado en app/parameters.yml. Es tiempo de crear la base de datos usando otra tarea de Doctrine 2. Esta tarea sólo 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 representación 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 más potentes migraciones de Doctrine 2. Por ahora vamos a utilizar la tarea schema. Veremos las migraciones de Doctrine en el siguiente capítulo. 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 código SQL necesario para generar el esquema de base de datos para la entidad blog. Además le puedes pasar la opción --dump-sql de la tarea para volcar el SQL en lugar de ejecutarlo contra la base de datos. Si ves tu base de datos notarás que la tabla blog se ha creado, con los campos que configuraste en la información de asignación. Truco

Hemos utilizado una serie de tareas de la línea de ordenes de Symfony2, y en verdadero formato de línea de ordenes, todas las tareas proporcionan ayuda, especificando la opción --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 información de ayuda será la salida que muestra el uso, y opciones disponibles. La mayoría 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 página para mostrar nuestro blog.

La ruta para mostrar el Blog¶ Empecemos creando una ruta para la acción 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 posición id. Esto significa que las direcciones URL similares a http://symblog.co.uk/1 y http://symblog.co.uk/my-blog coincidirán 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 restricción especificando que esta ruta sólo concordará cuando el parámetro id contenga un número entero. Esto se logra con el requisito id: \d+ de la ruta. Ahora sólo el primer ejemplo de URL anterior coincidiría, http://symblog.co.uk/my-blog ya no coincide con esta ruta. También puedes ver que una ruta coincidente ejecutará la acción show del controlador BloggerBlogBundle del Blog. Este controlador aún no lo hemos creado.

El controlador de la acción Show¶ El pegamento entre el modelo y la vista es el controlador, por lo tanto aquí es donde vamos a empezar a crear la página show. Podríamos añadir la acción show a nuestro controlador Page existente, pero como esta página se refiere a la exhibición de una entidad blog sería más adecuado crear su propio controlador en el blog. Crea un nuevo archivo situado en src/Blogger/BlogBundle/Controller/BlogController.php con el siguiente contenido:
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 acción show. Debido a que especificamos un parámetro id en la regla de enrutado en el BloggerBlogBundle_blog_show, este se pasará como argumento del método showAction. Si hubiéramos especificado más parámetros en la regla de enrutado, también se pasarían como argumentos independientes. Truco Las acciones de controlador también pasarán un objeto Symfony\Component\HttpFoundation\Request si lo especificas como un parámetro. Este puede ser útil cuando se trata con formularios. Ya hemos utilizado un formulario en el capítulo 2, pero no utilizamos este método ya que utilizamos un método ayudante de Symfony\Bundle\FrameworkBundle\Controller\Controller así: // src/Blogger/BlogBundle/Controller/PageController.php public function Action() { // .. $request = $this->getRequest(); }

En su lugar lo podríamos haber escrito de la siguiente manera: // src/Blogger/BlogBundle/Controller/PageController.php use Symfony\Component\HttpFoundation\Request; public function Action(Request $request) { // .. }

Ambos métodos consiguen el mismo resultado. Si tu controlador no extendiera la clase ayudante Symfony\Bundle\FrameworkBundle\Controller\Controller no podrías utilizar el primer método. Lo siguiente que necesitamos es recuperar la entidad Blog desde la base de datos. En primer lugar, utilizaremos otro método 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 recuperación 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 método find() pasándole el argumento $id. Este método 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 a una serie de útiles métodos 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 capítulo, cuando requeriremos de consultas más complejas.

La vista¶ Ahora que hemos construido la acción show para el controlador Blog nos podemos enfocar en mostrar la entidad Blog. Como especifica la acción 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">

{{ blog.title }} 6563z

{{ blog.title }} image not found

{{ blog.blog }}



{% endblock %}

Como es de esperar empezamos extendiendo el diseño principal de BloggerBlogBundle. A continuación remplazamos el título de la página con el título del blog. Esto será útil para el SEO puesto que el título de la página del blog es más descriptivo que el título establecido por omisión. Por último vamos a sustituir el bloque body para mostrar el contenido de la entidad Blog. Aquí, de nuevo utilizamos la función asset para reproducir la imagen del blog. Las imágenes del blog se deben colocar en el directorio web/images. CSS¶ A fin de garantizar que el blog muestre una bella página, le tenemos que añadir 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 estás utilizando el método de enlaces simbólicos para hacer referencia a los activos del paquete en el directorio web, ahora debes volver a ejecutar la tarea de instalación 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 acción show echemos un vistazo a la página para ver su apariencia. Apunta tu navegador a http://symblog.dev/app_dev.php/1. ¿No es la página 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. Podrías simplemente insertar una fila en la tabla blog de tu base de datos, pero ahora vamos a utilizar un método mucho mejor; Datos de prueba.

Datos de prueba¶ Podemos usar rios para poblar la base de datos con algunas ‘muestras/datos de prueba’. Para ello utilizaremos la extensión y paquete Fixtures de Doctrine. La extensión y paquete Fixtures de Doctrine no viene con la edición estándar de Symfony2, los tenemos que instalar manualmente. Afortunadamente, esta es una tarea muy sencilla. Abre el archivo deps (por dependencias) ubicado en la raíz de tu proyecto y añade la extensión 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 versión más reciente de cada uno de los repositorios de Github y los instalará en el lugar deseado. Nota Si estás usando una máquina que no tiene instalado Git tendrás que descargar e instalar manualmente la extensión y el paquete.

Extensión doctrine-fixtures: Descarga la versión actual de la extensión data-fixtures desde GitHub y expande su contenido en vendor/doctrine-fixtures. DoctrineFixturesBundle: Descarga la versión actual del paquete DoctrineFixturesBundle desde GitHub y la expande su contenido en vendor/bundles/Symfony/Bundle/DoctrineFixturesBundle. A continuación actualiza el archivo app/autoloader.php para registrar el nuevo espacio de nombres. Dado que DataFixtures también 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 más específicos se deben registrar antes de los menos específicos. // app/autoloader.php // ... $loader->Namespaces(array( // ... 'Doctrine\\Common\\DataFixtures' fixtures/lib', 'Doctrine\\Common' // ... ));

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

Ahora vamos a registrar el DoctrineFixturesBundle en el núcleo situado en app/AppKernel.php: // app/AppKernel.php public function Bundles() { $bundles = array( // ... new Symfony\Bundle\DoctrineFixturesBundle\DoctrineFixturesBundle(), // ... ); // ... }

rios para el Blog¶ Ahora estamos listos para definir algunos rios para nuestros blogs. Crea un archivo de rios en src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php y añádele el siguiente contenido: 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 rios muestra una serie de características importantes cuando se utiliza Doctrine 2, incluyendo la forma en que se persisten las entidades en la base de datos. Vamos a ver cómo 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 gestión 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 señalar que si bien Doctrine 2 ya está al tanto del objeto entidad, aún no lo ha guardado en la base de datos. Para ello se requiere una llamada a $manager->flush(). El método flush provoca que Doctrine 2 realmente interactúe con la base de datos y lleve a cabo las acciones en todas las entidades que está gestionando. Para un mejor rendimiento deberías agrupar las operaciones de Doctrine 2 y limpiarlas todas en conjunto de una sola vez. Así es como lo hicimos en nuestros rios. Creamos cada entidad, pidiendo a Doctrine 2 que las gestionara y luego, al final, limpiamos todas las operaciones.

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

Si echamos un vistazo a la página show en http://symblog.dev/app_dev.php/1 deberías ver el blog apropiado.

Intenta cambiar el parámetro id en la URL a 2. Deberías ver la siguiente entrada del blog. Si echas un vistazo a la URL http://symblog.dev/app_dev.php/100 deberías ver que se ha lanzado una excepción 404 No se ha encontrado. Esperábamos 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 excepción 404 No se ha encontrado? Esto se debe a que la acción show nunca se ejecuta. La URL no coincide con ninguna ruta en la aplicación debido al requisito \d+ que pusimos en la ruta BloggerBlogBundle_blog_show. Es por eso que ves una excepción No hay ruta para "GET /symfony2-blog".

Marcas de tiempo¶ Finalmente en este capítulo vamos a ver las dos propiedades de fecha y hora en la entidad Blog; created y updated. La funcionalidad para estas dos propiedades comúnmente 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 más reciente actualización 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 actualización, después de un guardado exitoso y después de ocurrida una eliminación. 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:
// .. /** * @ORM\Entity * @ORM\Table(name="blog") * @ORM\HasLifecycleCallbacks() */ class Blog { // .. }

Ahora vamos a añadir un método en la entidad Blog para registrar el evento preUpdate. También vamos a añadir un constructor para establecer los valores predeterminado para las propiedades created y updated. 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 rios notarás que las propiedades created y updated se ajustan automáticamente. Truco Debido a que las propiedades autofechables son un requisito común 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. Más adelante en la guía, veremos cómo integrar en nuestra aplicación este paquete. No te reprimas puedes explorar un capítulo sobre este tema en el recetario.

Conclusión¶ Hemos cubierto una serie de conceptos para hacer frente a los modelos de Doctrine 2. También vimos la definición de rios que nos proporciona una forma fácil de obtener datos adecuados para probar mientras desarrollamos nuestra aplicación. A continuación vamos a ver cómo extender más el modelo añadiendo la entidad comentario. Vamos a empezar a construir la página inicial y crearemos un Repositorio personalizado para ello. También vamos a introducir el concepto de Migraciones de Doctrine y cómo interactuar con formularios en Doctrine 2 para permitir que se añadan comentarios a un blog.

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

Descripción¶ En este capítulo construiremos sobre el modelo del blog que definimos en el capítulo anterior. Vamos a crear el modelo para los Comentarios, el cual cómo su nombre indica, se encargará de los comentarios de cada blog. Te presentaremos la creación de relaciones entre modelos, cómo 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. También exploraremos el concepto de Migraciones de Doctrine 2 que ofrece una forma programática para implementar cambios en la base de datos. Al final de este capítulo habrás creado el modelo del Comentario y lo habrás vinculado con el modelo del Blog. Además crearemos la página Inicial, y proporcionaremos la posibilidad de que los s envíen comentarios para un blog.

La página Inicial¶ Vamos a comenzar este capítulo, construyendo la página inicial de la aplicación. Al estilo de un verdadero blogger mostraremos fragmentos de cada entrada del blog, ordenados del más reciente al más antiguo. La entrada completa del blog estará disponible a través de enlaces a la página que muestra el blog. Puesto que ya hemos construido la ruta, el controlador y la vista de la página, 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 (también puedes ejecutar SQL crudo a través de Doctrine 2, pero este método no se recomienda, ya que quita la abstracción 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 acción 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 métodos que ofrece el Generador de consultas. La lista completa de métodos disponibles está en la documentación del Generador de consultas. Un buen lugar para comenzar es con los métodos ayudantes. Estos son los métodos que usaremos, como select(), from() y addOrderBy(). Al igual que con las interacciones previas con Doctrine 2, podemos utilizar la notación abreviada para hacer referencia a la entidad Blog a través 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 método 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 método getResult() que devuelve una colección de entidades Blog. Más adelante veremos que la instancia del DQL tiene una serie de métodos para devolver su resultado, tal como getSingleResult() y getArrayResult(). La vista¶ Ahora tenemos una colección de entidades Blog y necesitamos mostrarlas. Sustituye el contenido de la plantilla de la página 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 b blogs %} <article class="blog">

{{ blog.title }} 6563z

{{ blog.blog(500) }}

Continue reading...



{% else %}

There are no blog entries for symblog

{% 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 código PHP.

getTitle() ?> 5w2c60

There are no blog entries



La estructura de control for..else..endfor es una manera mucho más limpia de lograr esta tarea. La mayoría del código en la plantilla de la página Inicial atañe a la reproducción de la información del blog en HTML. Sin embargo, hay algunas cosas que es necesario tener en cuenta. En primer lugar, usamos la función path de Twig para generar las rutas de la página show del blog. Puesto que la página show del blog requiere que esté presente un id de blog en la URL, lo tenemos que pasar como argumento de la función path. Esto se puede ver con lo siguiente:

{{ blog.title }} 6563z



En segundo lugar reproducimos el contenido del blog utilizando

{{ blog.blog(500 ) }}

. El argumento 500 que pasamos, es la longitud máxima de la entrada en el blog que queremos recibir de vuelta desde la función. Para que esto funcione tenemos que actualizar el método getBlog que Doctrine 2 generó anteriormente para nosotros. Actualiza el método 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 método getBlog debe devolver la entrada completa en el blog, establecemos el parámetro $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/ deberías ver la página Inicial exhibiendo las entradas más recientes del blog. También deberías poder navegar a la

entrada completa del blog por cada entrada haciendo clic en el título 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 gustaría volver a utilizar la consulta en otras partes de la aplicación, sin necesidad de duplicar el código del QueryBuilder. 2. Si duplicamos el código del QueryBuilder, tendríamos que hacer varias modificaciones en el futuro si fuera necesario cambiar la consulta. 3. La separación 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 capítulo anterior, cuando creamos la página show del blog. Utilizamos la implementación predeterminada de la clase Doctrine\ORM\EntityRepository de Doctrine para recuperar una entidad blog desde la base de datos por medio del método 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 ubicación 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 intérprete para el BlogRepository situado en src/Blogger/BlogBundle/Repository/BlogRepository.php.
La clase BlogRepository extiende a la clase EntityRepository la cual ofrece el método find() que utilizamos anteriormente. Por último actualiza la clase BlogRepository, moviendo el código del QueryBuilder desde el controlador Page a la clase BlogRepository. createQueryBuilder('b') ->select('b') ->addOrderBy('b.created', 'DESC'); if (false === is_null($limit)) $qb->setMaxResults($limit);

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

}

Hemos creado el método getLatestBlogs el cual devolverá las entradas más recientes del blog, tanto en la misma forma que lo hizo el controlador como el código QueryBuilder. En la clase Repositorio tenemos directo al Generador de consultas a través del método createQueryBuilder(). También hemos añadido un parámetro $límit 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 teníamos necesidad de especificar la entidad a usar a través del método from(). Eso es porque estamos dentro del BlogRepository el cual está asociado con la entidad blog. Si nos fijamos en la implementación del método createQueryBuilder en la clase EntityRepository podemos ver que el método 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 acción 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 página esta debe mostrar exactamente lo mismo que antes. Todo lo que hemos hecho es reconstruir el código para que las clases correctas realicen las tareas correctas.

Más sobre el modelo: Creando la entidad Comentario¶ Los blogs son sólo la mitad de la historia cuando se trata de la comunicación y debate de ideas. También es necesario permitir a los lectores la posibilidad de comentar las publicaciones del blog. Estos comentarios también 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: 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 capítulo 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. También especificamos que la inversa de este enlace estará disponible a través 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 añadir esta asignación. comments = new ArrayCollection();

}

$this->setCreated(new \DateTime()); $this->setUpdated(new \DateTime());

// .. }

Hay unos cuantos cambios que debemos resaltar aquí. En primer lugar, añadimos metadatos a la propiedad $comments. Recuerda que en el capítulo anterior, no añadimos metadatos para esta propiedad, ya que no queríamos 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. También, toma en cuenta la declaración 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 métodos de . 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 métodos de correctos. También notarás 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. Podríamos 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 extensión Migraciones de Doctrine 2 y el paquete no vienen con la Edición estándar de Symfony2, tenemos que instalarlas manualmente como lo hicimos con la extensión y el paquete Fixtures. Abre el archivo deps ubicado en el directorio raíz de tu proyecto y añade la extensión 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 versión más reciente de cada uno de los repositorios desde Github y los instalará en el lugar solicitado. Nota Si estás usando una máquina que no tiene instalado Git tendrás que descargar e instalar manualmente la extensión y el paquete. Extension doctrine-migrations: Descarga la versión actual del paquete migrations de GitHub y expande su contenido en: vendor/doctrine-migrations. DoctrineMigrationsBundle: Descarga la versión actual del paquete DoctrineMigrationsBundle de GitHub y expande su contenido en: vendor/bundles/Symfony/Bundle/DoctrineMigrationsBundle. A continuación actualiza el archivo app/autoloader.php para registrar el nuevo espacio de nombres. Debido a que las migraciones de Doctrine 2 también están en el espacio de nombres Doctrine\DBAL la debes colocar por encima de la configuración 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 más específicos se deben registrar antes de los menos específicos. // app/autoloader.php // ... $loader->Namespaces(array( // ... 'Doctrine\\DBAL\\Migrations' => __DIR__.'/../vendor/doctrine-migrations/lib', 'Doctrine\\DBAL' => __DIR__.'/../vendor/doctrine-dbal/lib', // ... ));

Ahora vamos a registrar el paquete en el núcleo situado en app/AppKernel.php. // app/AppKernel.php public function Bundles() { $bundles = array( // ... new Symfony\Bundle\DoctrineMigrationsBundle\DoctrineMigrationsBundle(), // ... ); // ... }

Advertencia La biblioteca de Migraciones de Doctrine 2 todavía se encuentra en estado alfa por lo tanto su uso en servidores de producción 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 migración basándonos 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 También deberías notar una nueva tabla en la base de datos llamada migration_versions. Esta almacena los números de versión de cada migración para que la tarea de migración sea capaz de ver qué versión es la base de datos actual. Truco Las migraciones de Doctrine 2 son una gran manera de actualizar la base de datos de producción, los cambios se pueden hacer mediante programación. Esto significa que puedes integrar esta tarea en un guión para actualizar automáticamente la base de datos cuando despliegues una nueva versión de tu aplicación. Las migraciones de Doctrine 2 también nos permiten revertir los cambios creados ya que cada migración tiene los métodos up y down. Para revertir a una versión anterior es necesario especificar el número de versión a que te gustaría 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 añadirle algunos rios. Siempre es buena idea añadir algunos rios 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 configuración, para ello al crear rios para las entidades Comentario tendremos que especificar la entidad Blog a la que pertenecen. Ya creamos los rios para la entidad Blog por lo tanto simplemente podríamos actualizar ese archivo para agregar las entidades Comentario. Esto puede ser manejable —por ahora, pero, ¿qué sucederá después cuando agreguemos s, categorías del blog, y un montón de otras entidades a nuestro paquete? Una mejor manera sería crear un nuevo archivo para los rios de la entidad Comentario. El problema con este enfoque es: ¿cómo podemos acceder a las entidades Blog desde los rios blog? Afortunadamente esto se puede conseguir fácilmente estableciendo referencias a objetos en un archivo de rios donde estos pueden acceder a otros rios. Actualiza la entidad Blog DataFixtures ubicada en src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php con lo siguiente. Los cambios a destacar aquí son la extensión de la clase AbstractFixture y la implementación de OrderedFixtureInterface. También ten en cuenta las dos nuevas declaraciones use para importar esas clases: 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; }

Añadimos las referencias a las entidades Blog a través del método AddReference(). Este primer parámetro es un identificador de referencia que puedes utilizar más tarde al recuperar el objeto. Finalmente debemos implementar el método getOrder() para especificar el orden de carga de los rios. Los blogs se deben cargar antes que los comentarios por lo tanto devolverá 1.

Datos de prueba Comentario¶ Ahora estamos listos para definir algunos rios para nuestra entidad Comentario. Crea un archivo de rios en src/Blogger/BlogBundle/DataFixtures/ORM/CommentFixtures.php y añádele el siguiente contenido:
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->set('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->set('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->set('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->set('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->set('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->set('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->set('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->set('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->set('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->set('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->set('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->set('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->set('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->set('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->set('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->set('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 también extiende a la clase AbstractFixture e implementa la OrderedFixtureInterface. Esto significa que también debes implementar el método getOrder(). Esta vez fijamos el valor de retorno a 2, para garantizar que estos rios se cargarán después de los rios blog. También podemos ver cómo se están utilizando las referencias a las entidades Blog creadas anteriormente. $comment->setBlog($manager->merge($this->getReference('blog-2')));

Ahora estamos listos para cargar los rios 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 método para recuperar los comentarios aprobados más 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:
{ 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 método que debemos crear, recuperará los comentarios de un blog. Para ello tenemos que añadir una cláusula where a nuestra consulta. La cláusula where utiliza un parámetro nombrado que se ajusta con el método setParameter(). Siempre debes usar parámetros 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 podría dejar la consulta abierta a un ataque de inyección SQL.

Controlador del Blog¶ A continuación necesitamos actualizar la acción 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 método new en el CommentRepository para recuperar los comentarios aprobados del blog. También pasamos la colección $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, podríamos colocar la presentación de los comentarios directamente en la plantilla show del blog, pero, debido a que los comentarios tienen su propia entidad, sería mucho mejor separar tal presentación en otra plantilla, e incluir esa plantilla. Esto nos permitiría volver a utilizar la plantilla para reproducir los comentarios en otras partes de la aplicación. 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">

Comments 474c2s

{% include 'BloggerBlogBundle:Comment:index.html.twig' with { 'comments': comments } %} {% 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. Además le podemos pasar cualquier cantidad de argumentos a la plantilla. En este caso, le tenemos que pasar una colección de entidades Comentario para que las reproduzca.

Plantilla para mostrar Comentarios¶ La BloggerBlogBundle:Comment:index.html.twig que estamos incluyendo aún no existe, por lo tanto la tenemos que crear. Dado que esta sólo 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 }}">

<span class="highlight">{{ comment. }} commented

{{ comment.comment }}

{% else %}

There are no comments for this post. Be the first to comment...

{% endfor %}

Como puedes ver iteramos sobre una colección de entidades Comentario y mostramos los comentarios. También presentamos una de las otras agradables funciones de Twig, la función cycle. Esta función se moverá entre los valores de la matriz que se le pasa conforme avanza cada

iteración del bucle. El valor de la iteración actual del bucle se obtiene a través 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 código de bucle. También puedes notar que establecimos un ID HTML para el elemento artículo. Esto, más adelante, nos permitirá crear vínculos permanentes para crear más comentarios.

CSS para mostrar comentarios¶ Por último vamos a añadir 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 página que muestra un blog, por ejemplo, http://symblog.dev/app_dev.php/2 deberías ver los comentarios del blog.

Añadiendo comentarios¶ En la última parte de este capítulo añadiremos la funcionalidad para que los s agreguen comentarios a las publicaciones del blog. Esto será posible a través de un formulario en la página show del blog. Ya te presentamos la creación de formularios en Symfony2 cuando creamos el formulario de o. 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í notarás el uso de la versión dura para especificar una entidad Comentario. Truco Posiblemente habrás notado que también 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. add('') ->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, podríamos empezar a personalizar este tipo, pero, primero vamos a mostrar el formulario.

Mostrando el formulario de comentarios¶ Nuestro primer objetivo es permitir a nuestros s que agreguen comentarios desde la misma página del blog, podríamos crear el formulario en la acción show del controlador Blog y reproducir el formulario directamente en la plantilla show. Sin embargo, sería mucho mejor separar este código como lo hicimos con la visualización 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 método ligeramente diferente al anterior en el que sólo incluimos una plantilla.

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

Añade 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 continuación, 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: 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() }

);

return $this->render('BloggerBlogBundle:Comment:create.html.twig', array( ));

'comment' => $comment, 'form' => $form->createView()

} 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; } }

Creamos dos acciones en el controlador Comment, una para new y otra para create. La acción new tiene que ver con mostrar el formulario de comentarios, la acción create tiene que ver con el procesamiento al presentar el formulario de comentarios. Si bien esto puede parecer un gran plato de código, no hay nada nuevo aquí, ya hemos cubierto todo en el capítulo 2 cuando creamos el formulario de o. No obstante, antes de seguir adelante asegúrate de que entiendes completamente lo que está sucediendo en el controlador Comment.

Validando el formulario¶ No queremos que los s puedan enviar comentarios para los blogs con valores 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: addPropertyConstraint('', 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 como el comentario no deben estar en blanco. También hemos creado la opción 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 continuación 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_widget(form) }}



El propósito de esta plantilla es muy simple, sólo reproduce el formulario de comentarios. Además notarás que el método acción del formulario es POST a la nueva ruta que hemos creado BloggerBlogBundle_comment_create. A continuación vamos a añadir 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 %}

Add comment for blog post "{{ comment.blog.title }}" 2c5h26

{% include 'BloggerBlogBundle:Comment:form.html.twig' with { 'form': form } %} {% endblock %}

Como la acción create del controlador Comment trata con el procesamiento del formulario, también necesitamos poder visualizarlo, puesto que podría haber errores en el formulario. Reutilizamos el BloggerBlogBundle:Comment:form.html.twig para reproducir el formulario real y evitar la duplicidad de código. Ahora vamos a actualizar la plantilla show del blog para reproducir el formulario añadir 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"> {# .. #}

Add Comment l384u

{% render 'BloggerBlogBundle:Comment:new' with { 'blog_id': blog.id } %} {% 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 acción new del controlador BloggerBlogBundle:Comment:new. Si ahora le echas un vistazo a una de las páginas show del blog, como http://symblog.dev/app_dev.php/2 verás que Symfony2 lanza una excepción.

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

Si nos fijamos un poco más en el mensaje de excepción, este nos da algo más de información sobre la naturaleza del por qué se produjo la excepción. Las entidades pasadas al campo de elección deben tener definido un método “__toString()” Esto nos está diciendo que un campo de elección que estamos tratando de pintar no tiene establecido un método __toString() para la entidad asociada con el campo de elección. Un campo de elección es un elemento del formulario que le da al una serie de opciones, como un elemento select (desplegable). Tal vez estés preguntándote ¿dónde diablos estamos pintando un campo de elección en el formulario de comentarios? Si una vez más nos fijamos en la plantilla del formulario de comentarios te darás cuenta de que reproducimos el formulario usando la función {{ form_widget(form) }} de Twig. Esta función devuelve el formulario completo en su forma básica. Por lo tanto vamos a volver a la clase dónde creamos el formulario, la clase CommentType. Podemos ver que se añade una serie de campos al formulario a través del objeto FormBuilder. En particular, estamos agregando un campo blog.

Si recuerdas el capítulo 2, nos habló de cómo el FormBuilder trata de adivinar el tipo de campo a producir basándose en los metadatos relacionados con el campo. A medida que configuramos una relación entre los entidades Comentario y Blog, el FormBuilder ha adivinado que el comentario debe ser un campo choice, el cual permite al especificar la entrada en el blog adjuntándola al comentario. Es por eso que tenemos el campo choice en el formulario, y el porqué Symfony2 está lanzando la excepción. Podemos solucionar este problema implementado el método __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 depuración. Los mensajes de error también proporcionan una traza completa para que puedas ver los pasos que se estaban siguiendo al producirse el error. Ahora, al actualizar la página deberías ver aparecer el formulario de comentarios. También te darás 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 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 basándose en los metadatos que le proporciones. Puesto que hemos suministrado metadatos bastante específicos 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: add('') ->add('comment') ; } // .. }

Ahora, cuando actualices la página únicamente se emiten los campos y comentario. Si envías 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 validación. Entonces, ¿cómo persistimos la entidad Comentario a la base de datos? Ya has visto cómo hacerlo cuando creamos los DataFixtures. Actualiza la acción create del controlador Comentario para persistir la entidad Comentario a la base de datos. 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 sólo trata con objetos PHP, y Doctrine 2 gestiona y persiste esos objetos. No hay conexión directa entre la presentación de un formulario, y la persistencia a la base de datos de la información presentada. Ahora deberías poder añadir comentarios a las entradas del blog.

Conclusión¶ Hemos hecho buenos progresos en este capítulo. Nuestro sitio web de blogs está empezando a funcionar más conforme a nuestras expectativas. Ahora tenemos listos los elementos básicos, la página Inicial y la entidad comentarios. Ahora un puede enviar comentarios a los blogs y leer los comentarios dejados por otros s. Hemos visto cómo crear rios a los cuales se puede hacer referencia a través de múltiples archivos y utilizar las Migraciones de Doctrine 2 para mantener en línea el esquema de la base de datos con los cambios en las entidades. A continuación vamos a ver la construcción de la barra lateral para incluir una nube de etiquetas y comentarios recientes. También 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¶

Descripción¶ En este capítulo continuaremos construyendo la interfaz de para symblog. Vamos a modificar la página inicial para mostrar información acerca de los comentarios de un blog publicado y abordaremos el SEO añadiendo el título del blog a la URL. También 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 producción. 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 capítulo habremos integrado los comentarios a la página 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. También habremos visto cómo ejecutar symblog en el entorno de producción.

La página inicial — Blogs y Comentarios¶ Hasta ahora, la página inicial muestra las entradas más recientes del blog, pero no proporciona ninguna información respecto a los comentarios de los blogs. Ahora que hemos construido la entidad Comentario podemos volver a la página inicial y proporcionarle esta información. Puesto que hemos establecido la relación entre las entidades Blog y Comentario sabemos que Doctrine 2 será capaz de recuperar los comentarios de un blog (recuerda que hemos añadido un miembro $comments a la entidad Blog). Actualicemos la plantilla de la vista de la página inicial situada en src/Blogger/BlogBundle/Resources/views/Page/index.html.twig con lo siguiente: {# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #} {# .. #} {# .. #}

Hemos utilizado el captador comments para recuperar los comentarios del blog y luego depuramos la colección con el filtro length de Twig. Si echas un vistazo a la página inicial vía http://symblog.dev/app_dev.php/ verás que ahora exhibe el número 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. Habíamos logrado esto en el capítulo 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 relación entre blogs y comentarios, pero ¿cómo poblamos el miembro $comments con las entidades Comments relacionadas? Si recuerdas de nuevo el método BlogRepository que hemos creado (mostrado a continuación) obtiene la página inicial con los blogs sin haber hecho ninguna selección 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 depuración web. Ya hemos comenzado a explorar los fundamentos de la barra de depuración web y ahora es tiempo de introducir una de sus características más ú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 depuración web. El número al lado de este icono indica la cantidad de consultas que se ejecutaron en la base de datos para la petición HTTP actual.

Si haces clic en el icono de Doctrine 2 se te presentará información relacionada a las consultas que Doctrine 2 ejecutó en la base de datos para la petición HTTP actual.

Como puedes ver en la captura de pantalla anterior, hay una serie de consultas que se ejecutan para una petición a la página principal. La segunda consulta ejecutada recupera de la base de datos las entidades Blog y se ejecuta como resultado del método getLatestBlogs() en la clase BlogRepository. Siguiendo esta consulta te darás 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 parámetro (el id del blog) en la siguiente línea. Cada una de estas consultas son el resultado de las llamadas a {{ blog.comments }} en la plantilla de la página inicial. Cada vez que se ejecuta

esta función, 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 más 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 código 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') ->left('b.comments', 'c') ->addOrderBy('b.created', 'DESC'); if (false === is_null($limit)) $qb->setMaxResults($limit); return $qb->getQuery() ->getResult(); }

Si ahora actualizas la página web y examinas la salida de Doctrine 2 en la barra de depuración web notarás que el número de consultas se ha reducido. También puedes ver que la tabla de comentarios se ha unido a la tabla blog. La carga diferida y la unión 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 aplicación 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 más información recuperes de la base de datos, más procesamiento tiene que hacer Doctrine 2 para hidratar los objetos entidad. Más datos también significa utilizar más memoria del servidor para almacenar los objetos entidad. Antes de continuar hagamos una pequeña adición a la plantilla de la página inicial para agregar el número de comentarios que acabamos de añadir. Actualiza la plantilla de la página inicial ubicada en src/Blogger/BlogBundle/Resources/views/Page/index.html.twig añadiéndole un enlace para mostrar los comentarios del blog. {# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #} {# .. #} {# .. #}

La barra lateral¶ Actualmente la barra lateral de symblog se está viento un tanto vacía. 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 más comunes. Para lograr esto, necesitamos una manera de recuperar todas las etiquetas de todos los blogs. Para ello, vamos a crear algunos nuevos métodos 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 máximo 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 método getTags(). El método 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 también se barajan para determinar su aleatoriedad de exhibición en la página. Ahora que somos de capaces generar la nube de etiquetas, tenemos que mostrarla. Crea una nueva acción 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 acción es muy simple, esta utiliza los dos nuevos métodos 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">

Tag Cloud 225v5q

{% for tag, weight in tags %} <span class="weight-{{ weight }}">{{ tag }} {% else %}

There are no tags

{% endfor %}



La plantilla también es muy simple. Esta sólo 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 documentación de Twig. Si vuelves a mirar en la plantilla del diseño principal de BloggerBlogBundle, ubicada en `src/Blogger/BlogBundle/Resources/views/layout.html.twig te darás cuenta de que pusimos un marcador de posición para el bloque de la barra lateral. Vamos a sustituirlo ahora

reproduciendo la nueva acción sidebar. Recuerda del capítulo anterior que el método render de Twig reproducirá el contenido de una acción del controlador, en este caso la acción sidebar del controlador Page. {# src/Blogger/BlogBundle/Resources/views/base.html.twig #} {# .. #} {% block sidebar %} {% render "BloggerBlogBundle:Page:sidebar" %} {% endblock %}

Por último vamos a añadir el CSS necesario para la nube de etiquetas. Añade 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 añadido una nueva hoja de estilos necesitamos incluirla. Actualiza la plantilla del diseño 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() }} {% endblock %} {# .. #}

Nota Si no estás usando el método de enlace simbólico para hacer referencia a los activos de tu paquete en el directorio web ahora debes volver a ejecutar la tarea de instalación de activos para copiar el nuevo archivo CSS. $ php app/console assets:install web

Si ahora actualizas la página de symblog en tu navegador verás la nube de etiquetas en la barra lateral. A fin de obtener las etiquetas para reproducirlas con diferentes pesos, posiblemente tengas

que actualizar los rios del blog para que algunas etiquetas se utilicen más que otras.

Comentarios recientes¶ Ahora la nube de etiquetas está en su lugar, también agregaremos el componente de comentarios recientes a la barra lateral. Primero necesitamos una manera de recuperar los comentarios más recientes de los blogs. Para ello vamos a añadir un nuevo método al CommentRepository situado en src/Blogger/BlogBundle/Repository/CommentRepository.php. createQueryBuilder('c') ->select('c') ->addOrderBy('c.id', 'DESC'); if (false === is_null($limit)) $qb->setMaxResults($limit);

}

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

A continuación actualiza la acción de la barra lateral situada en src/Blogger/BlogBundle/Controller/PageController.php para recuperar los comentarios más 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 )); }

Notarás que hemos utilizado un nuevo parámetro denominado blogger_blog.comments.latest_comment_limit para limitar el número de comentarios recuperados. Para crear este parámetro actualiza tu archivo de configuración ubicado en src/Blogger/BlogBundle/Resources/config/config.yml con lo siguiente: # src/Blogger/BlogBundle/Resources/config/config.yml parameters: # .. # Blogger máximo 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">

Latest Comments 2x411o

{% for comment in latestComments %} <article class="comment">

<span class="highlight">{{ comment. }} commented on {{ comment.blog.title }} [<em>]

{{ comment.comment }}

{% else %}

There are no recent comments

{% endfor %}

Si ahora actualizas la página de symblog en tu navegador verás 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 estándar, tal como 04/21/2011. Un enfoque mucho mejor sería mostrar las fechas de los comentarios en términos de cuánto tiempo hace que fue publicado el comentario, tal como publicado hace 3 horas. Podríamos añadir un método a la entidad Comentario para lograrlo y cambiar las plantillas para utilizar este método en lugar de {{ comment.created | date('Ymd h: iA') }}. Debido a que posiblemente desees utilizar esta funcionalidad en otro lugar tendría más sentido ponerla fuera de la entidad Comentario. Puesto que la transformación de fechas es una tarea específica para la capa de la vista, la debemos implementar utilizando el motor de plantillas Twig. Twig nos proporciona esta habilidad, proveyendo una interfaz Extensión. Podemos utilizar la Interfaz extensión en Twig para extender la funcionalidad predeterminada que ofrece. Vamos a crear una nueva extensión de filtro Twig que podamos utilizar de la siguiente manera. {{ comment.created|created_ago }}

Esta devolverá la fecha de creación del comentario en un formato como publicado hace 2 días.

La extensión¶ Crea un archivo para la extensión de Twig situado en src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogExtension.php y actualízalo con el siguiente contenido:
// 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 creación de la extensión es bastante simple. Redefinimos el método getFilters() para devolver cualquier cantidad de filtros que deses estén disponibles. En este caso, estamos creando el filtro created_ago. Entonces registramos este filtro para usar el método 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 extensión¶ Para que la extensión 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 extensión 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">

Latest Comments 2x411o

{% for comment in latestComments %} {# .. #} <em> {# .. #} {% endfor %}

Si ahora diriges tu navegador a http://symblog.dev/app_dev.php/ verás que las fechas de los comentarios recientes utilizan el filtro Twig para pintar el lapso transcurrido desde que fue publicado el comentario. También debemos actualizar los comentarios que aparecen en la página show del blog para mostrarte cómo 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 }}">

<span class="highlight">{{ comment. }} commented

{{ comment.comment }}

{% else %}

There are no comments for this post. Be the first to comment...

{% endfor %}

Truco Hay una serie de útiles extensiones de Twig disponibles a través de la biblioteca Twig-Extensions en GitHub. Si creas una extensión útil envíala a través de una petición de extracción a este depósito y posiblemente sea incluida para que otras personas la usen.

Slugifying la URL¶ Actualmente la URL de cada entrada del blog sólo 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 información sobre el contenido del blog, algo como http://symblog.dev/1/a-day-with-symfony2 sería mucho mejor. Para lograr esto se slugify el título del blog y lo utilizamos como parte de esta URL. Slugifying el título se eliminarán 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 página 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 acción 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 acción del controlador no importa, sólo lo es su nombre. Symfony2 es capaz de hacer coincidir los argumentos de enrutado con la lista de parámetros por nosotros. A pesar de que todavía no hemos utilizado los valores predeterminados del los componentes vale la pena mencionarlos aquí. Si añadimos otro componente a la regla de enrutado podemos especificar un valor predeterminado para que el componente de los valores por defecto `` `` opción. 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 método, una petición a http://symblog.dev/1/symfony2-blog resultaría en establecer $comments a true en el showAction.

Slugificando el título¶ Debido a que deseamos generar la bala de el título del blog, que automáticamente 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 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 b blogs %} <article class="blog">

{{ blog.title }} 6563z

{{ blog.blog(500) }}

Continue reading...

{% else %}

There are no blog entries for symblog

{% 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 #} {# .. #} {{ comment.blog.title }} {# .. #}

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 aplicación es útil tener en pantalla la barra de depuración web mostrándote las excepciones y errores descriptivos, mientras que en producción 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 haven’t 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 ed 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 configuración del entorno desarrollo ubicado en app/config/config_dev.yml las siguientes líneas configurando el uso de la barra de depuración web. # app/config/config_dev.yml web_profiler: toolbar: true

Este ajuste está ausente en el archivo de configuración del entorno producción, puesto que no queremos mostrar la barra de depuración 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. RewriteBase / # ..

Notarás que la página se ve más o menos igual, pero algunas importantes características sustanciales son diferentes. Ahora se ha ido la barra de depuración web y ya no se muestra el mensaje de excepción detallado, trata de ir a http://symblog.dev/999.

The detailed exception message has be replaced by a simplified message informing the 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 you’ll 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? I’m 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 es this to app.php. You maybe on an Apache server that doesn’t 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, ing 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.

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/*' %} {% 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' %}

{% 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).

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 symb 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 %} {% endstylesheets %} {# .. #}

Now if you look at the rendered HTML you will see something like this.

Si ves el contenido de este archivo notarás 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' %} {% 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 %} {% stylesheets 'css/*' %} {% endstylesheets %} {% endblock %} {# .. #}

JavaScripts¶ While we currently don’t 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 }}"> {% 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 Ree. Many of these filters 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. 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 ed 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' %} {% 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' %} {% 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

Conclusión¶ 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 cómo 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¶

Descripción¶ 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. ../src/*/*Bundle/Tests ../src/*/Bundle/*Bundle/Tests

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 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.
= "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. 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 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.
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. You’ll 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 ed 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 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. 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 didn’t, the word second was not plural. Lets update the Twig Extension located at src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php to correct this. 1) ? "s" : "") . " ago"; } // .. } // .. }

Re run the PHPUnit tests. You should see the first assertion ing 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. 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 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. 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 doesn’t 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. You’ll notice that even though we are extending the class WebTestCase we still use the assert method as before ( 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 and then re run the test. The test will now fail as wont be found, causing asertEquals to equate to false. 1) Blogger\BlogBundle\Tests\Controller\PageControllerTest::testAboutIndex Failed asserting that 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 ed 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 would do on the site. In order for s 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">

A day with Symfony2 6t5q



<article class="blog">

The pool on the roof must have a leak 5f1p41



You can see the filter article.blog h2 a structure in place in the homepage markup. You’ll 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 Page¶ s of symblog are able to submit enquiries by completing the form on the page http://symblog.dev/. 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 page Populate form with values Submit form Check email was sent to symblog Check response to client contains notification of successful

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 test() to the PageControllerTest class located at src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php. // src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php public function test() { $client = static::createClient(); $crawler = $client->request('GET', '/');

$this->assertEquals(1, $crawler->filter('h1:contains(" 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';

= 'name'; = ' [email protected]'; = 'Subject'; = 'The comment body must constrain on the Enquiry

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

We begin in the usual fashion, making a request to the / 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 ed 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::test Failed asserting that <0><1>

>

>>>>



>>>>>>>>>>>>>>>



’’





>>>>>>

>>

>

>>>>>>>>>>>>

>>

>

>>>>>

>







>>








>>>>>>>>>>>>>>>>>>

>>>>>>>



>





ó¶



ó¶éóó

6l6m1j

6l6m1j

6l6m1j

6l6m1j
6l6m1j
6l6m1j
6l6m1j
6l6m1j
6l6m1j
1>0>