Creando y usando plantillas

Como sabes, el Controlador es responsable de manejar cada petición entrante en una aplicación Symfony2. En realidad, el controlador delega la mayor parte del trabajo pesado ​​a otros lugares para que el código se pueda probar y volver a utilizar. Cuando un controlador necesita generar HTML, CSS o cualquier otro contenido, que maneje el trabajo fuera del motor de plantillas. En este capítulo, aprenderás cómo escribir potentes plantillas que puedes utilizar para devolver contenido al usuario, rellenar el cuerpo de correo electrónico y mucho más. Aprenderás métodos abreviados, formas inteligentes para extender las plantillas y cómo reutilizar código de plantilla.

Nota

El tema de cómo reproducir plantillas se aborda en la página controlador del libro.

Plantillas

Una plantilla simplemente es un archivo de texto que puede generar cualquier formato basado en texto (HTML, XML, CSV, LaTeX...). El tipo de plantilla más familiar es una plantilla PHP — un archivo de texto interpretado por PHP que contiene una mezcla de texto y código PHP:

<!DOCTYPE html>
    <html>
        <head>
        <title>Welcome to Symfony!</title>
        </head>
        <body>
        <h1><?php echo $page_title ?></h1>

        <ul id="navigation">
            <?php foreach ($navigation as $item): ?>
                <li>
                    <a href="<?php echo $item->getHref() ?>">
                        <?php echo $item->getCaption() ?>
                        </a>
                </li>
            <?php endforeach; ?>
        </ul>
    </body>
</html>

Pero Symfony2 contiene un lenguaje de plantillas aún más potente llamado Twig. Twig te permite escribir plantillas concisas y fáciles de leer que son más amigables para los diseñadores web y, de varias maneras, más poderosas que las plantillas PHP:

<!DOCTYPE html>
    <html>
        <head>
        <title>Welcome to Symfony!</title>
        </head>
        <body>
        <h1>{{ page_title }}</h1>

        <ul id="navigation">
            {% for item in navigation %}
                <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
            {% endfor %}
        </ul>
    </body>
</html>

Twig define dos tipos de sintaxis especial:

  • {{ ... }}: «Dice algo»: imprime una variable o el resultado de una expresión a la plantilla;
  • {% ... %}: «Hace algo»: una etiqueta que controla la lógica de la plantilla; se utiliza para declaraciones if y ejecutar bucles for, por ejemplo.

Nota

