symblog

113
 Symblog Creando un blog con symfony 2 Creando un blog  en Symfony2IntroducciónEsta guía te llevara a través del proceso en la creación de un sitio web completo caracterizado para blog  usando Symfony2. Utilizaremos la Edición estándar de Symfony2, la cual incluye los  principales componentes necesarios en la construcción de tu propio sitio web. La guía está dividida en varias partes, cada parte cubre diferentes aspectos de Symfony2 y sus componentes. Está destinado a trabajarse de la misma manera que su símil Jobeet de Symfony 1. Partes de la guía[Parte 1] — Configurando Symfony2  y sus plant ill as [Parte 2] — Página de contacto: V alidadores, formularios y correo electrónico [Parte 3] — El modelo del  Blog  : Usando Doctrine 2  y accesorios [Parte 4] — El modelo de comentarios: Agregando comentarios, r epositorios y migraciones de Doctrine  [Parte 5] — Personalizando la vista: extensiones Twig  , la barra lateral y Assetic  [Parte 6] — Probando: Unidades y funcionales con PHPUnit  Sitio web de demostraciónPuedes visitar el sitio web en http://symblog.co.uk/. El código fuente está disponible en Github. De ahí se desprende cada parte de esta guía. CoberturaEsta guía tiene como objetivo cubrir las tareas comunes a que te enfrentas a la hora de crear sitios web utilizando Symfony2. 1. Pa qu etes 2. Con trolado res 3. Pla nti llas (us ando TWIG) 4. Mo de lo - Doctrine 2 5. Mi gr aci ones 6. Accesorio s 7. V ali dad ores 8. Formul ari os 9. En ru tado 10.Gestión de activos 11.C orreo electrónico

Transcript of symblog

Page 1: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 1/113

 

SymblogCreando un blog con symfony 2

Creando un blog en Symfony2¶

Introducción¶

Esta guía te llevara a través del proceso en la creación de un sitio web completo caracterizado para

blog usando Symfony2. Utilizaremos la Edición estándar de Symfony2, la cual incluye los

 principales componentes necesarios en la construcción de tu propio sitio web. La guía está divididaen varias partes, cada parte cubre diferentes aspectos de Symfony2 y sus componentes. Está

destinado a trabajarse de la misma manera que su símil Jobeet de Symfony 1.

Partes de la guía¶

• [Parte 1] — Configurando Symfony2 y sus plantillas

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

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

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

de Doctrine • [Parte 5] — Personalizando la vista: extensiones Twig , la barra lateral y Assetic • [Parte 6] — Probando: Unidades y funcionales con PHPUnit  

Sitio web de demostración¶

Puedes visitar el sitio web en http://symblog.co.uk/. El código fuente está disponible en Github. De

ahí se desprende cada parte de esta guía.

Cobertura¶

Esta guía tiene como objetivo cubrir las tareas comunes a que te enfrentas a la hora de crear sitios

web utilizando Symfony2.

1. Paquetes

2. Controladores

3. Plantillas (usando TWIG)

4. Modelo - Doctrine 2 

5. Migraciones

6. Accesorios

7. Validadores

8. Formularios

9. Enrutado10.Gestión de activos

11.Correo electrónico

Page 2: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 2/113

 

12.Entornos

13.Personalizando páginas de error 

14.Seguridad

15.El usuario y sesiones

16.Generando CRUD

17.Memoria caché

18.Probando19.Desplegando

Symfony2 es altamente personalizable y proporciona una serie de maneras diferentes para realizar la

misma tarea. Algunos ejemplos de esto incluyen la redacción de las opciones de configuración en

YAML, XML, PHP , o anotaciones, y la creación de plantillas con Twig o PHP . Para mantener esta

guía lo más sencilla posible vamos a utilizar YAML y anotaciones para la configuración y Twig para

las plantillas. El Libro de Symfony proporciona una gran fuente de ejemplos sobre cómo usar los

otros métodos. Si deseas contribuir con la realización de los métodos alternativos simplemente

 bifurca el repositorio en Github y envía tus peticiones de atracción :)

Autor¶

Esta guía la está escribiendo dsyph3r .

Colaborando¶

El código fuente y la documentación para esta guía está disponible en Github. Si quieres mejorar y

extender esta guía simplemente bifurca el proyecto y envía tus peticiones de atracción. También

 puedes plantear problemas en el rastreador de GitHub. Si alguien está interesado en crear un diseño

mucho más agradable a la vista, por favor, ¡póngase en contacto http://twitter.com/#!/dsyph3r !

Créditos¶

Un agradecimiento especial a todos los colaboradores de la Documentación oficial de Symfony2.

Esta proporcionó un invaluable recurso de información.

Buscando¶

¿En busca de un tema específico? Usa la búsqueda.

[Parte 1] — Configurando Symfony2 y sus plantillas¶

Descripción¶

Este capítulo cubre los primeros pasos para crear un sitio web Symfony2. Descargaremos y

configuraremos la Edición estándar de Symfony2, crearemos el paquete del Blog y adjuntaremos las

 principales plantillas HTML. Al final de este capítulo habremos configurado un sitio web Symfony2que estará disponible a través de un dominio local, por ejemplo, http://symblog.dev/. El

sitio web contendrá la estructura HTML principal del blog junto con algún soso contenido.

En este capítulo cubriremos las siguientes áreas:

1. La creación de una aplicación Symfony2 

2. Configurando un dominio de desarrollo

3. Los paquetes de Symfony2 

Page 3: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 3/113

 

4. El enrutado

5. Los controladores

6. Las plantillas con Twig  

Descargando e instalando¶

Como hemos dicho vamos a utilizar la edición estándar de Symfony2. Esta distribución viene con

las bibliotecas del núcleo de Symfony2 y los paquetes más comunes que se requieren para crear 

sitios web. Puedes descargar el paquete de Symfony2 de su sitio web. Puesto que no quiero repetir 

la excelente documentación proporcionada por el libro de Symfony2, por favor consulta el capítulo

de la Instalación y configuración de Symfony2  para ver los requisitos detallados. Esto te guiará en

el proceso de cuál es el paquete a descargar, cómo instalar los proveedores necesarios, y la forma

correcta de asignar permisos a los directorios.

Advertencia

Es importante prestar especial atención a la sección Configuración de Permisos en el capítulo de

instalación. Este explica las distintas formas en que puedes asignar permisos a los directoriosapp/cache y app/logs para que el usuario del servidor web y el usuario de línea de ordenes

tengan acceso de escritura a ellos.

Creando un dominio de Desarrollo¶

A efectos de esta guía vamos a utilizar el dominio local http://symblog.dev/ sin embargo,

 puedes elegir cualquier dominio que desees. Estas instrucciones son específicas para Apache y se

supone que ya has configurado Apache y está corriendo en tu máquina. Si te sientes cómodo

configurando dominios locales, o utilizas un servidor web diferente, tal como nginx puedes omitir 

esta sección.

 Nota

Estos pasos se llevaron a cabo en la distribución Fedora de Linux por lo tanto los nombres de ruta,

etc., pueden variar en función de tu sistema operativo.

Vamos a empezar creando un servidor virtual con Apache. Busca el archivo de configuración de

 Apache y anexa los siguientes ajustes, asegurándote de cambiar las rutas de DocumentRoot y

Directory consecuentemente. La ubicación y nombre del archivo de configuración de Apache

 puede variar mucho dependiendo de tu sistema operativo. En Fedora se encuentra ubicado en

/etc/httpd/conf/httpd.conf. Tendrás que editar este archivo con privilegios sudo.

# /etc/httpd/conf/httpd.conf

NameVirtualHost 127.0.0.1

<VirtualHost 127.0.0.1>ServerName symblog.devDocumentRoot "/var/www/html/symblog.dev/web"DirectoryIndex app.php<Directory "/var/www/html/symblog.dev/web">AllowOverride AllAllow from All

</Directory></VirtualHost>

Luego agrega un nuevo dominio en la parte inferior del archivo host ubicado en /etc/hosts.

Una vez más, tendrás que editar este archivo con privilegios sudo.

Page 4: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 4/113

 

# /etc/hosts127.0.0.1 symblog.dev

Por último no olvides reiniciar el servicio Apache. Esto deberá cargar los ajustes de configuración

actualizados que hemos hecho.

$ sudo service httpd restart

Truco

Si te encuentras creando dominios virtuales todo el tiempo, puedes simplificar este proceso, usando

servidores virtuales dinámicos.

Ahora deberías poder visitar http://symblog.dev/app_dev.php/.

 

Si esta es tu primera visita a la página de bienvenida de Symfony2, dedica algún tiempo para ver las

 páginas de demostración. Cada página de demostración ofrece fragmentos de código que muestran

cómo funciona cada página en segundo plano.

 Nota

También notarás una barra de herramientas en la parte inferior de la pantalla de bienvenida. Esta es

la barra de depuración web y te proporcionará información muy valiosa sobre el estado de tu

aplicación. La información incluye el tiempo consumido en la elaboración de la página, el uso de

memoria, las consultas hechas a la base de datos, el estado de autenticación y puedes ver mucho

más desde esta barra de herramientas. Por omisión, la barra de herramientas sólo está visible cuando

se ejecuta en el entorno dev, puesto que proporcionar la barra de herramientas en producción sería

un gran riesgo de seguridad ya que expone una gran cantidad de información interna de tu

aplicación. A través de esta guía, nos referiremos constantemente a la barra de herramientas a

medida que introduzcamos nuevas características.

Page 5: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 5/113

 

Configurando Symfony: La interfaz Web¶

Symfony2 introduce una interfaz web para configurar varios aspectos relacionados con el sitio web,

tal como la configuración de la base de datos. Para este proyecto necesitamos una base de datos, por 

lo tanto esto nos permite empezar utilizando el configurador.

Visita http://symblog.dev/app_dev.php/ y haz clic en el botón Configurar.

Introduce los detalles de configuración de la base de datos (esta guía asume el uso de MySQL, pero puedes elegir cualquier otra base de datos a la que tengas acceso), seguidos —en la siguiente página

 — por la generación de un segmento CSRF . Se te presentará la configuración de los parámetros que

Symfony2 ha generado. Presta especial atención a la notificación en la página, es probable que tu

archivo app/paramaters.yml no se pueda escribir, por lo tanto tendrás que copiar y pegar los

ajustes en el archivo que se encuentra en app/parameters.yml (esta configuración puede

sustituir los ajustes existentes en el archivo).

Paquetes: bloques de construcción de Symfony2¶

Los paquetes son el elemento fundamental de cualquier aplicación Symfony2, de hecho Symfony2 ensí mismo es un paquete. Los paquetes nos permiten separar la funcionalidad proporcionando

unidades de código reutilizable. Ellos encierran todo lo necesario para apoyar el propósito del

 paquete incluyendo controladores, modelo, plantillas, y diversos recursos, como imágenes y CSS .

Vamos a crear un paquete para nuestro sitio web bajo el espacio de nombres Blogger. Si no estás

familiarizado con los espacios de nombres en PHP debes dedicar algún tiempo leyendo sobre ellos,

ya que en Symfony2 se utilizan profusamente, todo es un espacio de nombres. Ve el cargador 

automático de Symfony2 para obtener detalles específicos sobre cómo logra Symfony2 cargar tus

clases automáticamente.

Truco

Una buena comprensión de los espacios de nombres te puede ayudar a eliminar problemas comunesque puedes encontrar cuando la estructuración de directorios no se hace correctamente en las

estructuras del espacio de nombres.

Creando el paquete¶

Para encapsular la funcionalidad del blog vamos a crear un paquete, le daremos un nombre original

 para salir de lo común y corriente, lo llamaremos  Blog . Este contará con todos los archivos y

recursos necesarios, por lo tanto fácilmente se podría instalar en otra aplicación de Symfony2.

Symfony2 ofrece una serie de tareas que nos ayudarán a realizar las operaciones más comunes. Una

de estas tareas es el generador de paquetes.

Para arrancar el generador de paquetes ejecuta la siguiente orden. Se te presentará una serie de

instrucciones que te permitirán la manera de configurar tu paquete. En esta ocasión, vamos a utilizar 

el valor predeterminado para cada pregunta.

$ php app/console generate:bundle --namespace=Blogger/BlogBundle --format=yml

Una vez finalizado el generador, Symfony2 habrá construido el diseño básico para el paquete. Aquí

debemos tener en cuenta unos cuantos cambios importantes.

Truco

 No es obligatorio que utilices el generador de tareas proporcionado por Symfony2, este simplemente

está ahí para ayudarte. Podrías haber creado manualmente la estructura de directorios y los archivosdel paquete. Si bien no es obligatorio usar los generadores, proporcionan algunos beneficios, como

son la rapidez y ejecución de todas las tareas necesarias para conseguir todo lo necesario en el

Page 6: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 6/113

 

 paquete y funcionando. Un ejemplo de ello es el registro del paquete.

Registrando el paquete¶

 Nuestro nuevo paquete BloggerBlogBundle automáticamente se ha registrado en el núcleo de

nuestra aplicación situado en app/AppKernel.php. Symfony2 nos obliga a registrar todos los

 paquetes que necesita usar la aplicación. También notarás que algunos paquetes se registran sólocuando estás en el entorno dev o test. Cargar esos paquetes en el entorno prod (por 

‘producción’) sería introducir una sobrecarga adicional de cierta funcionalidad que no se utilizaría.

El siguiente fragmento de código muestra cómo se ha registrado el paquete

BloggerBlogBundle.

// app/AppKernel.phpclass AppKernel extends Kernel{

