Upload
luis-olivera
View
477
Download
1
Embed Size (px)
Citation preview
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
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
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.
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.
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
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
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',
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.
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 %}
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") }}
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; }
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' %}
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.
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
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>
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
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.
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;}
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
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);
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// ..
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!
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 %}
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.
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.
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
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
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 ... #}
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()
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,
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.
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
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
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;
}
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:
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
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
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)
{ // ..}
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>
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?
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.
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
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"));
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.
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
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.
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()
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>
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
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()*/
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);
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
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);
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
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
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
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.
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"));
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?');
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
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
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
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
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.
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())) .
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(
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 #}
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.
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
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 .
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
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');
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
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>
{# .. #}
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);}
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
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
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
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.
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
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.
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 %}
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:
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
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.
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){
// ..
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.
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>
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
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'
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
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
# ..
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.
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.
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);
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.
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
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'));}
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
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());
}
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));
}}
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
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
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
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
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>
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');
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
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.
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
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
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());
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.