Hay una tercer sintaxis utilizada para crear comentarios: {# esto es un comentario #}. Esta sintaxis se puede utilizar en múltiples líneas como la sintaxis /* comentario */ equivalente de PHP.

Twig también contiene filtros, los cuales modifican el contenido antes de reproducirlo. El siguiente fragmento convierte a mayúsculas la variable title antes de reproducirla:

{{ title|upper }}

Twig viene con una larga lista de etiquetas y filtros que están disponibles de forma predeterminada. Incluso puedes agregar tus propias extensiones a Twig, según sea necesario.

Truco

Registrar una extensión Twig es tan fácil como crear un nuevo servicio y etiquetarlo con twig.extension.

Como verás a través de la documentación, Twig también es compatible con funciones y fácilmente puedes añadir nuevas. Por ejemplo, la siguiente función, utiliza una etiqueta for estándar y la función cycle para imprimir diez etiquetas div, alternando entre clases par e impar:

{% for i in 0..10 %}
    <div class="{{ cycle(['odd', 'even'], i) }}">
      <!-- some HTML here -->
    </div>
{% endfor %}

A lo largo de este capítulo, mostraremos las plantillas de ejemplo en ambos formatos Twig y PHP.

Truco

Si decides no utilizar Twig y lo desactivas, tendrás que implementar tu propio controlador de excepciones a través del evento kernel.exception.

Guardando en caché plantillas Twig

Twig es rápido. Cada plantilla Twig se compila hasta una clase PHP nativa que se reproduce en tiempo de ejecución. Las clases compiladas se encuentran en el directorio app/cache/{entorno}/twig (donde {entorno} es el entorno, tal como dev o prod) y, en algunos casos, pueden ser útiles mientras depuras. Consulta la sección Entornos para más información sobre los entornos.

Cuando está habilitado el modo debug (comúnmente en el entorno dev) al realizar cambios a una plantilla Twig, esta se vuelve a compilar automáticamente. Esto significa que durante el desarrollo, felizmente, puedes realizar cambios en una plantilla Twig e inmediatamente ver las modificaciones sin tener que preocuparte de limpiar ninguna caché.

Cuando el modo debug está desactivado (comúnmente en el entorno prod), sin embargo, debes borrar el directorio de caché para regenerar las plantillas. Recuerda hacer esto al desplegar tu aplicación.

Plantillas, herencia y diseño

A menudo, las plantillas en un proyecto comparten elementos comunes, como el encabezado, pie de página, barra lateral o más. En Symfony2, nos gusta pensar en este problema de forma diferente: una plantilla se puede decorar con otra. Esto funciona exactamente igual que las clases PHP: la herencia de plantillas nos permite crear un «diseño» de plantilla base que contiene todos los elementos comunes de tu sitio definidos como bloques (piensa en «clases PHP con métodos base»). Una plantilla hija puede extender el diseño base y reemplazar cualquiera de sus bloques (piensa en las «subclases PHP que sustituyen determinados métodos de su clase padre»).

En primer lugar, crea un archivo con tu diseño base:

  • Twig
    {# app/Resources/views/base.html.twig #}
    <!DOCTYPE html>
        <html>
            <head>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
            <title>{% block title %}Test Application{% endblock %}</title>
            </head>
            <body>
            <div id="sidebar">
                {% block sidebar %}
                <ul>
                    <li><a href="/">Home</a></li>
                    <li><a href="/blog">Blog</a></li>
                </ul>
                {% endblock %}
            </div>
    
            <div id="contenido">
                {% block body %}{% endblock %}
            </div>
        </body>
    </html>
    
  • PHP
    <!-- app/Resources/views/base.html.php -->
    <!DOCTYPE html>
        <html>
            <head>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
            <title><?php $view['slots']->output('title', 'Test Application') ?></title>
            </head>
            <body>
            <div id="sidebar">
                <?php if ($view['slots']->has('sidebar')): ?>
                    <?php $view['slots']->output('sidebar') ?>
                <?php else: ?>
                    <ul>
                        <li><a href="/">Home</a></li>
                        <li><a href="/blog">Blog</a></li>
                    </ul>
                <?php endif; ?>
            </div>
    
            <div id="contenido">
                <?php $view['slots']->output('body') ?>
            </div>
        </body>
    </html>
    

Nota

Aunque la explicación sobre la herencia de plantillas será en términos de Twig, la filosofía es la misma entre plantillas Twig y PHP.

Esta plantilla define el esqueleto del documento HTML base de una simple página de dos columnas. En este ejemplo, se definen tres áreas {% block %} (title, sidebar y body). Una plantilla hija puede sustituir cada uno de los bloques o dejarlos con su implementación predeterminada. Esta plantilla también se podría reproducir directamente. En este caso, los bloques title, sidebar y body simplemente mantienen los valores predeterminados usados en esta plantilla.

Una plantilla hija podría tener este aspecto:

  • Twig
    {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #}
    {% extends '::base.html.twig' %}
    
    {% block title %}My cool blog posts{% endblock %}
    
    {% block body %}
        {% for entry in blog_entries %}
            <h2>{{ entry.title }}</h2>
            <p>{{ entry.body }}</p>
        {% endfor %}
    {% endblock %}
    
  • PHP
    <!-- src/Acme/BlogBundle/Resources/views/Blog/index.html.php -->
    <?php $view->extend('::base.html.php') ?>
    
    <?php $view['slots']->set('title', 'My cool blog posts') ?>
    
    <?php $view['slots']->start('body') ?>
        <?php foreach ($blog_entries as $entry): ?>
            <h2><?php echo $entry->getTitle() ?></h2>
            <p><?php echo $entry->getBody() ?></p>
        <?php endforeach; ?>
    <?php $view['slots']->stop() ?>
    

Nota

La plantilla padre se identifica mediante una sintaxis de cadena especial (::base.html.twig) la cual indica que la plantilla vive en el directorio app/Resources/views del proyecto. Esta convención de nomenclatura se explica completamente en Nomenclatura y ubicación de plantillas.

La clave para la herencia de plantillas es la etiqueta {% extends %}. Esta le indica al motor de plantillas que primero evalúe la plantilla base, la cual establece el diseño y define varios bloques. Luego reproduce la plantilla hija, en ese momento, los bloques title y body del padre son reemplazados por los de la hija. Dependiendo del valor de blog_entries, el resultado sería algo como esto:

<!DOCTYPE html>
    <html>
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>My cool blog posts</title>
        </head>
        <body>
        <div id="sidebar">
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/blog">Blog</a></li>
            </ul>
        </div>

        <div id="contenido">
            <h2>My first post</h2>
            <p>The body of the first post.</p>

            <h2>Another post</h2>
            <p>The body of the second post.</p>
        </div>
    </body>
</html>

Ten en cuenta que como en la plantilla hija no has definido un bloque sidebar, en su lugar, se utiliza el valor de la plantilla padre. Una plantilla padre, de forma predeterminada, siempre utiliza una etiqueta {% block %} para el contenido.

Puedes utilizar tantos niveles de herencia como quieras. En la siguiente sección, explicaremos un modelo común de tres niveles de herencia junto con la forma en que se organizan las plantillas dentro de un proyecto Symfony2.

Cuando trabajes con la herencia de plantillas, ten en cuenta los siguientes consejos:

  • Si utilizas {% extends %} en una plantilla, esta debe ser la primer etiqueta en esa plantilla;

  • Mientras más etiquetas {% block %} tengas en tu plantilla base, mejor. Recuerda, las plantillas hijas no tienen que definir todos los bloques de los padres, por lo tanto crea tantos bloques en tus plantillas base como desees y dale a cada uno un razonable valor predeterminado. Mientras más bloques tengan tus plantillas base, más flexible será tu diseño;

  • Si te encuentras duplicando contenido en una serie de plantillas, muy probablemente significa que debes mover el contenido a un {% block %} en una plantilla padre. En algunos casos, una mejor solución podría ser mover el contenido a una nueva plantilla e incluirla con include (consulta Incluyendo otras plantillas);

  • Si necesitas conseguir el contenido de un bloque desde la plantilla padre, puedes usar la función {{ parent() }}. Esta es útil si deseas añadir algo al contenido de un bloque padre en vez de reemplazarlo por completo:

    {% block sidebar %}
        <h3>Table of Contents</h3>
    
        {# ... #}
    
        {{ parent() }}
    {% endblock %}
    

Nomenclatura y ubicación de plantillas

Nuevo en la versión 2.2: El soporte necesario para rutas en el espacio de nombres se añadió en 2.2, permitiendo nombres de plantilla tal como @AcmeDemo/layout.html.twig. Consulta Cómo utilizar y rutas Twig con espacios de nombres para más detalles.

De forma predeterminada, las plantillas pueden vivir en dos diferentes lugares:

  • app/Resources/views/: El directorio de las vistas de la aplicación puede contener todas las plantillas base de la aplicación (es decir, los diseños de tu aplicación), así como plantillas que sustituyen a plantillas de paquetes (consulta Sustituyendo plantillas del paquete);
  • ruta/al/paquete/Resources/views/ Cada paquete contiene sus plantillas en su directorio (y subdirectorios) Resources/views. La mayoría de las plantillas viven dentro de un paquete.

Symfony2 utiliza una sintaxis de cadena paquete:controlador:plantilla para las plantillas. Esto permite diferentes tipos de plantilla, dónde cada una vive en un lugar específico:

  • AcmeBlogBundle:Blog:index.html.twig: Esta sintaxis se utiliza para especificar una plantilla para una página específica. Las tres partes de la cadena, cada una separada por dos puntos (:), significan lo siguiente:

    • AcmeBlogBundle: (paquete) la plantilla vive dentro de AcmeBlogBundle (por ejemplo, src/Acme/BlogBundle);
    • Blog: (controlador) indica que la plantilla vive dentro del subdirectorio Blog de Resources/views;
    • index.html.twig: (plantilla) el nombre real del archivo es index.html.twig.

    Suponiendo que AcmeBlogBundle vive en src/Acme/BlogBundle, la ruta final para el diseño debería ser src/Acme/BlogBundle/Resources/views/Blog/index.html.twig.

  • AcmeBlogBundle::layout.html.twig: Esta sintaxis se refiere a una plantilla base que es específica para AcmeBlogBundle. Puesto que falta la porción central, «controlador» (por ejemplo, Blog), la plantilla vive en Resources/views/layout.html.twig dentro de AcmeBlogBundle.

  • ::base.html.twig: Esta sintaxis se refiere a una plantilla o diseño base de la aplicación. Observa que la cadena comienza con dobles dos puntos (::), lo cual significa que faltan ambas porciones paquete y controlador. Esto quiere decir que la plantilla no se encuentra en ningún paquete, sino en el directorio raíz de la aplicación app/Resources/views/.

En la sección Sustituyendo plantillas del paquete, encontrarás cómo puedes sustituir cada plantilla que vive dentro de AcmeBlogBundle, por ejemplo, colocando una plantilla del mismo nombre en el directorio app/Resources/AcmeBlog/views/. Esto nos da el poder para sustituir plantillas de cualquier paquete de terceros.

Truco

Esperemos que la sintaxis de nomenclatura de plantilla te resulte familiar —es la misma convención de nomenclatura utilizada para referirse al Patrón de nomenclatura para controladores.

Sufijo de plantilla

El formato paquete:controlador:plantilla de cada plantilla, especifica dónde se encuentra el archivo de plantilla. Cada nombre de plantilla también cuenta con dos extensiones que especifican el formato y motor de esa plantilla.

  • AcmeBlogBundle:Blog:index.html.twig — formato HTML, motor Twig
  • AcmeBlogBundle:Blog:index.html.php — formato HTML, motor PHP
  • AcmeBlogBundle:Blog:index.css.twig — formato CSS, motor Twig

De forma predeterminada, cualquier plantilla Symfony2 se puede escribir en Twig o PHP, y la última parte de la extensión (por ejemplo .twig o .php) especifica cuál de los dos motores se debe utilizar. La primera parte de la extensión, (por ejemplo .html, .css, etc.) es el formato final que la plantilla debe generar. A diferencia del motor, el cual determina cómo procesa Symfony2 la plantilla, esta simplemente es una táctica de organización utilizada en caso de que el mismo recurso se tenga que reproducir como HTML (index.html.twig), XML (index.xml.twig), o cualquier otro formato. Para más información, lee la sección Depurando.

Nota

Los «motores» disponibles se pueden configurar e incluso agregar nuevos motores. Consulta Configuración de plantillas para más detalles.

Etiquetas y ayudantes

Ya entendiste los conceptos básicos de las plantillas, cómo son denominadas y cómo utilizar la herencia en plantillas. Las partes más difíciles ya quedaron atrás. En esta sección, aprenderás acerca de un amplio grupo de herramientas disponibles para ayudarte a realizar las tareas de plantilla más comunes, como la inclusión de otras plantillas, enlazar páginas e incluir imágenes.

Symfony2 viene con varias etiquetas Twig especializadas y funciones que facilitan la labor del diseñador de la plantilla. En PHP, el sistema de plantillas extensible ofrece un sistema de ayudantes que proporciona funciones útiles en el contexto de la plantilla.

Ya hemos visto algunas etiquetas integradas en Twig ({% block %} y {% extends %}), así como un ejemplo de un ayudante PHP (consulta $view['slot']). Aquí aprenderás un poco más.

Incluyendo otras plantillas

A menudo querrás incluir la misma plantilla o fragmento de código en varias páginas diferentes. Por ejemplo, en una aplicación con «artículos de noticias», el código de la plantilla que muestra un artículo se puede utilizar en la página de detalles del artículo, en una página que muestra los artículos más populares, o en una lista de los artículos más recientes.

Cuando necesitas volver a utilizar un trozo de código PHP, normalmente mueves el código a una nueva clase o función PHP. Lo mismo es cierto para las plantillas. Al mover el código de la plantilla a su propia plantilla, este se puede incluir en cualquier otra plantilla. En primer lugar, crea la plantilla que tendrás que volver a usar.

  • Twig
    {# src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.twig #}
    <h2>{{ article.title }}</h2>
    <h3 class="byline">by {{ article.authorName }}</h3>
    
    <p>
        {{ article.body }}
    </p>
    
  • PHP
    <!-- src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.php -->
    <h2><?php echo $article->getTitle() ?></h2>
    <h3 class="byline">by <?php echo $article->getAuthorName() ?></h3>
    
    <p>
        <?php echo $article->getBody() ?>
    </p>
    

Incluir esta plantilla en cualquier otra plantilla es sencillo:

  • Twig
    {# src/Acme/ArticleBundle/Resources/views/Article/list.html.twig #}
    {% extends 'AcmeArticleBundle::layout.html.twig' %}
    
    {% block body %}
        <h1>Recent Articles<h1>
    
        {% for article in articles %}
            {{ include('AcmeArticleBundle:Article:articleDetails.html.twig', {'article': article}) }}
        {% endfor %}
    {% endblock %}
    
  • PHP
    <!-- src/Acme/ArticleBundle/Resources/Article/list.html.php -->
    <?php $view->extend('AcmeArticleBundle::layout.html.php') ?>
    
    <?php $view['slots']->start('body') ?>
        <h1>Recent Articles</h1>
    
        <?php foreach ($articles as $article): ?>
            <?php echo $view->render(
                'AcmeArticleBundle:Article:articleDetails.html.php',
                array('article' => $article)
            ) ?>
        <?php endforeach; ?>
    <?php $view['slots']->stop() ?>
    

La plantilla se incluye utilizando la función {{ include() }}. Observa que el nombre de la plantilla sigue la misma convención típica. La plantilla articleDetails.html.twig utiliza una variable article. Esta es proporcionada por la plantilla list.html.twig utilizando la orden with.

Truco

{'article': article} es la sintaxis de asignación estándar de Twig (es decir, un arreglo con claves nombradas). Si tuviéramos que pasar varios elementos, se vería así: {'foo': foo, 'bar': bar}.

Nuevo en la versión 2.2: La función include() es una nueva característica de Twig que está disponible en Symfony 2.2. Anteriormente se utilizaba la etiqueta {% include %}.

Integrando controladores

En algunos casos, es necesario hacer algo más que incluir una simple plantilla. Supongamos que en tu diseño tienes una barra lateral, la cual contiene los tres artículos más recientes. Recuperar los tres artículos puede incluir consultar la base de datos o realizar otra pesada lógica ​​que no se puede hacer desde dentro de una plantilla.

La solución es simplemente insertar el resultado de un controlador en tu plantilla entera. En primer lugar, crea un controlador que reproduzca una cierta cantidad de artículos recientes:

// src/Acme/ArticleBundle/Controller/ArticleController.php
class ArticleController extends Controller
{
    public function recentArticlesAction($max = 3)
    {
        // hace una llamada a la base de datos u otra lógica
        // para obtener los "$max" artículos más recientes
        $articles = ...;

        return $this->render(
            'AcmeArticleBundle:Article:recentList.html.twig',
            array('articles' => $articles)
        );
    }
}

La plantilla recentList es perfectamente clara:

  • Twig
    {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}
    {% for article in articles %}
        <a href="/article/{{ article.slug }}">
            {{ article.title }}
            </a>
    {% endfor %}
    
  • PHP
    <!-- src/Acme/ArticleBundle/Resources/views/Article/recentList.html.php -->
    <?php foreach ($articles as $article): ?>
        <a href="/article/<?php echo $article->getSlug() ?>">
            <?php echo $article->getTitle() ?>
            </a>
    <?php endforeach; ?>
    

Nota

Ten en cuenta que la URL del artículo está directamente en el código en este ejemplo (p. ej. /article/*slug*). Esta es una mala práctica. En la siguiente sección, aprenderás cómo hacer esto correctamente.

Para incluir el controlador, tendrás que referirte a él utilizando la sintaxis de cadena estándar para controladores (es decir, paquete:controlador:acción):

  • Twig
    {# app/Resources/views/base.html.twig #}
    
    {# ... #}
    <div id="sidebar">
        {{ render(controller('AcmeArticleBundle:Article:recentArticles', { 'max': 3 })) }}
    </div>
    
  • PHP
    <!-- app/Resources/views/base.html.php -->
    
    <!-- ... -->
    <div id="sidebar">
        <?php echo $view['actions']->render(
            new ControllerReference('AcmeArticleBundle:Article:recentArticles', array('max' => 3))
        ) ?>
    </div>
    

Cada vez que te encuentres necesitando una variable o una pieza de información a la que una plantilla no tiene acceso, considera reproducir un controlador. Los controladores se ejecutan rápidamente y promueven la buena organización y reutilización de código.

Contenido asíncrono con hinclude.js

Nuevo en la versión 2.1: La compatibilidad con hinclude.js se añadió en Symfony 2.1

Los controladores se pueden incorporar asincrónicamente usando la biblioteca JavaScript hinclude.js. Debido a que el contenido integrado proviene de otra página (o controlador para el caso), Symfony2 utiliza el ayudante render estándar para configurar las etiquetas hinclude:

  • Twig
    {% render url('...') with {}, {'standalone': 'js'} %}
    
  • PHP
    <?php echo $view['actions']->render(
        new ControllerReference('...'),
        array('renderer' => 'hinclude')
    ) ?>
    
    <?php echo $view['actions']->render(
        $view['router']->generate('...'),
        array('renderer' => 'hinclude')
    ) ?>
    

Nota

Para que trabaje, debes incluir hinclude.js en tu página.

Nota

Al utilizar un controlador en vez de una URL, debes habilitar la configuración de fragmentos de Symfony:

  • YAML
    # app/config/config.yml
    framework:
        # ...
        fragments: { path: /_fragment }
    
  • XML
    <!-- app/config/config.xml -->
    <framework:config>
        <framework:fragments path="/_fragment" />
    </framework:config>
    
  • PHP
    // app/config/config.php
    $container->loadFromExtension('framework', array(
        // ...
        'fragments' => array('path' => '/_fragment'),
    ));
    

Puedes especificar globalmente el contenido predeterminado (al cargar o si JavaScript está desactivado) en la configuración de tu aplicación:

  • YAML
    # app/config/config.yml
    framework:
        # ...
        templating:
            hinclude_default_template: AcmeDemoBundle::hinclude.html.twig
    
  • XML
    <!-- app/config/config.xml -->
    <framework:config>
        <framework:templating hinclude-default-template="AcmeDemoBundle::hinclude.html.twig" />
    </framework:config>
    
  • PHP
    // app/config/config.php
    $container->loadFromExtension('framework', array(
        // ...
        'templating'      => array(
            'hinclude_default_template' => array('AcmeDemoBundle::hinclude.html.twig'),
        ),
    ));
    

Nuevo en la versión 2.2: Las plantillas default para la función render se añadieron en Symfony 2.2

Puedes definir plantillas default para la función render (lo cuál sustituye cualquier plantilla default global que esté definida):

  • Twig
    {{ render_hinclude(controller('...'),  {'default': 'AcmeDemoBundle:Default:content.html.twig'}) }}
    
  • PHP
    <?php echo $view['actions']->render(
        new ControllerReference('...'),
        array(
            'renderer' => 'hinclude',
            'default' => 'AcmeDemoBundle:Default:content.html.twig',
        )
    ) ?>
    

O puedes especificar además, una cadena que se mostrará como el contenido predefinido:

  • Twig
    {{ render_hinclude(controller('...'), {'default': 'Loading...'}) }}
    
  • PHP
    <?php echo $view['actions']->render(
        new ControllerReference('...'),
        array(
            'renderer' => 'hinclude',
            'default' => 'Loading...',
        )
    ) ?>
    

Enlazando páginas

La creación de enlaces a otras páginas en tu aplicación es uno de los trabajos más comunes de una plantilla. En lugar de codificar las URL en las plantillas, utiliza la función path de Twig (o el ayudante router en PHP) para generar URL basadas en la configuración de enrutado. Más tarde, si deseas modificar la URL de una página en particular, todo lo que tienes que hacer es cambiar la configuración de enrutado; las plantillas automáticamente generarán la nueva URL.

En primer lugar, crea el enlace a la página «_welcome», la cual es accesible a través de la siguiente configuración de enrutado:

  • YAML
    _welcome:
        path:     /
        defaults: { _controller: AcmeDemoBundle:Welcome:index }
    
  • XML
    <route id="_welcome" path="/">
        <default key="_controller">AcmeDemoBundle:Welcome:index</default>
    </route>
    
  • PHP
    $collection = new RouteCollection();
    $collection->add('_welcome', new Route('/', array(
        '_controller' => 'AcmeDemoBundle:Welcome:index',
    )));
    
    return $collection;
    

Para enlazar a la página, sólo tienes que utilizar la función path de Twig y referir la ruta:

  • Twig
    <a href="{{ path('_welcome') }}">Home</a>
    
  • PHP
    <a href="<?php echo $view['router']->generate('_welcome') ?>">Home</a>
    

Como era de esperar, esto genera la URL /. Ahora para una ruta más complicada:

  • YAML
    article_show:
        path:     /article/{slug}
        defaults: { _controller: AcmeArticleBundle:Article:show }
    
  • XML
    <route id="article_show" path="/article/{slug}">
        <default key="_controller">AcmeArticleBundle:Article:show</default>
    </route>
    
  • PHP
    $collection = new RouteCollection();
    $collection->add('article_show', new Route('/article/{slug}', array(
        '_controller' => 'AcmeArticleBundle:Article:show',
    )));
    
    return $collection;
    

En este caso, es necesario especificar el nombre de la ruta (article_show) y un valor para el parámetro {slug}. Usando esta ruta, revisemos la plantilla recentList de la sección anterior y enlacemos los artículos correctamente:

  • Twig
    {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}
    {% for article in articles %}
        <a href="{{ path('article_show', {'slug': article.slug}) }}">
            {{ article.title }}
            </a>
    {% endfor %}
    
  • PHP
    <!-- src/Acme/ArticleBundle/Resources/views/Article/recentList.html.php -->
    <?php foreach ($articles in $article): ?>
        <a href="<?php echo $view['router']->generate('article_show', array('slug' => $article->getSlug()) ?>">
            <?php echo $article->getTitle() ?>
            </a>
    <?php endforeach; ?>
    

Truco

También puedes generar una URL absoluta utilizando la función url de Twig:

<a href="{{ url('_welcome') }}">Home</a>

Lo mismo se puede hacer en plantillas PHP pasando un tercer argumento al método generate():

<a href="<?php echo $view['router']->generate(
    '_welcome',
    array(),
    true
) ?>">Home</a>

Enlazando activos

Las plantillas también se refieren comúnmente a imágenes, JavaScript, hojas de estilo y otros activos. Por supuesto, puedes codificar la ruta de estos activos (por ejemplo /images/logo.png), pero Symfony2 ofrece una opción más dinámica a través de la función asset de Twig:

  • Twig
    <img src="{{ asset('images/logo.png') }}" alt="Symfony!" />
    
    <link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" />
    
  • PHP
    <img src="<?php echo $view['assets']->getUrl('images/logo.png') ?>" alt="Symfony!" />
    
    <link href="<?php echo $view['assets']->getUrl('css/blog.css') ?>" rel="stylesheet" type="text/css" />
    

El propósito principal de la función asset es hacer más portátil tu aplicación. Si tu aplicación vive en la raíz de tu servidor (por ejemplo, http://ejemplo.com), entonces las rutas reproducidas deben ser /images/logo.png. Pero si tu aplicación vive en un subdirectorio (por ejemplo, http://ejemplo.com/mi_aplic), cada ruta de activo debe reproducir el subdirectorio (por ejemplo /mi_aplic/images/logo.png). La función asset se encarga de esto determinando cómo se está utilizando tu aplicación y generando las rutas correctas en consecuencia.

Además, si utilizas la función asset, Symfony automáticamente puede añadir una cadena de consulta a tu activo, con el fin de garantizar que los activos estáticos actualizados no se almacenen en caché al desplegar tu aplicación. Por ejemplo, /images/logo.png podría ser /images/logo.png?v2. Para más información, consulta la opción de configuración assets_version.

Incluyendo hojas de estilo y JavaScript en Twig

Ningún sitio estaría completo sin incluir archivos de JavaScript y hojas de estilo. En Symfony, la inclusión de estos activos se maneja elegantemente, aprovechando la herencia de plantillas de Symfony.

Truco

Esta sección te enseñará la filosofía detrás de la inclusión de activos como hojas de estilo y Javascript en Symfony. Symfony también empaca otra biblioteca, llamada Assetic, la cual sigue esta filosofía, pero te permite hacer cosas mucho más interesantes con esos activos. Para más información sobre el uso de Assetic consulta Cómo utilizar Assetic para gestionar activos.

Comienza agregando dos bloques a la plantilla base que mantendrá tus activos: uno llamado stylesheet dentro de la etiqueta head y otro llamado javascript justo por encima de la etiqueta de cierre body. Estos bloques deben contener todas las hojas de estilo y archivos Javascript que necesitas en tu sitio:

{# app/Resources/views/base.html.twig #}
    <html>
        <head>
        {# ... #}

        {% block stylesheets %}
            <link href="{{ asset('/css/main.css') }}" type="text/css" rel="stylesheet" />
        {% endblock %}
        </head>
        <body>
        {# ... #}

        {% block javascripts %}
            <script src="{{ asset('/js/main.js') }}" type="text/javascript"></script>
        {% endblock %}
    </body>
</html>

¡Eso es bastante fácil! Pero ¿y si es necesario incluir una hoja de estilo extra o archivos Javascript desde una plantilla hija? Por ejemplo, supongamos que tienes una página de contacto y necesitas incluir una hoja de estilo contact.css sólo en esa página. Desde dentro de la plantilla de la página de contacto, haz lo siguiente:

{# src/Acme/DemoBundle/Resources/views/Contact/contact.html.twig #}
{% extends '::base.html.twig' %}

{% block stylesheets %}
    {{ parent() }}

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

{# ... #}

En la plantilla hija, sólo tienes que reemplazar el bloque stylesheets y poner tu nueva etiqueta de hoja de estilo dentro de ese bloque. Por supuesto, debido a que la quieres añadir al contenido del bloque padre (y no cambiarla en realidad), debes usar la función parent() de Twig para incluir todo, desde el bloque stylesheets de la plantilla base.

Además, puedes incluir activos ubicados en el directorio Resources/public de tus paquetes. Deberás ejecutar la orden php app/console assets:install destino [--symlink], la cual mueve (o enlaza simbólicamente) tus archivos a la ubicación correcta. (destino por omisión es «web»).

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

El resultado final es una página que incluye ambas hojas de estilo main.css y contact.css.

Variables de plantilla globales

En cada petición, Symfony2 debe configurar una variable de plantilla global app en ambos motores de plantilla predefinidos Twig y PHP. La variable app es una instancia de Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables que automáticamente te proporciona acceso a algunas variables específicas de la aplicación:

  • app.security - El contexto de seguridad.
  • app.user - El objeto Usuario actual.
  • app.request - El objeto Petición.
  • app.session - El objeto Sesión.
  • app.environment - El entorno actual («dev», «prod», etc.)
  • app.debug - True si está en modo de depuración. False en caso contrario.
  • Twig
    <p>Username: {{ app.user.username }}</p>
    {% if app.debug %}
        <p>Request method: {{ app.request.method }}</p>
        <p>Application Environment: {{ app.environment }}</p>
    {% endif %}
    
  • PHP
    <p>Username: <?php echo $app->getUser()->getUsername() ?></p>
    <?php if ($app->getDebug()): ?>
        <p>Request method: <?php echo $app->getRequest()->getMethod() ?></p>
        <p>Application Environment: <?php echo $app->getEnvironment() ?></p>
    <?php endif; ?>
    

Truco

Puedes agregar tus propias variables de plantilla globales. Ve el ejemplo de variables globales en el recetario.

Configurando y usando el servicio plantilla

El corazón del sistema de plantillas en Symfony2 es el motor de plantillas. Este objeto especial es el encargado de reproducir las plantillas y devolver su contenido. Cuando reproduces una plantilla en un controlador, por ejemplo, en realidad estás usando el motor del servicio de plantillas. Por ejemplo:

return $this->render('AcmeArticleBundle:Article:index.html.twig');

es equivalente a:

use Symfony\Component\HttpFoundation\Response;

$engine = $this->container->get('templating');
$content = $engine->render('AcmeArticleBundle:Article:index.html.twig');

return $response = new Response($content);

El motor de plantillas (o «servicio») está configurado para funcionar automáticamente al interior de Symfony2. Por supuesto, puedes configurar más en el archivo de configuración de la aplicación:

  • YAML
    # app/config/config.yml
    framework:
        # ...
        templating: { engines: ['twig'] }
    
  • XML
    <!-- app/config/config.xml -->
    <framework:templating>
        <framework:engine id="twig" />
    </framework:templating>
    
  • PHP
    // app/config/config.php
    $container->loadFromExtension('framework', array(
        // ...
    
        'templating' => array(
            'engines' => array('twig'),
        ),
    ));
    

Disponemos de muchas opciones de configuración y están cubiertas en el Apéndice Configurando.

Nota

En el motor de twig es obligatorio el uso del webprofiler (así como muchos otros paquetes de terceros).

Sustituyendo plantillas del paquete

La comunidad de Symfony2 se enorgullece de crear y mantener paquetes de alta calidad (consulta KnpBundles.com) para ver la gran cantidad de diferentes características. Una vez que utilizas un paquete de terceros, probablemente necesites redefinir y personalizar una o más de sus plantillas.

Supongamos que hemos incluido el paquete imaginario AcmeBlogBundle de código abierto en el proyecto (por ejemplo, en el directorio src/Acme/BlogBundle). Y si bien estás muy contento con todo, deseas sustituir la página «lista» del blog para personalizar el marcado específicamente para tu aplicación. Al excavar en el controlador del Blog de AcmeBlogBundle, encuentras lo siguiente:

public function indexAction()
{
    // alguna lógica para recuperar artículos
    $blogs = ...;

    $this->render(
        'AcmeBlogBundle:Blog:index.html.twig',
        array('blogs' => $blogs)
    );
}

Al reproducir AcmeBlogBundle:Blog:index.html.twig, en realidad Symfony2 busca la plantilla en dos diferentes lugares:

  1. app/Resources/AcmeBlogBundle/views/Blog/index.html.twig
  2. src/Acme/BlogBundle/Resources/views/Blog/index.html.twig

Para sustituir la plantilla del paquete, sólo tienes que copiar la plantilla index.html.twig del paquete a app/Resources/AcmeBlogBundle/views/Blog/index.html.twig (el directorio app/Resources/AcmeBlogBundle no existe, por lo tanto tendrás que crearlo). Ahora eres libre de personalizar la plantilla para tu aplicación.

Prudencia

Si agregas una plantilla en una nueva ubicación, posiblemente tengas que vaciar la caché (con php app/consola cache:clear), incluso si estás en modo de depuración.

Esta lógica también aplica a las plantillas base del paquete. Supongamos también que cada plantilla en AcmeBlogBundle hereda de una plantilla base llamada AcmeBlogBundle::layout.html.twig. Al igual que antes, Symfony2 buscará la plantilla en los dos siguientes lugares:

  1. app/Resources/AcmeBlogBundle/views/layout.html.twig
  2. src/Acme/BlogBundle/Resources/views/layout.html.twig

Una vez más, para sustituir la plantilla, sólo tienes que copiarla desde el paquete a app/Resources/AcmeBlogBundle/views/layout.html.twig. Ahora estás en libertad de personalizar esta copia como mejor te parezca.

Si retrocedes un paso, verás que Symfony2 siempre empieza a buscar una plantilla en el directorio app/Resources/{NOMBRE_PAQUETE}/views/. Si la plantilla no existe allí, continúa buscando dentro del directorio Resources/views del propio paquete. Esto significa que todas las plantillas del paquete se pueden sustituir colocándolas en el subdirectorio app/Resources correcto.

Nota

También puedes reemplazar las plantillas de un paquete usando la herencia de paquetes. Para más información, consulta Cómo utilizar la herencia de paquetes para redefinir partes de un paquete.

Sustituyendo plantillas del núcleo

Puesto que la plataforma Symfony2 en sí misma sólo es un paquete, las plantillas del núcleo se pueden sustituir de la misma manera. Por ejemplo, el núcleo de TwigBundle contiene una serie de diferentes plantillas para «excepción» y «error» que puedes sustituir copiando cada una del directorio Resources/views/Exception del TwigBundle a... ¡adivinaste! el directorio app/Resources/TwigBundle/views/Exception.

Herencia de tres niveles

Una manera común de usar la herencia es utilizar un enfoque de tres niveles. Este método funciona a la perfección con los tres diferentes tipos de plantilla que acabamos de cubrir:

  • Crea un archivo app/Resources/views/base.html.twig que contenga el diseño principal para tu aplicación (como en el ejemplo anterior). Internamente, esta plantilla se llama ::base.html.twig;

  • Crea una plantilla para cada «sección» de tu sitio. Por ejemplo, AcmeBlogBundle, tendría una plantilla llamada AcmeBlogBundle::layout.html.twig que sólo contiene los elementos específicos de la sección blog;

    {# src/Acme/BlogBundle/Resources/views/layout.html.twig #}
    {% extends '::base.html.twig' %}
    
    {% block body %}
        <h1>Blog Application</h1>
    
        {% block content %}{% endblock %}
    {% endblock %}
    
  • Crea plantillas individuales para cada página y haz que cada una extienda la plantilla de la sección adecuada. Por ejemplo, la página «index» se llama algo parecido a AcmeBlogBundle:Blog:index.html.twig y enumera las entradas del blog real.

    {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #}
    {% extends 'AcmeBlogBundle::layout.html.twig' %}
    
    {% block content %}
        {% for entry in blog_entries %}
            <h2>{{ entry.title }}</h2>
            <p>{{ entry.body }}</p>
        {% endfor %}
    {% endblock %}
    

Ten en cuenta que esta plantilla extiende la plantilla de la sección — (AcmeBlogBundle::layout.html.twig), que a su vez, extiende el diseño base de la aplicación (::base.html.twig). Este es el modelo común de la herencia de tres niveles.

Cuando construyas tu aplicación, podrás optar por este método o, simplemente, hacer que cada plantilla de página extienda directamente la plantilla base de tu aplicación (por ejemplo, {% extends '::base.html.twig' %}). El modelo de plantillas de tres niveles es un método de las buenas prácticas utilizadas por los paquetes de proveedores a fin de que la plantilla base de un paquete se pueda sustituir fácilmente para extender correctamente el diseño base de tu aplicación.

Mecanismo de escape

Cuando generas HTML a partir de una plantilla, siempre existe el riesgo de que una variable de plantilla pueda producir HTML involuntario o código peligroso de lado del cliente. El resultado es que el contenido dinámico puede romper el código HTML de la página resultante o permitir a un usuario malicioso realizar un ataque de Explotación de vulnerabilidades del sistema (Cross Site Scripting XSS). Considera este ejemplo clásico:

  • Twig
    Hello {{ name }}
    
  • PHP
    Hello <?php echo $name ?>
    

Imagina que el usuario introduce el siguiente código como su nombre:

<script>alert('hello!')</script>

Sin ningún tipo de mecanismo de escape, la plantilla resultante provocaría que aparezca un cuadro de alerta JavaScript:

Hello <script>alert('hello!')</script>

Y aunque esto parece inofensivo, si un usuario puede llegar hasta aquí, ese mismo usuario también será capaz de escribir código JavaScript malicioso que subrepticiamente realice acciones dentro de la zona segura de un usuario legítimo.

La respuesta al problema es el mecanismo de escape. Con el mecanismo de escape, reproduces la misma plantilla sin causar daño alguno, y literalmente, imprimes en pantalla la etiqueta script:

Hello &lt;script&gt;alert(&#39;helloe&#39;)&lt;/script&gt;

Twig y los sistemas de plantillas PHP abordan el problema de diferentes maneras. Si estás utilizando Twig, el mecanismo de escape por omisión está activado y tu aplicación está protegida. En PHP, el mecanismo de escape no es automático, lo cual significa que, de ser necesario, necesitas escapar todo manualmente.

Mecanismo de escape en Twig

Si estás utilizando las plantillas de Twig, entonces el mecanismo de escape está activado por omisión. Esto significa que estás protegido fuera de la caja de las consecuencias no intencionales del código presentado por los usuarios. De forma predeterminada, el mecanismo de escape asume que el contenido se escapó para salida HTML.

En algunos casos, tendrás que desactivar el mecanismo de escape cuando estás reproduciendo una variable de confianza y marcado que no se debe escapar. Supongamos que los usuarios administrativos están autorizados para escribir artículos que contengan código HTML. De forma predeterminada, Twig debe escapar el cuerpo del artículo.

Para reproducirlo normalmente, agrega el filtro raw:

{{ article.body|raw }}

También puedes desactivar el mecanismo de escape dentro de una área {% block %} o para una plantilla completa. Para más información, consulta la documentación de Twig sobre el Mecanismo de escape.

Mecanismo de escape en PHP

El mecanismo de escape no es automático cuando utilizas plantillas PHP. Esto significa que a menos que escapes una variable expresamente, no estás protegido. Para utilizar el mecanismo de escape, usa el método especial de la vista escape():

Hello <?php echo $view->escape($name) ?>

De forma predeterminada, el método escape() asume que la variable se está reproduciendo en un contexto HTML (y por tanto la variable se escapa para que sea HTML seguro). El segundo argumento te permite cambiar el contexto. Por ejemplo, para mostrar algo en una cadena JavaScript, utiliza el contexto js:

var myMsg = 'Hello <?php echo $view->escape($name, 'js') ?>';

Depurando

Nuevo en la versión 2.0.9: Esta característica está disponible desde Twig 1.5.x, que se adoptó por primera vez en Symfony 2.0.9.

Cuando utilizas PHP, puedes usar var_dump() si necesitas encontrar rápidamente el valor de una variable proporcionada. Esto es útil, por ejemplo, dentro de tu controlador. Lo mismo puedes lograr cuando utilizas Twig usando la extensión de depuración (debug). Necesitas activarla en la configuración:

  • YAML
    # app/config/config.yml
    services:
        acme_hello.twig.extension.debug:
            class:        Twig_Extension_Debug
            tags:
                     - { name: 'twig.extension' }
    
  • XML
    <!-- app/config/config.xml -->
        <services>
        <service id="acme_hello.twig.extension.debug" class="Twig_Extension_Debug">
            <tag name="twig.extension" />
        </service>
        </services>
    
  • PHP
    // app/config/config.php
    use Symfony\Component\DependencyInjection\Definition;
    
    $definition = new Definition('Twig_Extension_Debug');
    $definition->addTag('twig.extension');
    $container->setDefinition('acme_hello.twig.extension.debug', $definition);
    

Puedes descargar los parámetros de plantilla utilizando la función dump:

{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}
{{ dump(articles) }}

{% for article in articles %}
    <a href="/article/{{ article.slug }}">
        {{ article.title }}
        </a>
{% endfor %}

Las variables serán descargadas si configuras a Twig (en config.yml) con debug a true. De manera predeterminada, esto significa que las variables serán descargadas en el entorno dev, pero no el entorno prod.

Comprobando la sintaxis

Nuevo en la versión 2.1: La orden twig:lint fue añadida en Symfony 2.1

Puedes localizar errores de sintaxis en plantillas Twig utilizando la orden de consola twig:lint:

# Puedes buscar por nombre de archivo:
$ php app/console twig:lint src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig

# o por directorio:
$ php app/console twig:lint src/Acme/ArticleBundle/Resources/views

# o usando el nombre del paquete:
$ php app/console twig:lint @AcmeArticleBundle

Formato de plantillas

Las plantillas son una manera genérica para reproducir contenido en cualquier formato. Y, aunque en la mayoría de los casos debes utilizar plantillas para reproducir contenido HTML, una plantilla fácilmente puede generar JavaScript, CSS, XML o cualquier otro formato que puedas soñar.

Por ejemplo, el mismo «recurso» a menudo se reproduce en varios formatos diferentes. Para reproducir una página índice de artículos en formato XML, basta con incluir el formato en el nombre de la plantilla:

  • nombre de plantilla XML: AcmeArticleBundle:Article:index.xml.twig
  • nombre del archivo de plantilla XML: index.xml.twig

Ciertamente, esto no es más que una convención de nomenclatura y la plantilla realmente no se reproduce de manera diferente en función de ese formato.

En muchos casos, posiblemente quieras permitir que un solo controlador reproduzca múltiples formatos basándose en el «formato de la petición». Por esa razón, un patrón común es hacer lo siguiente:

public function indexAction()
{
    $format = $this->getRequest()->getRequestFormat();

    return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig');
}

El getRequestFormat en el objeto Petición por omisión es HTML, pero lo puedes devolver en cualquier otro formato basándote en el formato solicitado por el usuario. El formato de la petición muy frecuentemente es gestionado por el enrutador, donde puedes configurar una ruta para que /contact establezca el formato html de la petición, mientras que /contact.xml establezca al formato xml. Para más información, consulta el ejemplo avanzado en el capítulo de Enrutado.

Para crear enlaces que incluyan el parámetro de formato, agrega una clave _format en el parámetro hash:

  • Twig
    <a href="{{ path('article_show', {'id': 123, '_format': 'pdf'}) }}">
        PDF Version
    </a>
    
  • PHP
    <a href="<?php echo $view['router']->generate('article_show', array('id' => 123, '_format' => 'pdf')) ?>">
        PDF Version
    </a>
    

Consideraciones finales

El motor de plantillas de Symfony es una poderosa herramienta que puedes utilizar cada vez que necesites generar contenido de presentación en HTML, XML o cualquier otro formato. Y aunque las plantillas son una manera común de generar contenido en un controlador, su uso no es obligatorio. El objeto Respuesta devuelto por un controlador se puede crear usando o sin usar una plantilla:

// crea un objeto Respuesta donde el contenido reproduce la plantilla
$response = $this->render('AcmeArticleBundle:Article:index.html.twig');

// crea un objeto Respuesta cuyo contenido es texto simple
$response = new Response('response content');

El motor de plantillas de Symfony es muy flexible y de manera predeterminada disponemos de dos diferentes reproductores de plantilla: las tradicionales plantillas PHP y las elegantes y potentes plantillas Twig. Ambas apoyan una jerarquía de plantillas y vienen empacadas con un rico conjunto de funciones auxiliares capaces de realizar las tareas más comunes.

En general, el tema de las plantillas se debe pensar como una poderosa herramienta que está a tu disposición. En algunos casos, posiblemente no necesites reproducir una plantilla, y en Symfony2, eso está absolutamente bien.

Bifúrcame en GitHub