public function registerBundles(){

$bundles = array(// ..

new Blogger\BlogBundle\BloggerBlogBundle(),);// ..

return $bundles;}

// ..}

Enrutado¶

El enrutado del paquete se ha importado al archivo de enrutado principal de la aplicación ubicado

en app/config/routing.yml.

# app/config/routing.ymlBloggerBlogBundle:

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

La opción prefix nos permite montar todo el enrutado de BloggerBlogBundle con un

 prefijo. En nuestro caso hemos optado por montarlo en el valor predeterminado /. Si por ejemplo

quisieras que todas tus rutas llevaran el prefijo /blogger sólo tienes que cambiar el prefijo a

prefix: /blogger.

Estructura predeterminada¶

Por omisión el diseño del paquete se crea bajo el directorio src. Esto comienza en el nivel superior 

con el directorio Blogger, el cual corresponde directamente con el espacio de nombres

Blogger, en el que hemos creado nuestro paquete. Bajo este, tenemos el directorio BlogBundleel cual contiene el paquete real. Examinaremos el contenido de este directorio a medida que

vayamos avanzando. Si estás familiarizado con las plataformas MVC , algunos de los directorios se

explican por sí mismos.

El controlador predeterminado¶

Como parte del generador de paquetes, Symfony2 ha creado un controlador predeterminado.

Podemos ejecutar este controlador, visita la dirección

Page 7: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 7/113

 

http://symblog.dev/app_dev.php/hello/symblog. Deberías ver una página con un

simple saludo. Intenta cambiando la parte symblog de la URL por tu nombre. Podemos examinar 

cómo se generó esta página a un alto nivel.

Enrutado¶

El archivo de enrutado del BloggerBlogBundle ubicado ensrc/Blogger/BlogBundle/Recursos/config/routing.ymlcontiene la siguiente

regla de enrutado.

# src/Blogger/BlogBundle/Resources/config/routing.ymlBloggerBlogBundle_homepage:

pattern: /hello/{name}defaults: { _controller: BloggerBlogBundle:Default:index }

La ruta se compone de un patrón y algunas opciones predeterminadas. El patrón se compara con la

URL, y las opciones defaults especifican el controlador que se ejecutará si coincide la ruta. En

el patrón /hello/{name}, el marcador de posición {name} coincide con cualquier valor puesto

que no se han establecido requisitos específicos. La ruta tampoco especifica ningún formato,método HTML, o región. Puesto que no existen requisitos para métodos HTTP , todas las peticiones

GET , POST , PUT , etc., serán elegibles para concordar con el patrón.

Si la ruta reúne todos los criterios especificados, será ejecutada por el _controller de la opción

defaults. La opción _controller especifica el nombre lógico del controlador que permite a

Symfony2 asignarla a un archivo específico. El ejemplo anterior hará que la acción index en el

controlador Default ubicado en

src/Blogger/BlogBundle/Controller/DefaultController.phpse ejecute.

El controlador¶

El controlador en este ejemplo es muy sencillo. La clase DefaultController extiende la clase

Controller que proporciona algunos métodos útiles, como el método render que utilizaremos

a continuación. Debido a que nuestra ruta define un marcador de posición este se pasa a la acción

como el argumento $name. La acción no hace más que llamar al método render especificando la

 plantilla index.html.twig ubicada en el directorio Default de las vistas del

BloggerBlogBundle. El formato del nombre de la plantilla es paquete:controlador:plantilla.

En nuestro ejemplo, este es BloggerBlogBundle:Default:index.html.twig el cual

designa a la plantilla index.html.twig, ubicada en el directorio Default de las vistas del

BloggerBlogBundle, o físicamente en el archivo

src/Blogger/BlogBundle/Resources/views/Default/index.html.twig.

Puedes utilizar diferentes variaciones del formato de la plantilla para reproducir plantillas endiferentes lugares de tu aplicación y sus paquetes. Veremos esto más adelante en este capítulo.

También le pasamos la variable $name a la plantilla a través de una matriz de opciones.

<?php// src/Blogger/BlogBundle/Controller/DefaultController.php

namespace Blogger\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DefaultController extends Controller

