Symfony viene con un montón de tipos de campos fundamentales para la construcción de formularios. Sin embargo, hay situaciones en las cuales quieres crear un tipo de campo de formulario personalizado para un propósito específico. Esta receta asume que necesitas una definición de campo que contiene el género de una persona, basándote en el campo choice existente. Esta sección explica cómo definir el campo, cómo puedes personalizar su diseño y, por último, cómo lo puedes registrar para usarlo en tu aplicación.
Con el fin de crear el tipo de campo personalizado, primero tienes que crear la clase que representa el campo. En esta situación, la clase contendrá el tipo de campo que se llamará GenderType y el archivo se guardará en la ubicación predeterminada para campos de formulario, la cual es <NombrePaquete>\Form\Type. Asegúrate de que el campo se extiende de Symfony\Component\Form\AbstractType:
// src/Acme/DemoBundle/Form/Type/GenderType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class GenderType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'choices' => array(
'm' => 'Male',
'f' => 'Female',
)
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'gender';
}
}
Truco
La ubicación de este archivo no es importante - el directorio Form\Type sólo es una convención.
En este caso, el valor de retorno de la función getParent indica que estas extendiendo el tipo de campo choice. Esto significa que, por omisión, heredas toda la lógica y represtación de ese tipo de campo. Para ver algo de la lógica, echa un vistazo a la clase ChoiceType. Hay tres métodos que son particularmente importantes:
Truco
Si vas a crear un campo que consta de muchos campos, entonces, asegúrate de establecer tu tipo «padre» como form o algo que extienda a form. Además, si necesitas modificar la «vista» de cualquiera de tus tipos descendientes de tu tipo padre, usa el método finishView().
El método getName() devuelve un identificador que debe ser único en tu aplicación. Este se utiliza en varios lugares, tales como cuando personalizas cómo será pintado tu tipo de formulario.
El objetivo de este campo es extender el tipo choice para habilitar la selección de un género. Esto se consigue fijando las opciones a una lista de posibles géneros.
Cada tipo de campo está representado por un fragmento de la plantilla, el cual se determina en parte por el valor de su método getName(). Para más información, consulta ¿Qué son los temas de formulario?.
En este caso, debido a que el campo padre es choice, no necesitas hacer ningún trabajo debido a que el tipo de campo personalizado automáticamente lo dibuja como el tipo choice. Pero por el bien de este ejemplo, supón que al «expandir» tu campo (es decir, botones de radio o casillas de verificación, en vez de un campo de selección), lo quieres dibujar siempre en un elemento ul. En la plantilla del tema de tu formulario (consulta el enlace de arriba para más detalles), crea un bloque gender_widget para manejar esto:
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
{% block gender_widget %}
{% spaceless %}
{% if expanded %}
<ul {{ block('widget_container_attributes') }}>
{% for child in form %}
<li>
{{ form_widget(child) }}
{{ form_label(child) }}
</li>
{% endfor %}
</ul>
{% else %}
{# simplemente deja que el elemento gráfico 'choice' reproduzca la etiqueta select #}
{{ block('choice_widget') }}
{% endif %}
{% endspaceless %}
{% endblock %}
<!-- src/Acme/DemoBundle/Resources/views/Form/gender_widget.html.twig -->
<?php if ($expanded) : ?>
<ul <?php $view['form']->block($form, 'widget_container_attributes') ?>>
<?php foreach ($form as $child) : ?>
<li>
<?php echo $view['form']->widget($child) ?>
<?php echo $view['form']->label($child) ?>
</li>
<?php endforeach ?>
</ul>
<?php else : ?>
<!-- simplemente deja que el elemento gráfico 'choice' reproduzca la etiqueta select -->
<?php echo $view['form']->renderBlock('choice_widget') ?>
<?php endif ?>
Nota
Asegúrate que utilizas el prefijo correcto para el elemento gráfico. En este ejemplo, el nombre debe set gender_widget, de acuerdo con el valor devuelto por getName. Además, el archivo de configuración principal debe apuntar a la plantilla del formulario personalizado de modo que este se utilice al reproducir todos los formularios.
# app/config/config.yml
twig:
form:
resources:
- 'AcmeDemoBundle:Form:fields.html.twig'
<!-- app/config/config.xml -->
<twig:config>
<twig:form>
<twig:resource>AcmeDemoBundle:Form:fields.html.twig</twig:resource>
</twig:form>
</twig:config>
// app/config/config.php
$container->loadFromExtension('twig', array(
'form' => array(
'resources' => array(
'AcmeDemoBundle:Form:fields.html.twig',
),
),
));
Ahora puedes utilizar el tipo de campo personalizado de inmediato, simplemente creando una nueva instancia del tipo en uno de tus formularios:
// src/Acme/DemoBundle/Form/Type/AuthorType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class AuthorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('gender_code', new GenderType(), array(
'empty_value' => 'Choose a gender',
));
}
}
Pero esto sólo funciona porque el GenderType() es muy sencillo. ¿Qué pasa si los códigos de género se almacena en la configuración o en una base de datos? La siguiente sección se explica cómo resuelven este problema los tipos de campo más complejos.
Hasta ahora, este artículo ha supuesto que tienes un tipo de campo personalizado muy simple. Pero si necesitas acceder a la configuración de una conexión base de datos, o a algún otro servicio, entonces querrás registrar tu tipo personalizado como un servicio. Por ejemplo, supón que estas almacenando los parámetros de género en la configuración:
# app/config/config.yml
parameters:
genders:
m: Male
f: Female
<!-- app/config/config.xml -->
<parameters>
<parameter key="genders" type="collection">
<parameter key="m">Male</parameter>
<parameter key="f">Female</parameter>
</parameter>
</parameters>
// app/config/config.php
$container->setParameter('genders.m', 'Male');
$container->setParameter('genders.f', 'Female');
Para utilizar el parámetro, define tu tipo de campo personalizado como un servicio, inyectando el valor del parámetro genders como el primer argumento de la función __construct que vas a crear:
# src/Acme/DemoBundle/Resources/config/services.yml
services:
acme_demo.form.type.gender:
class: Acme\DemoBundle\Form\Type\GenderType
arguments:
- "%genders%"
tags:
- { name: form.type, alias: gender }
<!-- src/Acme/DemoBundle/Resources/config/services.xml -->
<service id="acme_demo.form.type.gender" class="Acme\DemoBundle\Form\Type\GenderType">
<argument>%genders%</argument>
<tag name="form.type" alias="gender" />
</service>
// src/Acme/DemoBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
$container
->setDefinition('acme_demo.form.type.gender', new Definition(
'Acme\DemoBundle\Form\Type\GenderType',
array('%genders%')
))
->addTag('form.type', array(
'alias' => 'gender',
))
;
Truco
Asegúrate de que estás importando el archivo de servicios. Consulta Importando configuración con imports para más detalles.
Asegúrate de que la etiqueta del atributo alias corresponde con el valor devuelto por el método getName definido anteriormente. Verás la importancia de esto en un momento cuando utilices el tipo de campo personalizado. Pero primero, agrega un método __construct, el cual recibe la configuración del género:
// src/Acme/DemoBundle/Form/Type/GenderType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
// ...
// ...
class GenderType extends AbstractType
{
private $genderChoices;
public function __construct(array $genderChoices)
{
$this->genderChoices = $genderChoices;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'choices' => $this->genderChoices,
));
}
// ...
}
¡Genial! El GenderType ahora es impulsado por los parámetros de configuración y está registrado como un servicio. Adicionalmente, debido a que utilizaste el alias form.type en tu configuración, es mucho más fácil utilizar el campo:
// src/Acme/DemoBundle/Form/Type/AuthorType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
// ...
class AuthorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('gender_code', 'gender', array(
'empty_value' => 'Choose a gender',
));
}
}
Ten en cuenta que en vez de crear una nueva instancia, puedes simplemente referirte a ella por el alias utilizado en tu configuración del servicio, gender. ¡Que te diviertas!