Symfony 2.0, el libro oficial

12.9. Temas para formularios

Symfony te permite personalizar cómo se muestra cada parte de los formulario. Puedes modificar la estructura de cada fila del formulario, la forma en la que se muestran los mensajes de error e incluso cómo debería renderizarse cada tipo de campo. No hay nada que no puedas modificar y puedes aplicar los cambios formulario por formulario, sin afectar a los demás formularios de la aplicación.

Symfony utiliza plantillas para renderizar todas y cada una de las partes de un formulario, como las etiquetas <label>, las etiquetas <input>, los mensajes de error y todo lo demás.

En las plantillas Twig, cada fragmento del formulario está representado por un bloque de Twig. Para personalizar alguna parte del formulario, sólo hay que reemplazar el bloque adecuado.

En las plantillas PHP, cada fragmento del formulario se renderiza mediante un archivo de plantilla. Para personalizar cualquier parte del formulario, sólo hay que reemplazar la plantilla existente creando una nueva.

Para entender mejor cómo funciona esto, vamos a personalizar el fragmento form_row añadiendo un atributo class al elemento div que agrupa cada fila. Para ello, crea un nuevo archivo de plantilla con el siguiente contenido:

{# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #}
{% block field_row %}
{% spaceless %}
    <div class="form_row">
        {{ form_label(form) }}
        {{ form_errors(form) }}
        {{ form_widget(form) }}
    </div>
{% endspaceless %}
{% endblock field_row %}
<!-- src/Acme/TaskBundle/Resources/views/Form/field_row.html.php -->
<div class="form_row">
    <?php echo $view['form']->label($form, $label) ?>
    <?php echo $view['form']->errors($form) ?>
    <?php echo $view['form']->widget($form, $parameters) ?>
</div>

El fragmento field_row se usa cuando muestras los campos del formulario a través de la función form_row. Para indicar al componente Form que utilice este nuevo fragmento field_row, añade lo siguiente en la parte superior de la plantilla que muestra el formulario:

{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
{% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' %}

{% form_theme form 'AcmeTaskBundle:Form:fields.html.twig'
                   'AcmeTaskBundle:Form:fields2.html.twig' %}

<form ...>
<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->
<?php $view['form']->setTheme($form, array('AcmeTaskBundle:Form')) ?>

<?php $view['form']->setTheme($form, array('AcmeTaskBundle:Form', 'AcmeTaskBundle:Form')) ?>

<form ...>

La etiqueta form_theme (en Twig) importa los fragmentos definidos en la plantilla indicada y los utiliza al mostrar el formulario. En otras palabras, cuando más adelante en esta plantilla se utilice la función form_row, se empleará el bloque field_row de tu tema en lugar del bloque field_row predefinido de Symfony.

Los temas personalizados no están obligados a redefinir todos los bloques. Cuando la plantilla requiera un bloque que no se define en tu tema, se utiliza el bloque por defecto global de Symfony.

Si hay varios temas personalizados siempre se busca por orden en cada uno de ellos y si no existe en ninguno, se utiliza de nuevo el bloque definido globalmente.

Para personalizar cualquier parte de un formulario, sólo tienes que reemplazar el fragmento apropiado. La siguiente sección explica cómo saber exactamente qué bloque sustituir.

12.9.1. Sintaxis de los fragmentos de formulario

En Symfony, cada parte de un formulario (elementos HTML, errores, títulos de campo, etc.) se definen en un tema base, que es una colección de bloques en Twig y una colección de plantillas en PHP.

En Twig, todos los bloques se definen en un solo archivo de plantilla llamado form_div_layout.html.twig que se encuentra dentro del bridge de Twig. Si accedes a este archivo, verás todos los bloques que se utilizan para mostrar un formulario y cada uno de los tipos de campo.

En PHP, cada fragmento es una plantilla diferente. Todas las plantillas por defecto se encuentran en el directorio Resources/views/Form del bundle FrameworkBundle.

El nombre de cada fragmento sigue el mismo patrón básico y se divide en dos partes, separadas por un solo carácter de guión bajo (_). Estos son algunos ejemplos:

  • field_row, usado por form_row para mostrar la mayoría de los campos.
  • textarea_widget, usado por form_widget para mostrar un campo de tipo textarea.
  • field_errors, usado por form_errors para mostrar los errores de un campo.

Cada fragmento sigue el mismo patrón básico: type_part. La porción type corresponde al tipo del campo que se está mostrando (por ejemplo, textarea, checkbox, date, etc.), mientras que la porción part corresponde a lo que se está mostrando (por ejemplo, label, widget, errors, etc.). Por defecto, hay cuatro posibles valores para el trozo part:

Nombre Ejemplo Descripción
label field_label Muestra el título del campo
widget field_widget Muestra las etiquetas HTML del campo
errors field_errors Muestra los errores del campo
row field_row Muestra el título, errores y etiquetas HTML del campo

Nota En realidad, existen otras tres partes definidas (rows, rest y enctype) pero es casi imposible que tengas que redefinir su contenido.

Si conoces el tipo de campo (por ejemplo, textarea) y la parte que deseas personalizar (por ejemplo, widget), puedes construir fácilmente el nombre del fragmento que debes crear (por ejemplo, textarea_widget).

12.9.2. Herencia de fragmentos de plantilla

En algunos casos, parece que falta el fragmento que deseas personalizar. Por ejemplo, no existe un fragmento llamado textarea_errors en el tema definido por Symfony. Entonces, ¿cómo se muestran los errores de un campo textarea?

La respuesta es: a través del fragmento field_errors. Cuando Symfony renderiza los errores del tipo textarea, primero busca un fragmento llamado textarea_errors y si no existe, utiliza el fragmento field_errors. Cada tipo de campo tiene un "tipo padre" (el tipo primario del textarea es field, y su padre es el form), y Symfony utiliza el fragmento del padre si no existe el fragmento que busca.

Por lo tanto, para sustituir sólo los errores de los campos textarea, copia el fragmento field_errors, renómbralo como textarea_errors y personalízalo. Para modificar cómo se muestran todos los errores, copia y personaliza el fragmento field_errors directamente.

Truco El tipo padre de cada campo está documentado en la referencia para cada tipo de campo.

12.9.3. Temas globales de formulario

En el ejemplo anterior, se utiliza el helper form_theme (en Twig) para importar fragmentos de formulario personalizados sólo para ese formulario. También puedes decirle a Symfony que aplique el mismo tema personalizado a todos los formularios de la aplicación.

12.9.3.1. Twig

Para incluir automáticamente en todas las plantillas los bloques personalizados del archivo fields.html.twig definido anteriormente, modifica el archivo de configuración global de tu aplicación:

# app/config/config.yml
twig:
    form:
        resources:
            - 'AcmeTaskBundle:Form:fields.html.twig'
    # ...
<!-- app/config/config.xml -->
<twig:config ...>
        <twig:form>
            <resource>AcmeTaskBundle:Form:fields.html.twig</resource>
        </twig:form>
        <!-- ... -->
</twig:config>
// app/config/config.php
$container->loadFromExtension('twig', array(
    'form' => array('resources' => array(
        'AcmeTaskBundle:Form:fields.html.twig',
     ))
    // ...
));

Con esta configuración, cualquier bloque que definas en el archivo fields.html.twig se utilizará en todos los formularios de la aplicación.

12.9.3.2. PHP

Para incluir automáticamente todas las plantillas personalizadas del directorio Acme/TaskBundle/Resources/views/Form creado anteriormente, modifica el archivo de configuración global de tu aplicación:

# app/config/config.yml
framework:
    templating:
        form:
            resources:
                - 'AcmeTaskBundle:Form'
# ...
<!-- app/config/config.xml -->
<framework:config ...>
    <framework:templating>
        <framework:form>
            <resource>AcmeTaskBundle:Form</resource>
        </framework:form>
    </framework:templating>
    <!-- ... -->
</framework:config>
// app/config/config.php
$container->loadFromExtension('framework', array(
    'templating' => array('form' =>
        array('resources' => array(
            'AcmeTaskBundle:Form',
     )))
    // ...
));

Con esta configuración, cualquier fragmento dentro del directorio Acme/TaskBundle/Resources/views/Form se aplicará automáticamente a todos los formularios de la aplicación.