{ public function indexAction($name){

return $this->render('BloggerBlogBundle:Default:index.html.twig',

Page 8: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 8/113

 

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

}

La plantilla (la Vista)¶

Como puedes ver la plantilla es muy simple. Esta imprime Hello seguido por el argumentonombre pasado desde el controlador.

{# src/Blogger/BlogBundle/Resources/views/Default/index.html.twig #}Hello {{ name }}!

Limpiando¶

Puesto que no necesitamos algunos de los archivos creados por el generador de paquetes, podemos

limpiar un poco.

Puedes eliminar el archivo del controlador 

src/Blogger/BlogBundle/Controller/DefaultController.php, junto con eldirectorio de la vista y su contenido en

src/Blogger/BlogBundle/Resources/views/Default/. Por último, elimina la ruta

definida en src/Blogger/BlogBundle/Resources/config/routing.yml

Plantillas¶

Cuando utilizamos Symfony2, tenemos 2 opciones predeterminadas para el motor de plantillas;

Twig y PHP . Incluso, podrías optar por no usar ninguno de estos y elegir una biblioteca diferente.

Esto es posible gracias al contenedor de inyección de dependencias de Symfony2. Vamos a usar 

Twig como nuestro motor de plantillas por una serie de razones:1. Twig es rápido — Las plantillas Twig se compilan hasta clases PHP por lo que hay muy poca

sobrecarga al usar las plantillas Twig .2. Twig es conciso — Twig nos permite realizar funcionalidad de plantillas con muy poco

código. Comparando esto con PHP en que estas son unas declaraciones a ser muy

detalladas.

3. Twig admite la herencia entre plantillas — Esto personalmente es uno de mis favoritos. Las

 plantillas tienen la capacidad de extender y sustituir otras plantillas, lo cual permite a las

 plantillas hijas cambiar los valores predeterminados proporcionados por sus padres.

4. Twig es seguro — De manera predeterminada Twig tiene activado el escape de toda su

 producción, e incluso proporciona un entorno de área de seguridad para plantillas

importadas.

5. Twig es extensible — Twig viene con un montón de funcionalidades básicas comunes que se

espera tenga un motor de plantillas, pero para aquellas ocasiones donde se requiere una

funcionalidad adicional a medida, fácilmente puedes extender a Twig .

Estos sólo son algunos de los beneficios de Twig . Para más razones por las que debes utilizar Twig ve el sitio oficial de Twig.

Estructurando el diseño¶

Debido a que Twig admite la herencia entre plantillas, vamos a utilizar el enfoque de la herencia de

tres niveles. Este enfoque nos permite modificar la vista en tres distintos niveles dentro de laaplicación, lo cual nos da un montón de espacio para nuestras personalizaciones.

Page 9: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 9/113

 

La plantilla principal — Nivel 1¶

Vamos a empezar creando nuestra plantilla básica a nivel de bloques para symblog. Aquí,

necesitaremos dos archivos, la plantilla y la hoja de estilo — CSS . Puesto que Symfony2 es

compatible con HTML5 también lo vamos a utilizar.

<!-- app/Resources/views/base.html.twig --><!DOCTYPE html><html>

<head><meta http-equiv="Content-Type" content="text/html"; charset=utf-8" /><title>{% block title %}symblog{% endblock %} - symblog</title><!--[if lt IE 9]>

<scriptsrc="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>

<![endif]-->{% block stylesheets %}

<link href='http://fonts.googleapis.com/css?family=Irish+Grover'rel='stylesheet' type='text/css'>

<link href='http://fonts.googleapis.com/css?family=La+Belle+Aurore'

rel='stylesheet' type='text/css'><link href="{{ asset('css/screen.css') }}" type="text/css"rel="stylesheet" />

{% endblock %}<link rel="shortcut icon" href="{{ asset('favicon.ico') }}" />

</head><body>

<section id="wrapper"><header id="header">

<div class="top">{% block navigation %}

<nav>

<ul class="navigation"><li><a href="#">Home</a></li><li><a href="#">About</a></li><li><a href="#">Contact</a></li>

</ul></nav>

{% endblock %}</div>

<hgroup><h2>{% block blog_title %}<a href="#">symblog</a>{% endblock

%}</h2><h3>{% block blog_tagline %}<a href="#">creating a blog in

Symfony2</a>{% endblock %}</h3></hgroup>

</header>

<section class="main-col">{% block body %}{% endblock %}

</section><aside class="sidebar">

{% block sidebar %}{% endblock %}</aside>

<div id="footer">

{% block footer %}Symfony2 blog tutorial - created by <ahref="https://github.com/dsyph3r">dsyph3r</a>

{% endblock %}

Page 10: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 10/113

 

</div></section>

{% block javascripts %}{% endblock %}</body>

</html>

 Nota

Hay tres archivos externos incluidos en la plantilla, un JavaScript y dos CSS . El archivo JavaScript corrige la carencia de apoyo para HTML5 en los navegadores IE previos a la 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:

<title>{% block title %}symblog{% endblock %} - symblog</title>

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 elnuestro. 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.

<link href="{{ asset('css/screen.css') }}" type="text/css" rel="stylesheet" />

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") }}

Page 11: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 11/113

 

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 quenecesitamos 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,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,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,audio,video{border:0;font-size:100%;font:inherit;vertical-

align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style: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; border-left: 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; font-size: 24px; margin-bottom: 20px; font-weight: normal; }

.main-col { width: 700px; display: inline-block; float: left; border-right: 1pxsolid #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; }

Page 12: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 12/113

 

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.twigcon 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 sufuncionalidad 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.phpcon el siguiente

contenido:

<?php// src/Blogger/BlogBundle/Controller/PageController.php

namespace Blogger\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class PageController extends Controller{

public function indexAction(){

return $this->render('BloggerBlogBundle:Page:index.html.twig');}

}

Ahora crea la plantilla para esta 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' %}

Page 13: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 13/113

 

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

Page 14: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 14/113

 

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.ymly añade la siguiente

regla de enrutado.

# src/Blogger/BlogBundle/Resources/config/routing.ymlBloggerBlogBundle_about:

pattern: /aboutdefaults: { _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.phpy agrega la acción

 para procesar la página sobre.

// src/Blogger/BlogBundle/Controller/PageController.phpclass 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.twigy copia el

siguiente contenido.

{# src/Blogger/BlogBundle/Resources/views/Page/about.html.twig #}{% extends 'BloggerBlogBundle::base.html.twig' %}

{% block title %}About{% endblock%}

{% block body %}<header>

<h1>About symblog</h1></header><article>

<p>Donec imperdiet ante sed diam consequat et dictum erat faucibus.

Aliquam sitamet vehicula leo. Morbi urna dui, tempor ac posuere et, rutrum at dui.Curabitur neque quam, ultricies ut imperdiet id, ornare varius arcu. Ut

congue

Page 15: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 15/113

 

urna sit amet tellus malesuada nec elementum risus molestie. Donecgravida

tellus sed tortor adipiscing fringilla. Donec nulla mauris, mollisegestas

condimentum laoreet, lacinia vel lorem. Morbi vitae justo sit amet felisvehicula commodo a placerat lacus. Mauris at est elit, nec vehicula

urna. Duis a

lacus nisl. Vestibulum ante ipsum primis in faucibus orci luctus etultricesposuere cubilia Curae.</p>

</article>{% 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 hayforma de que un usuario 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:

<a href="/contact">Contact</a>

<?php $this->redirect("/contact"); ?>

Tal vez te estés preguntando qué es lo incorrecto con este enfoque, tal vez la forma en que siemprese 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 contacto 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

acceso a nuestra aplicación en el entorno dev. Si sustituyes app_dev.php con app.phpvas 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.

<!-- app/Resources/views/base.html.twig -->{% block navigation %}

<nav><ul class="navigation">

<li><a href="{{ path('BloggerBlogBundle_homepage') }}">Home</a></li><li><a href="{{ path('BloggerBlogBundle_about') }}">About</a></li><li><a href="#">Contact</a></li>

</ul>

Page 16: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 16/113

 

</nav>{% 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.

<!-- app/Resources/views/base.html.twig --><hgroup>

<h2>{% block blog_title %}<ahref="{{ path('BloggerBlogBundle_homepage') }}">symblog</a>{% endblock %}</h2>

<h3>{% block blog_tagline %}<ahref="{{ path('BloggerBlogBundle_homepage') }}">creating a blog inSymfony2</a>{% endblock %}</h3></hgroup>

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 Contacto. Esta página es un poco más

complicada que la página Sobre, ya que permite a los usuarios 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 contacto: 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 Contacto. Al

final de este capítulo tendrás una página de Contacto que permite a los usuarios enviar sus

consultas al administrador del sitio. Estas consultas serán enviadas por correo electrónico al

administrador 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 contacto¶

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 Contacto. Abre el archivo de enrutado del BloggerBlogBundle ubicado

Page 17: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 17/113

 

en src/Blogger/BlogBundle/Resources/config/routing.ymly añade la siguiente

regla de enrutado.

# src/Blogger/BlogBundle/Resources/config/routing.ymlBloggerBlogBundle_contact:

pattern: /contactdefaults: { _controller: BloggerBlogBundle:Page:contact }

requirements:_method: GET

 No hay nada nuevo aquí, la regla coincide con el patrón /contact, para el método GET del

 protocolo HTTP y ejecuta la acción contact del controlador Page en el

BloggerBlogBundle.

Controlador¶

A continuación vamos a añadir la acción para la página Contacto al controlador Page en el

BloggerBlogBundle situado en

src/Blogger/BlogBundle/Controller/PageController.php.// src/Blogger/BlogBundle/Controller/PageController.php// ..public function contactAction(){

return $this->render('BloggerBlogBundle:Page:contact.html.twig');}// ..

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

volveremos al controlador.

La vista¶

Crea la vista de la página contacto en

src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig y añade

el siguiente contenido.

{# src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig #}{% extends 'BloggerBlogBundle::base.html.twig' %}

{% block title %}Contact{% endblock%}

{% block body %}

<header><h1>Contact symblog</h1>

</header>

<p>Want to contact symblog?</p>{% endblock %}

Esta plantilla 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 contacto.

Page 18: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 18/113

 

<!-- app/Resources/views/base.html.twig -->{% block navigation %}

<nav><ul class="navigation">

<li><a href="{{ path('BloggerBlogBundle_homepage') }}">Home</a></li><li><a href="{{ path('BloggerBlogBundle_about') }}">About</a></li><li><a

href="{{ path('BloggerBlogBundle_contact') }}">Contact</a></li></ul></nav>

{% endblock %}

Si diriges tu navegador a http://symblog.dev/app_dev.php/ y haces clic en el enlace de

contacto en la barra de navegación, deberías ver una página de contacto muy básica. Ahora que

hemos configurado la página correctamente, es hora de empezar a trabajar en el formulario de

Contacto. Esto se divide en dos partes bien diferenciadas; Los validadores y el formulario. Antes de

que podamos abordar el concepto de los validadores y el formulario, tenemos que pensar en cómo

vamos a manejar los datos de la consulta de Contacto.

La entidad Contacto¶

Vamos a empezar creando una clase que representa una consulta de Contacto de un usuario.

Queremos capturar información básica como nombre, asunto y el cuerpo de la consulta. Crea un

nuevo archivo situado en src/Blogger/BlogBundle/Entity/Enquiry.phpy pega el

siguiente contenido:

<?php// src/Blogger/BlogBundle/Entity/Enquiry.php

namespace Blogger\BlogBundle\Entity;

class Enquiry{

protected $name;

protected $email;

protected $subject;

protected $body;

public function getName(){

return $this->name;}

public function setName($name){

$this->name = $name;}

public function getEmail(){

return $this->email;}

public function setEmail($email){

$this->email = $email;}

Page 19: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 19/113

 

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 locual 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->registerNamespaceFallbacks(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

Page 20: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 20/113

 

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.phpy pega el siguiente contenido:

<?php// src/Blogger/BlogBundle/Form/EnquiryType.php

namespace Blogger\BlogBundle\Form;

use Symfony\Component\Form\AbstractType;use Symfony\Component\Form\FormBuilder;

class EnquiryType extends AbstractType{

public function buildForm(FormBuilder $builder, array $options){

$builder->add('name');$builder->add('email', 'email');$builder->add('subject');$builder->add('body', 'textarea');

}

public function getName()

{ return 'contact';}

}

La clase EnquiryType introduce la clase FormBuilder. La clase FormBuilder es tu mejor 

amiga cuando se trata de crear formularios. Esta es capaz de simplificar el proceso de 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

Contacto para usarlas. Remplaza el contenido de la acción Contacto ubicada en

src/Blogger/BlogBundle/Controller/PageController.phpcon lo siguiente:

// src/Blogger/BlogBundle/Controller/PageController.php

public function contactAction(){

$enquiry = new Enquiry();$form = $this->createForm(new EnquiryType(), $enquiry);

Page 21: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 21/113

 

$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 usuarioreenvíe

// el formulario si actualiza la páginareturn $this->redirect($this-

>generateUrl('BloggerBlogBundle_contact'));}

}

return $this->render('BloggerBlogBundle:Page:contact.html.twig', array('form' => $form->createView()

));}

Empezamos creando una instancia de la entidad Enquiry. Esta entidad representa los datos de una

consulta de contacto. A continuación, creamos el formulario real. Especificamos el EnquiryTypeque 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 usuario

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.phpcon lo siguiente. Las

declaraciones use se deben colocar bajo las use existentes.

<?php// src/Blogger/BlogBundle/Controller/PageController.php

namespace Blogger\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;// Importa el nuevo espacio de nombresuse Blogger\BlogBundle\Entity\Enquiry;use Blogger\BlogBundle\Form\EnquiryType;

class PageController extends Controller// ..

Page 22: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 22/113

 

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 action="{{ path('BloggerBlogBundle_contact') }}" method="post"{{ form_enctype(form) }}>

{{ form_widget(form) }}

<input type="submit" /></form>

Si bien esto es muy útil para formularios simples y prototipos, tiene sus limitaciones cuando

necesitas personalización extendida, que a menudo es el caso con los formularios.

Para nuestro formulario de contacto, vamos a optar por un término medio. Reemplaza el código dela plantilla ubicada en

src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig con el

siguiente.

{# src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig #}{% extends 'BloggerBlogBundle::base.html.twig' %}

{% block title %}Contact{% endblock%}

{% block body %}<header>

<h1>Contact symblog</h1></header>

<p>Want to contact symblog?</p>

<form action="{{ path('BloggerBlogBundle_contact') }}" method="post"{{ form_enctype(form) }} class="blogger">

{{ form_errors(form) }}

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

{{ form_rest(form) }}

<input type="submit" value="Submit" /></form>

{% endblock %}

Como puedes ver, utilizamos cuatro nuevos 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 cargade archivos y que no tiene establecido el tipo de contenido, ¡se puede convertir en un verdadero

rascadero de cabeza!

Page 23: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 23/113

 

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 contacto en http://symblog.dev/app_dev.php/contact te

darás cuenta de que no se ve tan atractivo. Le vamos a añadir algo de estilo para mejorar eseaspecto. 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.cssy 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; font-size: 20px; }form.blogger input[type="submit"] { margin-left: 110px; width: 508px; line-height: 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 contacto, 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 desrc/Blogger/BlogBundle/Resources/views/layout.html.twigy sustitúyelo con

el siguiente contenido:

{# src/Blogger/BlogBundle/Resources/views/base.html.twig #}{% extends '::base.html.twig' %}

{% block stylesheets %}{{ parent() }}<link href="{{ asset('bundles/bloggerblog/css/blog.css') }}" type="text/css"

rel="stylesheet" />{% endblock %}

{% block sidebar %}Sidebar content

{% endblock %}

Page 24: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 24/113

 

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 contacto 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.

Page 25: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 25/113

 

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 /contact 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 contacto ubicada en

src/Blogger/BlogBundle/Resources/config/routing.ymlpara permitir también

las peticiones POST .

# src/Blogger/BlogBundle/Resources/config/routing.ymlBloggerBlogBundle_contact:

pattern: /contactdefaults: { _controller: BloggerBlogBundle:Page:contact }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 noesperes que haga mucho todavía. La página sólo te regresará al formulario de contacto.

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.

Page 26: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 26/113

 

Asegúrate de añadir las 5 nuevas declaraciones use en la parte superior del archivo:

<?php// src/Blogger/BlogBundle/Entity/Enquiry.php

namespace Blogger\BlogBundle\Entity;

use Symfony\Component\Validator\Mapping\ClassMetadata;use Symfony\Component\Validator\Constraints\NotBlank;use Symfony\Component\Validator\Constraints\Email;use Symfony\Component\Validator\Constraints\MinLength;use 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áticoloadValidatorMetadata. 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 contacto, 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

Page 27: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 27/113

 

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:

<input type="email" value="" required="required" name="contact[email]"id="contact_email">

Este ha utilizado uno de los nuevos tipos de campo de entrada HTML5, email y ha establecido el

atributo necesario. La 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 usuario malintencionado eludir la validación del lado del cliente.

Enviando correo electrónico¶

Aunque nuestro formulario de contacto permitirá a los usuarios enviar consultas, realmente nada

sucede con ellos todavía. Actualicemos el controlador para enviar un correo electrónico aladministrador 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_user=""mailer_password=""

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 usuario y contraseña cuando sea necesario.

mailer_transport="gmail"mailer_encryption="ssl"

mailer_auth_mode="login"mailer_host="smtp.gmail.com"mailer_user="your_username"mailer_password="your_password"

Advertencia

Ten cuidado si estás usando un sistema de control de versiones (CVS ) como Git para tu proyecto,

especialmente si es repositorio de acceso público, puesto que tu nombre de usuario 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

Page 28: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 28/113

 

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.phpcon el siguientecontenido:

// src/Blogger/BlogBundle/Controller/PageController.php

public function contactAction(){

// ..if ($form->isValid()) {

$message = \Swift_Message::newInstance()->setSubject('Contact enquiry from symblog')->setFrom('[email protected]')

->setTo('[email protected]')->setBody($this-

>renderView('BloggerBlogBundle:Page:contactEmail.txt.twig', array('enquiry' =>$enquiry)));

$this->get('mailer')->send($message);

$this->get('session')->setFlash('blogger-notice', 'Your contact enquirywas successfully sent. Thank you!');

// Redirige - Esto es importante para prevenir que el usuario reenvíe// el formulario si actualiza la páginareturn $this->redirect($this->generateUrl('BloggerBlogBundle_contact'));

}

// ..}

Cuando utilizas la biblioteca Swift Mailer para crear una instancia de Swift_Message, la

cual puedes enviar como correo 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 contacto para informar al usuario que se ha

enviado la consulta. Como los mensajes flash sólo persisten durante exactamente una petición,

son perfectos para notificar al usuario del éxito de las acciones anteriores.

Para mostrar el mensaje flash tenemos que actualizar la plantilla de contacto situada en

src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig.

Actualiza el contenido de la plantilla con lo siguiente:

{# src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig #}

{# resto de la plantilla ... #}

Page 29: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 29/113

 

<header><h1>Contact symblog</h1>

</header>

{% if app.session.hasFlash('blogger-notice') %}<div class="blogger-notice">

{{ app.session.flash('blogger-notice') }}

</div>{% endif %}

<p>Want to contact symblog?</p>

{# resto de la plantilla ... #}

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

lo devuelve.

Registrando el correo electrónico del administrador¶

Symfony2 ofrece un sistema de configuración que podemos utilizar para definir nuestros propiosvalores. Vamos a utilizar este sistema para establecer la dirección de correo electrónico del

administrador 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.ymly pega

lo siguiente:

# src/Blogger/BlogBundle/Resources/config/config.ymlparameters:

# dirección de correo electrónico para contacto del Bloggerblogger_blog.emails.contact_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.ymlimports:

# .. 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 Contacto para utilizar el parámetro.

// src/Blogger/BlogBundle/Controller/PageController.php

public function contactAction()

Page 30: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 30/113

 

{// ..if ($form->isValid()) {

$message = \Swift_Message::newInstance()->setSubject('Contact enquiry from symblog')->setFrom('[email protected]')

->setTo($this->container->getParameter('blogger_blog.emails.contact_email'))->setBody($this-

>renderView('BloggerBlogBundle:Page:contactEmail.txt.twig', array('enquiry' =>$enquiry)));

$this->get('mailer')->send($message);

// ..}// ..

}

Truco

Puesto que el archivo de ajustes se importa en la parte superior del archivo de 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.ymlparameters:

# dirección de correo electrónico para contacto del Bloggerblogger_blog.emails.contact_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, Symfony2tambié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/contactEmail.txt.twig

y agrégale lo siguiente:

{# src/Blogger/BlogBundle/Resources/view/Page/contactEmail.txt.twig #}A contact enquiry was made by {{ enquiry.name }} at {{ "now" | date("Y-m-d H:i")}}.

Reply-To: {{ enquiry.email }}Subject: {{ enquiry.subject }}Body:{{ enquiry.body }}

El contenido del correo electrónico es sólo la consulta que envía el usuario.

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,

Page 31: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 31/113

 

.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.contact_email.

TrucoSymfony2 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.ymlswiftmailer:

disable_delivery: true

Tal vez sería útil duplicar esta funcionalidad en el entorno dev. Después de todo, durante eldesarrollo, 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 contacto, 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 devcolocando la siguiente configuración en el archivo de configuración dev ubicado en

app/config/config_dev.yml.

# app/config/config_dev.ymlswiftmailer:

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.

Page 32: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 32/113

 

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 accesorios.

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

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 SQLllamado 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: accesorios) 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 accesorios. 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 usuarios, probablemente esta

tenga campos como username, password, 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 username, password, first_name, etc., que nos permite llamar a

métodos tales como getUsername() y setPassword(). 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 usuario, o de manera diferida en el futuro. Ahora,

consideremos que nuestro usuario tiene algunos amigos con los que está relacionado. Para ello

deberíamos tener una tabla de amigos, almacenando la clave primaria de la tabla usuario dentro de

ella. Usando el ORM ahora podríamos hacer una llamada como $user->getFriends() para

recuperar objetos de la tabla de amigos. Si eso no es suficiente, el ORM también se ocupa deguardarlos 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

Page 33: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 33/113

 

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. Vela 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 administrador del sitio.

Crea un nuevo archivo situado en src/Blogger/BlogBundle/Entity/blog.php y pega

el siguiente contenido:

<?php// src/Blogger/BlogBundle/Entity/Blog.php

namespace Blogger\BlogBundle\Entity;

class Blog{

protected $title;

protected $author;

protected $blog;

protected $image;

protected $tags;

protected $comments;

protected $created;

protected $updated;}

Como puedes ver, esta es una simple clase PHP . No extiende a ninguna clase y no hay manera de

acceder a sus propiedades. Cada una de las propiedades se ha declarado como protegida por lo que

no puedes acceder a ellas cuando operas en un objeto de esta clase. 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 acceso 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 lasasignaciones 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

Page 34: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 34/113

 

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 blogsituada en src/Blogger/BlogBundle/Entity/blog.php con lo siguiente:

<?php// src/Blogger/BlogBundle/Entity/Blog.php

namespace Blogger\BlogBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/*** @ORM\Entity* @ORM\Table(name="blog")*/

class Blog{

/*** @ORM\Id

* @ORM\Column(type="integer")* @ORM\GeneratedValue(strategy="AUTO")*/protected $id;

/*** @ORM\Column(type="string")*/protected $title;

/*** @ORM\Column(type="string", length=100)

*/protected $author;

/*** @ORM\Column(type="text")*/protected $blog;

/*** @ORM\Column(type="string", length="20")*/protected $image;

/*** @ORM\Column(type="text")*/protected $tags;

protected $comments;

/*** @ORM\Column(type="datetime")*/protected $created;

/**

* @ORM\Column(type="datetime")*/protected $updated;

}

Page 35: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 35/113

 

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ásadelante 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 acceso usando la siguiente orden:

Page 36: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 36/113

 

$ php app/console doctrine:generate:entities Blogger

 Notarás que la entidad Blog se ha actualizado con métodos de acceso. 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 acceso

existentes en la entidad, por lo tanto tus métodos de acceso existentes nunca se van a remplazar con

esta orden. Esto es importante ya que más tarde puedes personalizar algunos de los métodos deacceso 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" yamlsrc/Blogger/BlogBundle/Resources/config/doctrine

Esto creará un archivo ubicado en

src/Blogger/BlogBundle/Resources/config/doctrine/Blogger.BlogBundle.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 autilizar 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

Page 37: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 37/113

 

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 elfuncionamiento 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.ymlcon lo siguiente:

# src/Blogger/BlogBundle/Resources/config/routing.ymlBloggerBlogBundle_blog_show:

pattern: /{id}defaults: { _controller: BloggerBlogBundle:Blog:show }requirements:

_method: GETid: \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 Pageexistente, 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.phpcon el siguiente

contenido:<?php// src/Blogger/BlogBundle/Controller/BlogController.php

Page 38: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 38/113

 

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\Controllerasí:

// src/Blogger/BlogBundle/Controller/PageController.phppublic function contactAction(){

// ..$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 contactAction(Request $request)

{ // ..}

Page 39: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 39/113

 

Ambos métodos consiguen el mismo resultado. Si tu controlador no extendiera la clase ayudante

Symfony\Bundle\FrameworkBundle\Controller\Controllerno 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\Controllerpara 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 acceso 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.twigcon en el

siguiente contenido:

{# src/Blogger/BlogBundle/Resouces/views/Blog/show.html.twig #}{% extends 'BloggerBlogBundle::base.html.twig' %}

{% block title %}{{ blog.title }}{% endblock %}

{% block body %}<article class="blog">

<header><div class="date"><time datetime="{{ blog.created|

date('c') }}">{{ blog.created|date('l, F j, Y') }}</time></div><h2>{{ blog.title }}</h2>

</header><img src="{{ asset(['images/', blog.image]|join) }}"

alt="{{ blog.title }} image not found" class="large" /><div><p>{{ blog.blog }}</p>

</div>

Page 40: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 40/113

 

</article>{% 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.csscon 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?

Page 41: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 41/113

 

 

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 accesorios 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.gittarget=/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.

Page 42: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 42/113

 

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 denombres. Dado que DataFixtures también está en el espacio de nombres Doctrine\Commonesto 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->registerNamespaces(array(// ...'Doctrine\\Common\\DataFixtures' => __DIR__.'/../vendor/doctrine-fixtures/lib','Doctrine\\Common' => __DIR__.'/../vendor/doctrine-common/lib',// ...));

Ahora vamos a registrar el DoctrineFixturesBundle en el núcleo situado en

app/AppKernel.php:

// app/AppKernel.phppublic function registerBundles(){

$bundles = array(// ...new Symfony\Bundle\DoctrineFixturesBundle\DoctrineFixturesBundle(),

// ...);// ...

}

Accesorios para el Blog ¶

Ahora estamos listos para definir algunos accesorios para nuestros blogs. Crea un archivo de

accesorios en src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.phpy

añádele el siguiente contenido:

<?php// src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php

namespace Blogger\BlogBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;use Blogger\BlogBundle\Entity\Blog;

class BlogFixtures implements FixtureInterface{

public function load($manager){

$blog1 = new Blog();

$blog1->setTitle('A day with Symfony2');$blog1->setBlog('Lorem ipsum dolor sit amet, consectetur adipiscingeletra electrify denim vel ports.\nLorem ipsum dolor sit amet, consecteturadipiscing elit. Morbi ut velocity magna. Etiam vehicula nunc non leo hendrerit

Page 43: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 43/113

 

commodo. Vestibulum vulputate mauris eget erat congue dapibus imperdiet justoscelerisque. Nulla consectetur tempus nisl vitae viverra. Cras el mauris egeterat congue dapibus imperdiet justo scelerisque. Nulla consectetur tempus nislvitae viverra. Cras elementum molestie vestibulum. Morbi id quam nisl. Praesenthendrerit, orci sed elementum lobortis, justo mauris lacinia libero, nonfacilisis 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 idquam 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 justoscelerisque.');

$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 vitaeviverra.');

$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 justoscelerisque.');

$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"));

Page 44: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 44/113

 

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

$manager->flush();}

}

El archivo de accesorios 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. Nullaconsectetur...');$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 accesorios. Creamos cada

entidad, pidiendo a Doctrine 2 que las gestionara y luego, al final, limpiamos todas las operaciones.

Cargando los accesorios¶

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

$ php app/console doctrine:fixtures:load

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

apropiado.

Page 45: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 45/113

 

 

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 rutapara "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:

<?php// src/Blogger/BlogBundle/Entity/Blog.php

Page 46: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 46/113

 

// ..

/*** @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.

<?php// src/Blogger/BlogBundle/Entity/Blog.php

// ..

/*** @ORM\Entity* @ORM\Table(name="blog")* @ORM\HasLifecycleCallbacks()*/

class Blog{

// ..

public function __construct(){

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

/*** @ORM\preUpdate*/public function setUpdatedValue(){

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

// ..

}

Registramos la entidad Blog para que sea notificada cuando ocurra el evento preUpdate para

establecer el valor de updated. Ahora, cuando se vuelva a ejecutar la tarea para cargar accesorios

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.

Page 47: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 47/113

 

Conclusión¶

Hemos cubierto una serie de conceptos para hacer frente a los modelos de Doctrine 2. También

vimos la definición de accesorios 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 usuarios 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.phppara recuperar los

blogs de la base de datos.

// src/Blogger/BlogBundle/Controller/PageController.phpclass PageController extends Controller{

public function indexAction(){

$em = $this->getDoctrine()

Page 48: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 48/113

 

->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 delGenerador 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.twigcon lo

siguiente:

{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}{% extends 'BloggerBlogBundle::base.html.twig' %}

{% block body %}{% for blog in blogs %}

<article class="blog"><div class="date"><time datetime="{{ blog.created|

date('c') }}">{{ blog.created|date('l, F j, Y') }}</time></div><header>

<h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id':blog.id }) }}">{{ blog.title }}</a></h2>

</header>

<img src="{{ asset(['images/', blog.image]|join) }}" /><div class="snippet">

<p>{{ blog.blog(500) }}</p><p class="continue"><a

href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}">Continuereading...</a></p>

Page 49: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 49/113

 

</div>

<footer class="meta"><p>Comments: -</p><p>Posted by <span class="highlight">{{blog.author}}</span> at

{{ blog.created|date('h:iA') }}</p><p>Tags: <span class="highlight">{{ blog.tags }}</span></p>

</footer></article>{% else %}

<p>There are no blog entries for symblog</p>{% endfor %}

{% endblock %}

Aquí te presentamos una de las estructuras de control de Twig , la estructura

for..else..endfor. Si no has utilizado un motor de plantillas antes,probablemente te sea

familiar el siguiente fragmento de código PHP .

<?php if (count($blogs)): ?><?php foreach ($blogs as $blog): ?>

<h1><?php echo $blog->getTitle() ?><?h1><!-- resto del contenido -->

<?php endforeach ?><?php else: ?>

<p>There are no blog entries</p><?php endif ?>

La estructura de control for..else..endfor es una manera mucho 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, lotenemos que pasar como argumento de la función path. Esto se puede ver con lo siguiente:

<h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}">{{ blog.title }}</a></h2>

En segundo lugar reproducimos el contenido del blog utilizando <p>{{ blog.blog(500) }}</p>. El argumento 500 que pasamos, es la longitud 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.phppublic function getBlog($length = null){

if (false === is_null($length) && $length > 0)return substr($this->blog, 0, $length);

elsereturn $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

Page 50: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 50/113

 

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()*/

Page 51: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 51/113

 

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.

<?php// src/Blogger/BlogBundle/Repository/BlogRepository.php

namespace Blogger\BlogBundle\Repository;

use Doctrine\ORM\EntityRepository;

/*** BlogRepository** Esta clase fue generada por el ORM de Doctrine. Abajo añade* tu propia personalización a los métodos del repositorio.*/

class BlogRepository extends EntityRepository{

}

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.

<?php// src/Blogger/BlogBundle/Repository/BlogRepository.php

namespace Blogger\BlogBundle\Repository;

use Doctrine\ORM\EntityRepository;

/*** BlogRepository** Esta clase fue generada por el ORM de Doctrine. Abajo añade* tu propia personalización a los métodos del repositorio.*/

class BlogRepository extends EntityRepository{

public function getLatestBlogs($limit = null){

$qb = $this->createQueryBuilder('b')->select('b')

->addOrderBy('b.created', 'DESC');

if (false === is_null($limit))$qb->setMaxResults($limit);

Page 52: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 52/113

 

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 claseRepositorio tenemos acceso 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.phpclass 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 unblog puede tener muchos comentarios.

Vamos a comenzar definiendo los fundamentos de la clase entidad Comment. Crea un nuevo

Page 53: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 53/113

 

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

contenido:

<?php// src/Blogger/BlogBundle/Entity/Comment.php

namespace Blogger\BlogBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/***

@ORM\Entity(repositoryClass="Blogger\BlogBundle\Repository\CommentRepository")* @ORM\Table(name="comment")* @ORM\HasLifecycleCallbacks()*/

class Comment{

/*** @ORM\Id

* @ORM\Column(type="integer")* @ORM\GeneratedValue(strategy="AUTO")*/protected $id;

/*** @ORM\Column(type="string")*/protected $user;

/*** @ORM\Column(type="text")*/

protected $comment;

/*** @ORM\Column(type="boolean")*/protected $approved;

/*** @ORM\ManyToOne(targetEntity="Blog", inversedBy="comments")* @ORM\JoinColumn(name="blog_id", referencedColumnName="id")*/protected $blog;

/*** @ORM\Column(type="datetime")*/protected $created;

/*** @ORM\Column(type="datetime")*/protected $updated;

public function __construct(){

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

$this->setApproved(true);

Page 54: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 54/113

 

}

/*** @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.<?php// src/Blogger/BlogBundle/Entity/Blog.php

namespace Blogger\BlogBundle\Entity;

use Doctrine\ORM\Mapping as ORM;use Doctrine\Common\Collections\ArrayCollection;

/*** @ORM\Entity(repositoryClass="Blogger\BlogBundle\Repository\BlogRepository")* @ORM\Table(name="blog")

* @ORM\HasLifecycleCallbacks()*/class Blog{

// ..

/*** @ORM\OneToMany(targetEntity="Comment", mappedBy="blog")*/protected $comments;

// ..

public function __construct(){

$this->comments = new ArrayCollection();

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

}

// ..}

Hay unos cuantos cambios que debemos resaltar aquí. En primer lugar, 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

Page 55: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 55/113

 

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 acceso. Ejecuta la siguiente tarea de Doctrine 2 como antes

 para alcanzar este objetivo:

$ php app/console doctrine:generate:entities Blogger

Ahora, ambas entidades deben estar actualizadas con los métodos de acceso correctos. También

notarás que se ha creado la clase CommentRepository en

src/Blogger/BlogBundle/Repository/CommentRepository.phpcomo 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.gittarget=/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\DBALexistente puesto que esta especifica una nueva ruta. Los espacios de nombres son revisados de

Page 56: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 56/113

 

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->registerNamespaces(array(// ...

'Doctrine\\DBAL\\Migrations' => __DIR__.'/../vendor/doctrine-migrations/lib','Doctrine\\DBAL' => __DIR__.'/../vendor/doctrine-dbal/lib',// ...));

Ahora vamos a registrar el paquete en el núcleo situado en app/AppKernel.php.

// app/AppKernel.phppublic function registerBundles(){

$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

Page 57: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 57/113

 

Datos de prueba: Revisados¶

Ahora que ya hemos creado la entidad Comment, vamos a añadirle algunos accesorios. Siempre es

 buena idea añadir algunos accesorios cada vez que creas una entidad. Sabemos que un comentario

debe tener una entidad Blog relacionada, de esta manera lo establecimos en los metadatos de su

configuración, para ello al crear accesorios para las entidades Comentario tendremos que

especificar la entidad Blog a la que pertenecen. Ya creamos los accesorios para la entidad Blog por lo tanto simplemente podríamos actualizar ese archivo para agregar las entidades

Comentario. Esto puede ser manejable —por ahora, pero, ¿qué sucederá después cuando

agreguemos usuarios, 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 accesorios de la entidad Comentario. El

 problema con este enfoque es: ¿cómo podemos acceder a las entidades Blog desde los accesorios

blog?

Afortunadamente esto se puede conseguir fácilmente estableciendo referencias a objetos en un

archivo de accesorios donde estos pueden acceder a otros accesorios. Actualiza la entidad BlogDataFixtures ubicada en

src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.phpcon losiguiente. 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:

<?php// src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php

namespace Blogger\BlogBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;use Doctrine\Common\DataFixtures\OrderedFixtureInterface;use Blogger\BlogBundle\Entity\Blog;

class BlogFixtures extends AbstractFixture implements OrderedFixtureInterface{

public function load($manager){

// ..

$manager->flush();

$this->addReference('blog-1', $blog1);$this->addReference('blog-2', $blog2);$this->addReference('blog-3', $blog3);$this->addReference('blog-4', $blog4);$this->addReference('blog-5', $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 decarga de los accesorios. Los blogs se deben cargar antes que los comentarios por lo tanto

devolverá 1.

Page 58: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 58/113

 

Datos de prueba Comentario¶

Ahora estamos listos para definir algunos accesorios para nuestra entidad Comentario. Crea un

archivo de accesorios en

src/Blogger/BlogBundle/DataFixtures/ORM/CommentFixtures.phpy añádele

el siguiente contenido:

<?php// src/Blogger/BlogBundle/DataFixtures/ORM/CommentFixtures.php

namespace Blogger\BlogBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;use Doctrine\Common\DataFixtures\OrderedFixtureInterface;use Blogger\BlogBundle\Entity\Comment;use Blogger\BlogBundle\Entity\Blog;

class CommentFixtures extends AbstractFixture implements OrderedFixtureInterface{

public function load($manager)

{$comment = new Comment();$comment->setUser('symfony');$comment->setComment('To make a long story short. You can\'t go wrong by

choosing Symfony! And no one has ever been fired for using Symfony.');$comment->setBlog($manager->merge($this->getReference('blog-1')));$manager->persist($comment);

$comment = new Comment();$comment->setUser('David');$comment->setComment('To make a long story short. Choosing a framework

must not be taken lightly; it is a long-term commitment. Make sure that you makethe right selection!');

$comment->setBlog($manager->merge($this->getReference('blog-1')));$manager->persist($comment);

$comment = new Comment();$comment->setUser('Dade');$comment->setComment('Anything else, mom? You want me to mow the lawn?

Oops! I forgot, New York, No grass.');$comment->setBlog($manager->merge($this->getReference('blog-2')));$manager->persist($comment);

$comment = new Comment();$comment->setUser('Kate');

$comment->setComment('Are you challenging me? ');$comment->setBlog($manager->merge($this->getReference('blog-2')));$comment->setCreated(new \DateTime("2011-07-23 06:15:20"));$manager->persist($comment);

$comment = new Comment();$comment->setUser('Dade');$comment->setComment('Name your stakes.');$comment->setBlog($manager->merge($this->getReference('blog-2')));$comment->setCreated(new \DateTime("2011-07-23 06:18:35"));$manager->persist($comment);

$comment = new Comment();

$comment->setUser('Kate');$comment->setComment('If I win, you become my slave.');$comment->setBlog($manager->merge($this->getReference('blog-2')));$comment->setCreated(new \DateTime("2011-07-23 06:22:53"));

Page 59: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 59/113

 

$manager->persist($comment);

$comment = new Comment();$comment->setUser('Dade');$comment->setComment('Your SLAVE?');$comment->setBlog($manager->merge($this->getReference('blog-2')));$comment->setCreated(new \DateTime("2011-07-23 06:25:15"));

$manager->persist($comment);

$comment = new Comment();$comment->setUser('Kate');$comment->setComment('You wish! You\'ll do shitwork, scan, crack

copyrights...');$comment->setBlog($manager->merge($this->getReference('blog-2')));$comment->setCreated(new \DateTime("2011-07-23 06:46:08"));$manager->persist($comment);

$comment = new Comment();$comment->setUser('Dade');$comment->setComment('And if I win?');

$comment->setBlog($manager->merge($this->getReference('blog-2')));$comment->setCreated(new \DateTime("2011-07-23 10:22:46"));$manager->persist($comment);

$comment = new Comment();$comment->setUser('Kate');$comment->setComment('Make it my first-born!');$comment->setBlog($manager->merge($this->getReference('blog-2')));$comment->setCreated(new \DateTime("2011-07-23 11:08:08"));$manager->persist($comment);

$comment = new Comment();$comment->setUser('Dade');

$comment->setComment('Make it our first-date!');$comment->setBlog($manager->merge($this->getReference('blog-2')));$comment->setCreated(new \DateTime("2011-07-24 18:56:01"));$manager->persist($comment);

$comment = new Comment();$comment->setUser('Kate');$comment->setComment('I don\'t DO dates. But I don\'t lose either, so

you\'re on!');$comment->setBlog($manager->merge($this->getReference('blog-2')));$comment->setCreated(new \DateTime("2011-07-25 22:28:42"));$manager->persist($comment);

$comment = new Comment();$comment->setUser('Stanley');$comment->setComment('It\'s not gonna end like this.');$comment->setBlog($manager->merge($this->getReference('blog-3')));$manager->persist($comment);

$comment = new Comment();$comment->setUser('Gabriel');$comment->setComment('Oh, come on, Stan. Not everything ends the way you

think it should. Besides, audiences love happy endings.');$comment->setBlog($manager->merge($this->getReference('blog-3')));$manager->persist($comment);

$comment = new Comment();$comment->setUser('Mile');$comment->setComment('Doesn\'t Bill Gates have something like that?');

Page 60: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 60/113

 

$comment->setBlog($manager->merge($this->getReference('blog-5')));$manager->persist($comment);

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

$manager->persist($comment);

$manager->flush();}

public function getOrder(){

return 2;}

}

Al igual que con las modificaciones que hicimos a la clase BlogFixtures, la clase

CommentFixtures también extiende a la clase AbstractFixture e implementa laOrderedFixtureInterface. Esto significa que también debes implementar el método

getOrder(). Esta vez fijamos el valor de retorno a 2, para garantizar que estos accesorios se

cargarán después de los accesorios 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 accesorios a la base de datos.

$ php app/console doctrine:fixtures:load

Mostrando comentarios¶

Ahora podemos mostrar los comentarios relacionados con cada entrada del blog . Empecemos

actualizando el CommentRepository con un 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.phpy reemplaza sucontenido con lo siguiente:

<?php// src/Blogger/BlogBundle/Repository/CommentRepository.php

namespace Blogger\BlogBundle\Repository;

use Doctrine\ORM\EntityRepository;

/*** CommentRepository*

* Esta clase fue generada por el ORM de Doctrine. Abajo añade* tu propia personalización a los métodos del repositorio.*/

class CommentRepository extends EntityRepository

Page 61: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 61/113

 

{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 seajusta 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.phpcon 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

Page 62: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 62/113

 

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">

<h3>Comments</h3>{% include 'BloggerBlogBundle:Comment:index.html.twig' with

{ 'comments': comments } %}</section></section>

{% endblock %}

Como puedes ver, usamos una nueva etiqueta de Twig , la etiqueta include. Esta incluirá el

contenido de la plantilla especificada por 

BloggerBlogBundle:Comment:index.html.twig. 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.twigque 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.twig con el siguiente contenido:

{# src/Blogger/BlogBundle/Resources/public/views/Comment/index.html.twig #}

{% for comment in comments %}

<article class="comment {{ cycle(['odd', 'even'], loop.index0) }}"id="comment-{{ comment.id }}"><header>

<p><span class="highlight">{{ comment.user }}</span> commented <timedatetime="{{ comment.created|date('c') }}">{{ comment.created|date('l, F j,Y') }}</time></p>

</header><p>{{ comment.comment }}</p>

</article>{% else %}

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

Como puedes ver iteramos sobre una 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

Page 63: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 63/113

 

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.csscon 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 usuarios agreguencomentarios 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 contacto. En lugar de crear manualmente el formulario de comentarios, le puedes

Page 64: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 64/113

 

 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.

TrucoPosiblemente 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.

<?php// src/Blogger/BlogBundle/Form/CommentType.php

namespace Blogger\BlogBundle\Form;

use Symfony\Component\Form\AbstractType;use Symfony\Component\Form\FormBuilder;

class CommentType extends AbstractType{

public function buildForm(FormBuilder $builder, array $options){

$builder->add('user')->add('comment')->add('approved')->add('created')->add('updated')

->add('blog');}

public function getName(){

return 'blogger_blogbundle_commenttype';}

}

Ya hemos explorado lo que está pasando aquí cuando generamos la clase EnquiryType anterior.

Ahora, 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 usuarios 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.

Page 65: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 65/113

 

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: POSTblog_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.phpcon el siguiente

contenido:

<?php// src/Blogger/BlogBundle/Controller/CommentController.php

namespace Blogger\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;use Blogger\BlogBundle\Entity\Comment;use Blogger\BlogBundle\Form\CommentType;

/*** Comment controller.*/

class CommentController extends Controller{

public function newAction($blog_id){$blog = $this->getBlog($blog_id);

$comment = new Comment();$comment->setBlog($blog);$form = $this->createForm(new CommentType(), $comment);

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

));}

public function createAction($blog_id){

$blog = $this->getBlog($blog_id);

$comment = new Comment();$comment->setBlog($blog);$request = $this->getRequest();$form = $this->createForm(new CommentType(), $comment);$form->bindRequest($request);

if ($form->isValid()) {// TODO: Persistir la entidad comentario

return $this->redirect($this->generateUrl('BloggerBlogBundle_blog_show', array(

'id' => $comment->getBlog()->getId())) .

Page 66: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 66/113

 

'#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 contacto. 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 usuarios puedan enviar comentarios para los blogs con valores usuario o

comentario en blanco. Para lograrlo nos remontaremos a los validadores que te presentamos en

la parte 2 cuando creamos el formulario de consulta. Actualiza la entidad Comentario ubicada en

src/Blogger/BlogBundle/Entity/Comment.php con lo siguiente:

<?php// src/Blogger/BlogBundle/Entity/Comment.php

// ..

use Symfony\Component\Validator\Mapping\ClassMetadata;use Symfony\Component\Validator\Constraints\NotBlank;

// ..class Comment{

// ..

public static function loadValidatorMetadata(ClassMetadata $metadata){

$metadata->addPropertyConstraint('user', new NotBlank(array('message' => 'You must enter your name'

)));$metadata->addPropertyConstraint('comment', new NotBlank(array(

Page 67: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 67/113

 

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

}

// ..}

Las restricciones garantizan que tanto el usuario 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 NotBlankcomo 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.twig con el siguiente contenido:

{# src/Blogger/BlogBundle/Resources/public/views/Comment/form.html.twig #}

<form action="{{ path('BloggerBlogBundle_comment_create', { 'blog_id' :comment.blog.id } ) }}" method="post" {{ form_enctype(form) }} class="blogger">

{{ form_widget(form) }}<p>

<input type="submit" value="Submit"></p>

</form>

El 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 creadoBloggerBlogBundle_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 %}<h1>Add comment for blog post "{{ comment.blog.title }}"</h1>

{% 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.twigcon lo siguiente:

{# src/Blogger/BlogBundle/Resources/public/views/Blog/show.html.twig #}

Page 68: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 68/113

 

{# .. #}

{% block body %}

{# .. #}

<section class="comments" id="comments">

{# .. #}

<h3>Add Comment</h3>{% render 'BloggerBlogBundle:Comment:new' with { 'blog_id': blog.id } %}

</section>{% endblock %}

Aquí utilizamos otra nueva etiqueta de Twig , la etiqueta render. Esta etiqueta reproducirá el

contenido de un controlador en la plantilla. En nuestro caso, reproduce el contenido de la 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 usuario 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 claseCommentType. 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.

Page 69: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 69/113

 

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 usuario 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.phppublic 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 user es un

campo text, el campo comment es un campo textarea, los 2 campos DateTime son una

serie de campos select que te permiten seleccionarlos para precisar la hora, etc.

Esto se debe a la habilidad del FormBuilder para adivinar el tipo de la propiedad y el campo que

necesita reproducir. Este es capaz de hacerlo 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.phppara producir únicamente los

campos que necesitamos:

<?php// src/Blogger/BlogBundle/Form/CommentType.php

// ..class CommentType extends AbstractType{

public function buildForm(FormBuilder $builder, array $options){

$builder->add('user')->add('comment')

;}

// ..}

Ahora, cuando actualices la página únicamente se emiten los campos usuario y comentario.

Si envías ahora el formulario, el comentario en realidad no se guardará en la base de datos. Esto es

Page 70: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 70/113

 

 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.

<?php// src/Blogger/BlogBundle/Controller/CommentController.php

// ..class CommentController extends Controller{

public function createAction($blog_id){

// ..

if ($form->isValid()) {$em = $this->getDoctrine()

->getEntityManager();$em->persist($comment);$em->flush();

return $this->redirect($this->generateUrl('BloggerBlogBundle_blog_show', array(

'id' => $comment->getBlog()->getId())) .'#comment-' . $comment->getId()

);}

// ..}

}

La persistencia de la entidad Comentario es tan simple como una llamada a persist() y otraa 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 .

Page 71: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 71/113

 

 

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 usuario puede enviar comentarios a los blogs y

leer los comentarios dejados por otros usuarios. Hemos visto cómo crear accesorios 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 yAssetic¶

Descripción¶

En este capítulo continuaremos construyendo la interfaz de usuario 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

Page 72: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 72/113

 

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 laentidad 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.twigcon lo siguiente:

{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}

{# .. #}

<footer class="meta"><p>Comments: {{ blog.comments|length }}</p><p>Posted by <span class="highlight">{{ blog.author }}</span> at

{{ blog.created|date('h:iA') }}</p><p>Tags: <span class="highlight">{{ blog.tags }}</span></p>

</footer>

{# .. #}

Hemos utilizado el captador comments para recuperar los comentarios del blog y luego

depuramos la 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');

Page 73: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 73/113

 

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 serecuperan 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 WHEREt0.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

Page 74: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 74/113

 

esta función, Doctrine 2 tiene que cargar —de manera diferida— la entidad Comentariorelacionada 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 unirlas 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.phppara unirlo con los

comentarios.

// src/Blogger/BlogBundle/Repository/BlogRepository.php

public function getLatestBlogs($limit = null){

$qb = $this->createQueryBuilder('b')->select('b, c')->leftJoin('b.comments', 'c')->addOrderBy('b.created', 'DESC');

if (false === is_null($limit))$qb->setMaxResults($limit);

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

}

Si ahora actualizas la página web y examinas la salida de  Doctrine 2 en la barra de depuración webnotará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, perose 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.twigañadiéndole un enlace para mostrar los comentarios del blog.

{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}

{# .. #}

<footer class="meta"><p>Comments: <a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id

}) }}#comments">{{ blog.comments|length }}</a></p><p>Posted by <span class="highlight">{{ blog.author }}</span> at

{{ blog.created|date('h:iA') }}</p><p>Tags: <span class="highlight">{{ blog.tags }}</span></p>

</footer>

{# .. #}

Page 75: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 75/113

 

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.phpcon 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 etiquetasuksort($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);}

Page 76: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 76/113

 

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.phppara 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"><header>

<h3>Tag Cloud</h3></header><p class="tags">

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

<p>There are no tags</p>{% endfor %}

</p></section>

La plantilla 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 valorde 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

Page 77: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 77/113

 

reproduciendo la nueva acción sidebar. Recuerda del capítulo anterior que el método renderde Twig reproducirá el contenido de una acción del controlador, en este caso la acción sidebardel 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; font-weight: 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.twigcon lo siguiente:

{# src/Blogger/BlogBundle/Resources/views/base.html.twig #}

{# .. #}

{% block stylesheets %}{{ parent() }}<link href="{{ asset('bundles/bloggerblog/css/blog.css') }}" type="text/css"

rel="stylesheet" /><link href="{{ asset('bundles/bloggerblog/css/sidebar.css') }}"

type="text/css" rel="stylesheet" />{% endblock %}

{# .. #}

 Nota

Si no 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

Page 78: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 78/113

 

que actualizar los accesorios 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 ellovamos a añadir un nuevo método al CommentRepository situado en

src/Blogger/BlogBundle/Repository/CommentRepository.php.

<?php// src/Blogger/BlogBundle/Repository/CommentRepository.php

public function getLatestComments($limit = 10){

$qb = $this->createQueryBuilder('c')->select('c')->addOrderBy('c.id', 'DESC');

if (false === is_null($limit))$qb->setMaxResults($limit);

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

}

A continuación actualiza la acción de la barra lateral situada en

src/Blogger/BlogBundle/Controller/PageController.phppara 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_limitpara 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.ymlcon lo siguiente:

# src/Blogger/BlogBundle/Resources/config/config.yml

parameters:

# ..

# Blogger máximo de comentarios recientesblogger_blog.comments.latest_comment_limit: 10

Page 79: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 79/113

 

Por último, debemos reproducir los comentarios recientes en la plantilla de la barra lateral.

Actualiza la plantilla ubicada en

src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig con lo

siguiente:

{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #}

{# .. #}

<section class="section"><header>

<h3>Latest Comments</h3></header>{% for comment in latestComments %}

<article class="comment"><header>

<p class="small"><spanclass="highlight">{{ comment.user }}</span> commented on

<a href="{{ path('BloggerBlogBundle_blog_show', { 'id':comment.blog.id }) }}#comment-{{ comment.id }}">

{{ comment.blog.title }}</a>[<em><time datetime="{{ comment.created|

date('c') }}">{{ comment.created|date('Y-m-d h:iA') }}</time></em>]</p>

</header><p>{{ comment.comment }}</p></p>

</article>{% else %}

<p>There are no recent comments</p>{% endfor %}

</section>

Si ahora actualizas la página de symblog en tu navegador verás que se muestran los comentarios

recientes en la barra lateral bajo la nube de etiquetas.

Page 80: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 80/113

 

 

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 loscomentarios 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 2días.

La extensión¶

Crea un archivo para la extensión de Twig situado en

src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogExtension.php yactualízalo con el siguiente contenido:

<?php

Page 81: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 81/113

 

// 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 thefuture");

$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 cualsimplemente transforma un objeto DateTime en una cadena que representa el lapso de tiempo

transcurrido desde que el valor fue almacenado en el objeto DateTime.

Page 82: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 82/113

 

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.ymlcon lo

siguiente:

services:

blogger_blog.twig.extension:class: Blogger\BlogBundle\Twig\Extensions\BloggerBlogExtensiontags:

- { 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"><header>

<h3>Latest Comments</h3></header>{% for comment in latestComments %}

{# .. #}<em><time datetime="{{ comment.created|date('c') }}">{{ comment.created|created_ago }}</time></em>

{# .. #}{% endfor %}

</section>

Si ahora diriges tu navegador a http://symblog.dev/app_dev.php/ 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 ensrc/Blogger/BlogBundle/Resources/views/Comment/index.html.twig con lo

siguiente:

{# src/Blogger/BlogBundle/Resources/views/Comment/index.html.twig #}

{% for comment in comments %}<article class="comment {{ cycle(['odd', 'even'], loop.index0) }}"

id="comment-{{ comment.id }}"><header>

<p><span class="highlight">{{ comment.user }}</span> commented <timedatetime="{{ comment.created|date('c') }}">{{ comment.created|created_ago }}</time></p>

</header><p>{{ comment.comment }}</p></article>

{% else %}

Page 83: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 83/113

 

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

Truco

Hay una serie de útiles extensiones de Twig disponibles a 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.ymlpara que tenga esta

apariencia:

# src/Blogger/BlogBundle/Resources/config/routing.yml

BloggerBlogBundle_blog_show:pattern: /{id}/{slug}defaults: { _controller: BloggerBlogBundle:Blog:show }requirements:

_method: GETid: \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.phppara 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:

Page 84: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 84/113

 

pattern: /{id}/{slug}/{comments}defaults: { _controller: BloggerBlogBundle:Blog:show, comments: true }requirements:

_method: GETid: \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

Page 85: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 85/113

 

$text = trim($text, '-');

// transliterateif (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.twigand

replace its contents with the following. There have been 3 edits to the generation of theBloggerBlogBundle_blog_show route in this template. The edits simply pass in the blog

slug to the Twig path function.

Page 86: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 86/113

 

{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}

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

{% block body %}{% for blog in blogs %}

<article class="blog">

<div class="date"><time datetime="{{ blog.created|date('c') }}">{{ blog.created|date('l, F j, Y') }}</time></div><header>

<h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id':blog.id, 'slug': blog.slug }) }}">{{ blog.title }}</a></h2>

</header>

<img src="{{ asset(['images/', blog.image]|join) }}" /><div class="snippet">

<p>{{ blog.blog(500) }}</p><p class="continue"><a

href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id, 'slug':blog.slug }) }}">Continue reading...</a></p>

</div>

<footer class="meta"><p>Comments: <a href="{{ path('BloggerBlogBundle_blog_show',

{ 'id': blog.id, 'slug': blog.slug }) }}#comments">{{ blog.comments|length }}</a></p>

<p>Posted by <span class="highlight">{{ blog.author }}</span> at{{ blog.created|date('h:iA') }}</p>

<p>Tags: <span class="highlight">{{ blog.tags }}</span></p></footer>

</article>{% else %}

<p>There are no blog entries for symblog</p>

{% endfor %}{% endblock %}

Also, one update needs to be made to the Latest Comments section of the sidebar template located

at src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig.

{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #}

{# .. #}

<a href="{{ path('BloggerBlogBundle_blog_show', { 'id': comment.blog.id, 'slug':comment.blog.slug }) }}#comment-{{ comment.id }}">

{{ comment.blog.title }}</a>

{# .. #}

Finally the createAction of the CommentController needs to be updated when redirecting

to the blog show page on a successful comment posting. Update the CommentControllerlocated at src/Blogger/BlogBundle/Controller/CommentController.phpwith

the following.

// src/Blogger/BlogBundle/Controller/CommentController.php

public function createAction($blog_id){

// ..

Page 87: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 87/113

 

if ($form->isValid()) {// ..

return $this->redirect($this->generateUrl('BloggerBlogBundle_blog_show',array(

'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.

Page 88: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 88/113

 

In contrast, if we look at the front controller for the production environment located at

web/app.php we see the following:

$kernel = new AppKernel('prod', false);

You can see the prod environment is passed into the AppKernel in this instance.

The test environment is not supposed to be run via the web browser which is why there is noapp_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.

<IfModule mod_rewrite.c>RewriteBase /# ..

</IfModule>

Page 89: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 89/113

 

 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 user of the

 problem. These exception screens can be customised to match the look and feel of your application.

We will explore this in later chapters.

Further 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

 passes this to app.php.

You maybe on an Apache server that doesn’t have the mod_rewrite.c enable. If this is the caseyou can simply add app.php to the URL such as http://symblog.dev/app.php/.

While we have covered the basics of the production environment, we have not covered many

other production related tasks such as customising error pages, and deployment to the

production server using tools such as capifony. These topics will be covered in later chapters.

Creating New Environments¶

Finally its worth noting that you can setup your own environments easily in Symfony2. For 

example, you may want a staging environment that would run on the production server, but output

some of the debugging information such as exceptions. This would allow the platform to be tested

manually on the actual production server as production and development configurations of servers

can differ.

While creating a new environment is a simple task, it is outside the scope of this tutorial. There is

an excellent article in the Symfony2 cookbook that covers this.

Assetic¶

The Symfony2 Standard Distribution is bundled with a library for assets management called

Assetic. The library was developed by Kris Wallsmith and was inspired by the Python library

webassets.

Assetic deals with 2 parts of asset management, the assets such as images, stylesheets and

JavaScript and the filters that can be applied to these assets. These filters are able to perform useful

tasks such as minifying your CSS and JavaScript, passing CoffeeScript files through the

Page 90: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 90/113

 

CoffeeScript compiler, and combining asset files together to reduce the number of HTTP request

made to the server.

Currently we have been using the Twig asset function to include assets into the template as

follows.

<link href="{{ asset('bundles/bloggerblog/css/blog.css') }}" type="text/css"rel="stylesheet" />

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

Assets¶

The Assetic library describes an asset as follows:

 An Assetic asset is something with filterable content that can be loaded and dumped. An asset alsoincludes 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

BloggerBlogBundlemain layout template. Update the content of the template located at

src/Blogger/BlogBundle/Resources/views/layout.html.twigwith the

following.

{# src/Blogger/BlogBundle/Resources/views/base.html.twig #}

{# .. #}

{% block stylesheets %}

{{ parent () }}

{% stylesheets'@BloggerBlogBundle/Resources/public/css/*'

%}<link href="{{ asset_url }}" rel="stylesheet" media="screen" />

{% endstylesheets %}{% endblock %}

{# .. #}

We have replaced the 2 previous links for CSS files with some Assetic functionality. Using

stylesheets from Assetic we have specified that all CSS files in the locationsrc/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'

Page 91: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 91/113

 

'@BloggerBlogBundle/Resources/public/css/sidebar.css'%}

<link href="{{ asset_url }}" rel="stylesheet" media="screen" />{% endstylesheets %}

{% endblock %}

{# .. #}

The end result in both cases is the same. The first option using the * ensures that when new CSS

files are added to the directory, they will always be included in the combined CSS file by Assetic.

This may not be the desired functionality for your website, so use either method above to suit your 

needs.

If you have a look at the HTML output via http://symblog.dev/app_dev.php/ you will

see the CSS has been included something like this (Notice we are running back in the

development environment again).

<link href="/app_dev.php/css/d8f44a4_part_1_blog_1.css" rel="stylesheet"media="screen" />

<link href="/app_dev.php/css/d8f44a4_part_1_sidebar_2.css" rel="stylesheet"media="screen" />

Firstly you maybe wondering why there are 2 files. Above it was stated that Assetic would combine

the files into 1 CSS file. This is because we are running symblog in the developmentenvironment. We can ask Assetic to run in non-debug mode by setting the debug flag to false as

follows.

{# src/Blogger/BlogBundle/Resources/views/base.html.twig #}

{# .. #}

{% stylesheets'@BloggerBlogBundle/Resources/public/css/*'debug=false

%}<link href="{{ asset_url }}" rel="stylesheet" media="screen" />

{% endstylesheets %}

{# .. #}

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

<link href="/app_dev.php/css/3c7da45.css" rel="stylesheet" media="screen" />

Si ves el contenido de este archivo notarás que los dos archivos CSS , blog.css ysidebar.css se han combinado en un solo archivo. The filename given to the generated CSS

file is randomly generated by Assetic. If you would like to control the name given to the generated

file use the output option as follows.

{% stylesheets'@BloggerBlogBundle/Resources/public/css/*'output='css/blogger.css'

%}<link href="{{ asset_url }}" rel="stylesheet" media="screen" />

{% endstylesheets %}

Before you continue remove the debug flag from the previous snippet as we want to resume default behavior on the assets.

We also need to update the applications base template located at

Page 92: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 92/113

 

app/Resources/views/base.html.twig.

{# app/Resources/views/base.html.twig #}

{# .. #}

{% block stylesheets %}<link href='http://fonts.googleapis.com/css?family=Irish+Grover'

rel='stylesheet' type='text/css'><link href='http://fonts.googleapis.com/css?family=La+Belle+Aurore'

rel='stylesheet' type='text/css'>{% stylesheets

'css/*'%}

<link href="{{ asset_url }}" rel="stylesheet" media="screen" />{% endstylesheets %}

{% endblock %}

{# .. #}

JavaScripts¶

While we currently 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 }}"></script>

{% endjavascripts %}

Filtros¶

The real power in Assetic comes from the filters. Filters can be applied to assets or collections of 

assets. There are a large number of filters provided within the core of the library including the

following common filters:

1. CssMinFilter: minifies CSS

2. JpegoptimFilter: optimize your JPEGs

3. Yui\CssCompressorFilter: compresses CSS using the YUI compressor 

4. Yui\JsCompressorFilter: compresses JavaScript using the YUI compressor 

5. CoffeeScriptFilter: compiles CoffeeScript into JavaScript

There is a full list of available filters in the Assetic Readme.

Many of these filters pass the actual task onto another program or library, such as YUI Compressor,

so you may need to install/configure the appropriate libraries to use some of the filters.

Download the YUI Compressor , extract the archive and copy the file located in the builddirectory to app/Resources/java/yuicompressor-2.4.6.jar. This assumes you

downloaded the 2.4.6 version of the YUI Compressor. If not change your version number 

accordingly.

 Next we will configure an Assetic filter to minify the CSS using the YUI Compressor. Update the

application config located at app/config/config.ymlwith the following.

# app/config/config.yml

# ..

Page 93: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 93/113

 

assetic:filters:

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

# ..

We have configured a filter called yui_css that will use the YUI Compressor Java executable we placed in the applications resources directory. In order to use the filter you need to specify which

assets you want the filter applied to. Update the template located at

src/Blogger/BlogBundle/Resources/views/layout.html.twig to apply the

yui_css filter.

{# src/Blogger/BlogBundle/Resources/views/base.html.twig #}

{# .. #}

{% stylesheets'@BloggerBlogBundle/Resources/public/css/*'

output='css/blogger.css'filter='yui_css'%}

<link href="{{ asset_url }}" rel="stylesheet" media="screen" />{% endstylesheets %}

{# .. #}

 Now if you refresh the symblog website and view the files output by Assetic you will notice they

have been minified. While minification is great for production servers, it can make debugging

difficult, especially when JavaScript is minified. We can disable the minification when running in

the development environment by prefixing the filter with a ? as follows.

{% stylesheets'@BloggerBlogBundle/Resources/public/css/*'output='css/blogger.css'filter='?yui_css'

%}<link href="{{ asset_url }}" rel="stylesheet" media="screen" />

{% endstylesheets %}

Dumping the assets for production¶

In production we can dump the asset files using Assetic so they become actual resources on disk 

ready to be served by the web server. The process of creating the assets through Assetic with every page request can be quite slow, especially when filters are being applied to the assets. Dumping the

assets for production ensures that Assetic is not used to serve the assets and instead the pre-

 processed 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.

Page 94: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 94/113

 

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 anumber 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 multiplecomponents 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.

Page 95: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 95/113

 

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 testthat 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 totraverse 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);

Page 96: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 96/113

 

// Check 1 === 2 is false$this->assertFalse(1 === 2);

// Check 'Hello' equals 'Hello'$this->assertEquals('Hello', 'Hello');

// Check array has key 'language'$this->assertArrayHasKey('language', array('language' => 'php', 'size' =>'1024'));

// Check array contains value 'php'$this->assertContains('php', array('php', 'ruby', 'c++', 'JavaScript'));

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

Running Symfony2 Tests¶

Before we begin writing some tests, lets look at how we run tests in Symfony2. PHPUnit can be set

to execute using a configuration file. In our Symfony2 project this file is located at

app/phpunit.xml.dist. As this file is suffixed with .dist, you need to copy its contents

into a file called app/phpunit.xml.

Truco

If you are using a VCS such as Git, you should add the new app/phpunit.xml file to the VCS

ignore list.

If you have a look at the contents of the PHPUnit configuration file you will see the following.

<!-- app/phpunit.xml -->

<testsuites><testsuite name="Project Test Suite">

<directory>../src/*/*Bundle/Tests</directory><directory>../src/*/Bundle/*Bundle/Tests</directory>

</testsuite></testsuites>

The following settings configure some directories that are part of our test suite. When running

PHPUnit it will look in the above directories for tests to run. You can also pass additional command

line arguments to PHPUnit to run tests in specific directories, instead of the test suite tests. You will

see how to achieve this later.

You will also notice the configuration is specifying the bootstrap file located atapp/bootstrap.php.cache. This file is used by PHPUnit to get the testing environment

setup.

<!-- app/phpunit.xml -->

<phpunitbootstrap = "bootstrap.php.cache" >

Truco

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

documentation.

Page 97: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 97/113

 

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.phpyou will see the following content.

<?php// src/Blogger/BlogBundle/Tests/Controller/DefaultControllerTest.php

namespace Blogger\BlogBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class DefaultControllerTest extends WebTestCase{

public function testIndex(){

$client = static::createClient();

$impulsor = $cliente->request('GET', '/hola/Fabien');

$this->assertTrue($crawler->filter('html:contains("Hello Fabien")')->count() > 0);

}}

This is a functional test for the DefaultController class that Symfony2 generated. If you

remember back to chapter 1, this Controller had an action that handled requests to /hello/{name}. The fact that we removed this controller is why the above test is failing. Try going to the

URL http://symblog.dev/app_dev.php/hello/Fabien in your browser. You should

 be informed that the route could not be found. As the above test makes a request to the same URL,

it will also get the same response, hence why the test fails. Functional testing is a large part of this

chapter and will be covered in detail later.

As the DefaultController class has been removed, you can also remove this test class. Delete

the DefaultControllerTest class located atsrc/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

Page 98: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 98/113

 

Comment.phpController/

PageController.phpTwig/

Extensions/BloggerBlogExtension.php

Tests/

Entity/BlogTest.phpCommentTest.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.phpand add the following.

<?php// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php

namespace Blogger\BlogBundle\Tests\Entity;

use Blogger\BlogBundle\Entity\Blog;

class BlogTest extends \PHPUnit_Framework_TestCase

{

}

We have created a test class for the Blog entity. Notice the location of the file complies with the

folder structure mentioned above. The BlogTest class extends the base PHPUnit class

PHPUnit_Framework_TestCase. All tests you write for PHPUnit will be a child of this class.

You’ll remember from previous chapters that the \ must be placed in front of the

PHPUnit_Framework_TestCase class name as the class is declared in the PHP public

namespace.

 Now we have the skeleton class for our Blog entity tests, lets write a test case. Test cases inPHPUnit are methods of the Test class prefixed with test, such as testSlugify(). Update the

BlogTest located at src/Blogger/BlogBundle/Tests/Entity/BlogTest.phpwith

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

Page 99: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 99/113

 

}

This is a very simple test case. It instantiates a new Blog entity and runs an assertEquals()on the result of the slugify method. The assertEquals()method takes 2 mandatory

arguments, the expected result and the actual result. An optional 3rd argument can be passed in to

specify a message to display when the test case fails.

Lets run our new unit test. Run the following on the command line.

$ phpunit -c app

You should see the following output.

PHPUnit 3.5.11 por Sebastian Bergmann.

.

Time: 1 second, Memory: 4.25Mb

OK (1 test, 1 assertion)

The output from PHPUnit is very simple, Its start by displaying some information about PHPUnit

and the outputs a number of . for each test it runs, in our case we are only running 1 test so only 1

. is output. The last statement informs us of the result of the tests. For our BlogTest we only ran

1 test with 1 assertion. If you have color output on your command line you will also see the last line

displayed in green showing everything executed OK. Lets update the testSlugify() method to

see what happens when the tests fails.

// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php

// ..

public function testSlugify(){

$blog = new Blog();

$this->assertEquals('hello-world', $blog->slugify('Hello World'));$this->assertEquals('a day with symfony2', $blog->slugify('A Day With

Symfony2'));}

Re run the unit tests as before. The following output will be displayed

PHPUnit 3.5.11 por Sebastian Bergmann.

F

Time: 0 seconds, Memory: 4.25Mb

There was 1 failure:

1) Blogger\BlogBundle\Tests\Entity\BlogTest::testSlugifyFailed 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

Page 100: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 100/113

 

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::testSlugifymethod 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 tothe 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());

}

Page 101: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 101/113

 

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 setTitlemethod is called on the Blog entity.

Run the tests to verify the Blog entity is functioning correctly.

Testing the Twig extension¶

In the previous chapter we created a Twig extension to convert a \DateTime instance into a string

detailing the duration since a time period. Create a new test file located at

src/Blogger/BlogBundle/Tests/Twig/Extensions/BloggerBlogExtensionTest.php and update with the following content.

<?php// src/Blogger/BlogBundle/Tests/Twig/Extensions/BloggerBlogExtensionTest.php

namespace Blogger\BlogBundle\Tests\Twig\Extensions;

use Blogger\BlogBundle\Twig\Extensions\BloggerBlogExtension;

class BloggerBlogExtensionTest extends \PHPUnit_Framework_TestCase{

public function testCreatedAgo()

{ $blog = new BloggerBlogExtension();

$this->assertEquals("0 seconds ago", $blog->createdAgo(new \DateTime()));

$this->assertEquals("34 seconds ago", $blog->createdAgo($this->getDateTime(-34)));

$this->assertEquals("1 minute ago", $blog->createdAgo($this->getDateTime(-60)));

$this->assertEquals("2 minutes ago", $blog->createdAgo($this->getDateTime(-120)));

$this->assertEquals("1 hour ago", $blog->createdAgo($this->getDateTime(-3600)));

$this->assertEquals("1 hour ago", $blog->createdAgo($this->getDateTime(-3601)));$this->assertEquals("2 hours ago", $blog->createdAgo($this-

>getDateTime(-7200)));

// Cannot create time in the future$this->setExpectedException('\Exception');$blog->createdAgo($this->getDateTime(60));

}

protected function getDateTime($delta){

return new \DateTime(date("Y-m-d H:i:s", time()+$delta));

}}

Page 102: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 102/113

 

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 appsrc/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::testCreatedAgoFailed 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/BloggerBlogExtensionTest.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.

<?php// src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php

namespace Blogger\BlogBundle\Twig\Extensions;

class BloggerBlogExtension extends \Twig_Extension{

// ..

public function createdAgo(\DateTime $dateTime){

// ..if ($delta < 60){

// Segundos$time = $delta;$duration = $time . " second" . (($time === 0 || $time > 1) ? "s" :

"") . " ago";}// ..

}

// ..}

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

Page 103: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 103/113

 

fails. Lets examine the next output.

1)Blogger\BlogBundle\Tests\Twig\Extension\BloggerBlogExtensionTest::testCreatedAgoFailed 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/BloggerBlogExtensionTest.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 alsosee this is the case when checking for hours. Update the Twig extension located at

src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php to

correct this.

<?php// src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php

namespace Blogger\BlogBundle\Twig\Extensions;

class BloggerBlogExtension extends \Twig_Extension{

// ..

public function createdAgo(\DateTime $dateTime){

// ..

else if ($delta < 3600){

// Mins$time = floor($delta / 60);$duration = $time . " minute" . (($time > 1) ? "s" : "") . " ago";

}else if ($delta < 86400){

// Hours$time = floor($delta / 3600);$duration = $time . " hour" . (($time > 1) ? "s" : "") . " ago";

}

// ..}

// ..}

 Now re run all our tests using the following command.

$ phpunit -c app

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

Page 104: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 104/113

 

number of unit tests you should be getting a feel for how powerful and important testing is when

writing code. While the above errors were minor, they were still errors. Testing also helps any future

functionality added to the project breaking previous features. This concludes the unit testing for 

now. We will see more unit testing in the following chapters. Try adding some of your own unit

tests to test functionality that has been missed.

Functional Testing¶

 Now we have written some unit tests, lets move on to testing multiple components together. The

first section of the functional testing will involve simulating browser requests to tests the generated

responses.

Testing the About page¶

We begin testing the PageController class for the about page. As the about page is very

simple, this is a good place to start. Create a new file located at

src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php and

add the following content.

<?php// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

namespace Blogger\BlogBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class PageControllerTest extends WebTestCase{

public function testAbout(){

$client = static::createClient();

$crawler = $client->request('GET', '/about');

$this->assertEquals(1, $crawler->filter('h1:contains("About symblog")')->count());

}}

We have already seen a Controller test very similar to this when we briefly looked at the

DefaultControllerTest class. This is testing the about page of symblog, checking the string

About symblog is present in the generated HTML, specifically within the H1 tag. The

PageControllerTest class 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_TestCaseclass.

// vendor/symfony/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php

Page 105: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 105/113

 

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 (remember the PageControllerTestclass is still is child of the \PHPUnit_Framework_TestCase class).

Lets run the PageControllerTest using the following command. When writing tests its useful

to only execute the tests for the file you are currently working on. As your test suite gets large,

running tests can be a time consuming tasks.

$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

You should be greeted with the message OK (1 test, 1 assertion) letting us know that 1

test (the testAboutIndex()) ran, with 1 assertion (the assertEquals()).

Try changing the About symblog string to Contact and then re run the test. The test will now

fail as Contact wont be found, causing asertEquals to equate to false.1) Blogger\BlogBundle\Tests\Controller\PageControllerTest::testAboutIndexFailed asserting that <boolean:false> is true.

Revert the string back to About symblog before moving on.

The Crawler instance used allows you to traverse either HTML or XML documents (which

means the Crawler will only work with responses that return HTML or XML). We can use the

Crawler to traverse the generated response using methods such as filter(), first(),

last(), and parents(). If you have used jQuery before you should feel right at home with the

Crawler class. A full list of supported Crawler traversal methods can be found in the Testing 

chapter of the Symfony2 book. We will explore more of the Crawler features as we continue.

Homepage¶

While the test for the about page was simple, it has outlined the basic principles of functional

testing the website pages.

1. Create the client

2. Request a page

3. Check the response

This is a simple overview of the process, in fact there are a number of other steps we could also do

such as clicking links and populating and submitting forms.

Lets create a method to test the homepage. We know the homepage is available via the URL / and

Page 106: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 106/113

 

that is should display the latest blog posts. Add a new method testIndex() to the

PageControllerTest class located at

src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php as

shown below.

// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

public function testIndex(){

$client = static::createClient();

$impulsor = $cliente->request('GET', '/');

// Check there are some blog entries on the page$this->assertTrue($crawler->filter('article.blog')->count() > 0);

}

You can see the same steps are taken as with the tests for the about page. Run the test to ensure

everything is working as expected.

$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

Lets now take the testing a bit further. Part of functional testing involves being able to replicate

what a user would do on the site. In order for users to move between pages on your website they

click links. Lets simulate this action now to test the links to the show blog page work correctly

when the blog title is clicked. Update the testIndex() method in the PageControllerTestclass 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 nextpage

$blogLink = $crawler->filter('article.blog h2 a')->first();$blogTitle = $blogLink->text();$crawler = $client->click($blogLink->link());

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

>count());}

The first thing we do it use the Crawler to extract the text within the first blog title link. This is

done using the filter article.blog h2 a. This filter is used return the a tag within the H2 tag

of the article.blog article. To understand this better, have a look at the markup used on the

homepage for displaying blogs.

<article class="blog"><div class="date"><time datetime="2011-09-05T21:06:19+01:00">Monday,

September 5, 2011</time></div><header>

<h2><a href="/app_dev.php/1/a-day-with-symfony2">A day withSymfony2</a></h2>

</header>

<!-- .. --></article>

Page 107: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 107/113

 

<article class="blog"><div class="date"><time datetime="2011-09-05T21:06:19+01:00">Monday,

September 5, 2011</time></div><header>

<h2><a href="/app_dev.php/2/the-pool-on-the-roof-must-have-a-leak">Thepool on the roof must have a leak</a></h2>

</header>

<!-- .. --></article>

You can see the filter article.blog h2 a structure in place in the homepage markup. 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 Contact Page¶

Users of symblog are able to submit contact enquiries by completing the form on the contact page

http://symblog.dev/contact. Lets test that submissions of this form work correctly. First

we need to outline what should happen when the form is successfully submitted (successfully

submitted in this case means there are no errors present in the form).

1. Navigate to contact page

2. Populate contact form with values

3. Submit form

4. Check email was sent to symblog5. Check response to client contains notification of successful contact

So far we have explored enough to be able to complete steps 1 and 5 only. We will now look at how

to test the 3 middle steps.

Add a new method testContact() to the PageControllerTest class located at

src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php.

// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

public function testContact(){

$client = static::createClient();

$crawler = $client->request('GET', '/contact');

Page 108: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 108/113

 

$this->assertEquals(1, $crawler->filter('h1:contains("Contact symblog")')->count());

// Select based on button value, or id or name for buttons$form = $crawler->selectButton('Submit')->form();

$form['blogger_blogbundle_enquirytype[name]'] = 'name';$form['blogger_blogbundle_enquirytype[email]'] = '[email protected]';$form['blogger_blogbundle_enquirytype[subject]'] = 'Subject';$form['blogger_blogbundle_enquirytype[body]'] = 'The comment body must

be at least 50 characters long as there is a validation constrain on the Enquiryentity';

$crawler = $client->submit($form);

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

We begin in the usual fashion, making a request to the /contact URL, and checking the pagecontains the correct H1 title. Next we use the Crawler to select the form submit button. The

reason we select the button and not the form is that a form may contain multiple buttons that we

may want to click independently. From the selected button we are able to retrieve the form. We are

able to set the form values using the array subscript notation []. Finally the form is passed to the

client submit() method to actually submit the form. As usual, we receive a Crawler instance

 back. Using the Crawler response we check to ensure the flash message is present in the returned

response. Run the test to check everything is functioning correctly.

$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

The tests failed. We are given the following output from PHPUnit.1) Blogger\BlogBundle\Tests\Controller\PageControllerTest::testContactFailed asserting that <integer:0> matches expected <integer:1>.

/var/www/html/symblog/symblog/src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php:53

FAILURES!Tests: 3, Assertions: 5, Failures: 1.

The output is informing us that the flash message could not be found in the response from the form

submit. This is because when in the test environment, redirects are not followed. When the formis successfully validated in the PageController class a redirect happens. This redirect is not

 being followed; We need to explicitly say that the redirect should be followed. The reason redirects

are not followed is simple, you may want to check the current response first. We will demonstrate

this soon to check the email was sent. Update the PageControllerTest class to set the client

to follow the redirect.

// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

public function testContact(){

// ..

$crawler = $client->submit($form);

// Need to follow redirect

Page 109: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 109/113

 

$crawler = $client->followRedirect();

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

 No when you run the PHPUnit tests they should pass. Lets now look at the final step of checking

the contact form submission process, step 4, checking an email was sent to symblog. We alreadyknow that emails will not be delivered in the test environment due to the following configuration.

# app/config/config_test.yml

swiftmailer:disable_delivery: true

We can test the emails were sent using the information gathered by the web profiler. This is where

the importance of the client not following redirects comes in. The check on the profiler needs to be

done before the redirect happens, as the information in the profiler will be lost. Update the

testContact() message with the following.

// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

public function testContact(){

// ..

$crawler = $client->submit($form);

// Check email has been sentif ($profile = $client->getProfile()){

$swiftMailerProfiler = $profile->getCollector('swiftmailer');

// Only 1 message should have been sent$this->assertEquals(1, $swiftMailerProfiler->getMessageCount());

// Get the first message$messages = $swiftMailerProfiler->getMessages();$message = array_shift($messages);

$symblogEmail = $client->getContainer()->getParameter('blogger_blog.emails.contact_email');

// Check message is being sent to correct address$this->assertArrayHasKey($symblogEmail, $message->getTo());

}

// Need to follow redirect$crawler = $client->followRedirect();

$this->assertTrue($crawler->filter('.blogger-notice:contains("Your contactenquiry was successfully sent. Thank you!")')->count() > 0);}

After the form submit we check to see if the profiler is available as it may have been disabled by a

configuration setting for the current environment.

Truco

Remember tests don’t have to be run in the test environment, they could be run on the

production environment where things like the profiler wont be available.

Page 110: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 110/113

 

If we are able to get the profiler we make a request to retrieve the swiftmailer collector. The

swiftmailer collector works behind the scenes to gather information about how the emailing

service is used. We can use this to get information regarding which emails have been sent.

 Next we use the getMessageCount()method to check that 1 email was sent. This maybe

enough to ensure that at least an email is going to be sent, but it doesn’t verify that the email will be

sent to the correct location. It could be very embarrassing or even damaging for emails to be sent to

the wrong email address. To check this isn’t the case we verify the email to address is correct.

 Now re run the tests to check everything is working correctly.

$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

Testing Adding Blog Comments¶

Lets now use the knowledge we have gained from the previous tests on the contact page to test the

 process of submitting a blog comment. Again we outline what should happen when the form is

successfully submitted.

1. Navigate to a blog page

2. Populate comment form with values

3. Submit form

4. Check new comment is added to end of blog comment list

5. Also check sidebar latest comments to ensure comment is at top of list

Create a new file located at

src/Blogger/BlogBundle/Tests/Controller/BlogControllerTest.php and

add in the following.

<?php

// src/Blogger/BlogBundle/Tests/Controller/BlogControllerTest.php

namespace Blogger\BlogBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class BlogControllerTest extends WebTestCase{

public function testAddBlogComment(){

$client = static::createClient();

$crawler = $client->request('GET', '/1/a-day-with-symfony');

$this->assertEquals(1, $crawler->filter('h2:contains("A day withSymfony2")')->count());

// Select based on button value, or id or name for buttons$form = $crawler->selectButton('Submit')->form();

$crawler = $client->submit($form, array('blogger_blogbundle_commenttype[user]' => 'name','blogger_blogbundle_commenttype[comment]' => 'comment',

));

// Need to follow redirect$crawler = $client->followRedirect();

// Check comment is now displaying on page, as the last entry. Thisensure comments

Page 111: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 111/113

 

// are posted in order of oldest to newest$articleCrawler = $crawler->filter('section .previous-comments

article')->last();

$this->assertEquals('name', $articleCrawler->filter('headerspan.highlight')->text());

$this->assertEquals('comment', $articleCrawler->filter('p')->last()-

>text());

// Check the sidebar to ensure latest comments are display and there is10 of them

$this->assertEquals(10, $crawler->filter('aside.sidebar section')->last()

->filter('article')->count());

$this->assertEquals('name', $crawler->filter('aside.sidebar section')->last()

->filter('article')->first()

->filter('header span.highlight')->text()

);}

}

We jump straight in this time with the entire test. Before we begin dissecting the code, run the tests

for this file to ensure everything is working correctly.

$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Controller/BlogControllerTest.php

PHPUnit should inform you that the 1 test was executed successfully. Looking at the code for the

testAddBlogComment()we can see things begin in the usual format, creating a client,requesting a page and checking the page we are on is correct. We then proceed to get the add

comment form, and submit the form. The way we populate the form values is slightly different than

the previous version. This time we use the 2nd argument of the client submit() method to pass in

the values for the form.

Truco

We could also use the Object Oriented interface to set the values of the form fields. Some examples

are shown below.

// Tick a checkbox$form['show_emal']->tick();

// Select an option or a radio$form['gender']->select('Male');

After submitting the form, we request the client should follow the redirect so we can check the

response. We use the Crawler again to get the last blog comment, which should be the one we

 just submitted. Finally we check the latest comments in the sidebar to check the comment is also the

first one in the list.

Blog Repository¶

The last part of the functional testing we will explore in this chapter is testing a Doctrine 2repository. Create a new file located at

src/Blogger/BlogBundle/Tests/Repository/BlogRepositoryTest.php and

Page 112: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 112/113

 

add the following content.

<?php// src/Blogger/BlogBundle/Tests/Repository/BlogRepositoryTest.php

namespace Blogger\BlogBundle\Tests\Repository;

use Blogger\BlogBundle\Repository\BlogRepository;use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class BlogRepositoryTest extends WebTestCase{

/*** @var \Blogger\BlogBundle\Repository\BlogRepository*/private $blogRepository;

public function setUp(){

$kernel = static::createKernel();

$kernel->boot();$this->blogRepository = $kernel->getContainer()->get('doctrine.orm.entity_manager')->getRepository('BloggerBlogBundle:Blog')

;}

public function testGetTags(){

$tags = $this->blogRepository->getTags();

$this->assertTrue(count($tags) > 1);$this->assertContains('symblog', $tags);

}

public function testGetTagWeights(){

$tagsWeight = $this->blogRepository->getTagWeights(array('php', 'code', 'code', 'symblog', 'blog')

);

$this->assertTrue(count($tagsWeight) > 1);

// Test case where count is over max weight of 5$tagsWeight = $this->blogRepository->getTagWeights(

array_fill(0, 10, 'php')

);

$this->assertTrue(count($tagsWeight) >= 1);

// Test case with multiple counts over max weight of 5$tagsWeight = $this->blogRepository->getTagWeights(

array_merge(array_fill(0, 10, 'php'), array_fill(0, 2, 'html'),array_fill(0, 6, 'js'))

);

$this->assertEquals(5, $tagsWeight['php']);$this->assertEquals(3, $tagsWeight['js']);$this->assertEquals(1, $tagsWeight['html']);

// Test empty case$tagsWeight = $this->blogRepository->getTagWeights(array());

Page 113: symblog

5/14/2018 symblog - slidepdf.com

http://slidepdf.com/reader/full/symblog 113/113

 

$this->assertEmpty($tagsWeight);}

}

As we want to perform tests that require a valid connection to the database we extend the

WebTestCase again as this allows us to bootstrap the Symfony2 Kernel. Run this test for this file

using the following command.

$ phpunit -c app/ src/Blogger/BlogBundle/Tests/Repository/BlogRepositoryTest.php

Cobertura de código¶

Before we move on lets quickly touch on code coverage. Code coverage gives us an insight into

which parts of the code are executed when the tests are run. Using this we can see the parts of our 

code that have no tests run on them, and determine if we need to write test for them.

To output the code coverage analysis for your application run the following

$ phpunit --coverage-html ./phpunit-report -c app/

This will output the code coverage analysis to the folder phpunit-report. Open the

index.html file in your browser to see the analysis output.

See the Code Coverage Analysis chapter in the PHPUnit documentation for more information.

Conclusión¶

We have covered a number of key areas with regards to testing. We have explored both unit and

functional testing to ensure our website is functioning correctly. We have seen how to simulate

 browser requests and how to use the Symfony2 Crawler class to check the Response from these

requests.

 Next we will look at the Symfony2 security component, and more specifically how to use it for user 

management. También integraremos el FOSUserBundle y lo dejaremos listo para que trabajemos

en la sección de administración de symblog.