Herencia de plantillas en Twig

Twig permite heredar unas plantillas de otras de forma que pueden compartir y sobreescribir comportamientos por bloques

Contenido modificable

Si ves errores o quieres modificar/añadir contenidos, puedes crear un pull request. Gracias

De forma muy frecuente las templates de un proyecto comparten elementos comunes, como el header, footer, sidebar, etc. Con Symfony este problema se solventa de forma que una template puede decorarse con otra. Esto funciona de la misma forma que las clases PHP: la herencia de templates permite crear una capa base que contiene todos los elementos comunes definidos en bloques (como si fueran métodos base de clases PHP). Una template hija puede extender la base y sobreescribir cualquiera de sus bloques (como cuando una subclase de PHP sobreescribe métodos de su clase madre).

Ejemplo:

{# app/Resources/views/base.html.twig #}
<!DOCTYPE html>
<html>
    <head>
        <meta 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="content">
            {% block body %}{% endblock %}
        </div>
    </body>
</html>

Esta template define el esqueleto base HTML de una página simple de dos columnas. En este ejemplo se han creado tres áreas {% block %}: title, sidebar y body. Cada bloque puede ser sobreescrito por una template hija o se puede dejar con su implementación por defecto. También puede renderizarse directamente, lo que mostraría los valores por defecto de los tres bloques.

Una template hija puede ser como la siguiente:

{# app/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 %}

La template madre se identifica con sintaxis de string ('base.html.twig'), y hace referencia al directorio app/Resources/views del proyecto.

La key para la herencia de templates es la etiqueta {% extends %}. Esta le dice al motor de templates que primero evalúe la template base, que establece la primera capa y define varios bloques. La template hija entonces se renderiza, y los bloques title y body de la madre se sobreescriben con los de la hija. Dependiendo del valor de blog_entries, el output podría resultar así:

<!DOCTYPE html>
<html>
    <head>
        <meta 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="content">
            <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>

Ya que la template hija no ha definido ningún bloque sidebar, se utiliza el valor de la madre. El contenido de una etiqueta {% block %} en una template madre siempre se utiliza por defecto.

Algunos tips a tener en cuenta en la herencia de templates:

  • Si empleas la etiqueta {% extends %}, ésta ha de ser la primera que se define.
  • Cuantas más etiquetas {% block %} tengas mejor. Las templates hija no tienen que definir todos los bloques de la madre, por lo que puedes crear tantos bloques como quieras en las templates base para poder personalizarlas mejor. Cuantos más bloques más flexible será la template.
  • Si ves que estás duplicando contenido en varios templates, probablemente significa que debes mover ese contenido a un {% block %} en una template madre. En ocasiones puede resultar mejor mover el contenido en una template nueva e incluirlo con include.
  • Si necesitas obtener el contenido de un bloque de una template madre, puedes utilizar la función {{ parent() }}. Esto es útil si quieres añadir los contenidos de un bloque madre en lugar de sobreescribirlo por completo.
{% block sidebar %}
    <h3>Table of Contents</h3>

    {# ... #}

    {{ parent() }}
{% endblock %}

Puedes emplear tantos niveles de herencia como necesites.

Herencia de templates a tres niveles

La herencia de templates a tres niveles es una forma bastante común de organizar las templates de un proyecto.

  1. Se crea una template base app/Resources/views/base.html.twig que contiene el principal layout para la aplicación. Internamente esta template se llama base.html.twig.
  2. Crea una template para cada sección de tu sitio. Por ejemplo la funcionalidad blog tendría una template llamada blog/layout.html.twig que contiene sólo elementos de la sección blog.
    {# app/Resources/views/blog/layout.html.twig #}
    {% extends 'base.html.twig' %}

    {% block body %}
        <h1>Blog Application</h1>

        {% block content %}{% endblock %}
    {% endblock %}
  1. Crea templates individuales para cada página y haz que cada una extienda la sección a la que pertenece. Por ejemplo, la página index podría llamarse blog/index.html.twig y mostraría una lista de los posts del blog.

    {# app/Resources/views/blog/index.html.twig #}
    {% extends 'blog/layout.html.twig' %}

    {% block content %}
        {% for entry in blog_entries %}
            <h2>{{ entry.title }}</h2>
            <p>{{ entry.body }}</p>
        {% endfor %}
    {% endblock %}

De esta forma se crean los tres niveles: la template de entradas extiende a la template base de la sección (blog/layout.html.twig) y ésta extiende a la template base de la aplicación (base.html.twig).

Sobreescribir templates de bundles

Cuando se usa un bundle de terceros es probable que tengas que sobreescribir y customizar alguna de sus plantillas.

Si por ejemplo hemos instalado el bundle AcmeBlogBundle y queremos sobreescribir la página de la lista de blogs para personalizarla, podemos ver en el Blog controller:

public function indexAction()
{
    // Lógica para devolver los blogs
    $blogs = ...;

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

Cuando se renderiza AcmeBlogBundle:Blog:index.html.twig, Symfony busca en dos directorios la template:

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

Para sobreescribir la template del bundle, copia la template index.html.twig del bundle a app/Resources/AcmeBlogBundle/views/Blog/index.html.twig, si el directorio app/Resources/AcmeBlogBundle/ no existe, tendrás que crearlo. Ahora ya puedes customizar la template.

Si añades la template en un nuevo directorio, es probable que tengas que limpiar la cache (php bin/console cache:clear) incluso en debug mode.

Esta lógica también se aplica a las templates base de los bundles. Si cada template de AcmeBlogBundle hereda de una template base llamada AcmeBlogBundle::layout.html.twig, para sobreescribirla habrá que hacer lo mismo que antes, Symfony buscará en:

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

Para sobreescribirla, copiala del bundle a app/Resources/AcmeBlogBundle/views/layout.html.twig y customízala.

Sobreescribir templates Core

Ya que el Symfony Framework en sí mismo es un bundle, las templates core pueden sobreescribirse de la misma forma. Por ejemplo, TwigBundle contiene varios templates "exception" y "error" que pueden sobreescribirse copiando desde cada directorio, Resources/views/Exception del TwigBundle al directorio app/Resources/TwigBundle/views/Exception.