📜 ⬆️ ⬇️

Extending Symfony 2 Forms

Prelude


Forms is probably one of the most complex components of symfony2. But behind all its complexity lies a strikingly flexible architecture, providing ample room for expansion. We, as developers, can add (and change) form field types (Form Type), use listeners (Event Listeners), data transformers (Data Transformers) and type extensions (Type Extensions). On the latter today and talk.

Theory


Type extensions provide a powerful mechanism for changing the behavior and presentation (FormView) of field types. Within the extension, 4 entry points are provided to implement the necessary logic:
public function buildForm(FormBuilderInterface $builder, array $options) { } public function buildView(FormView $view, FormInterface $form, array $options) { } public function finishView(FormView $view, FormInterface $form, array $options) { } public function setDefaultOptions(OptionsResolverInterface $resolver) { } 

Consider them in more detail:
  1. buildForm - provides access to the FormBuilder object, which allows you to add, change or delete form fields, as well as attach listeners;
  2. buildView - provides access to the FormView and Form objects for modifying the form view. Within this method it is impossible to change the child views;
  3. finishView - similar to buildView , but allows you to change the child views;
  4. setDefaultOptions - provides access to the OptionsResolver object, to expand or change the list of options.

In most cases, we will operate with the setDefaultOptions and buildView methods . One of the most common algorithms for the use of form extensions:
  1. setDefaultOptions - register a new option in OptionsResolver ;
  2. buildView - we implement the logic of processing the value of the option and pass a new parameter to the view;
  3. change the form template to display the value of the parameter.

To control the type of fields for which the extension is applied, the getExtendedType method allows us:
 public function getExtendedType() { //        textarea return 'textarea'; } 

To use an extension in the Symfony 2 Framework, it must be registered as a service in a dependency container (Dependency Injection Container) using a special tag - form.type_extension . For the tag, you must specify the alias parameter with the type of fields to which the extension will be applied:
 services: acme_demo.form.my_extension: class: Acme\DemoBundle\Form\Extension\MyExtension tags: - { name: form.type_extension, alias: textarea } 

Practice


For example, we implement an extension that allows you to group form fields and display them inside the <fieldset> element.
 <?php namespace Acme\DemoBundle\Form\Extension; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\OptionsResolver\Options; class FieldsetExtension extends AbstractTypeExtension { private $rootView; public function getExtendedType() { //        return 'form'; } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( //     . 'group' => null, )); } public function buildView(FormView $view, FormInterface $form, array $options) { $group = $options['group']; if (null === $group) { return; } $root = $this->getRootView($view); $root->vars['groups'][$group][] = $form->getName(); } public function getRootView(FormView $view) { $root = $view->parent; while (null === $root) { $root = $root->parent; } return $root; } } 

Create a form template:
 {# src Acme/DemoBundle/Resources/views/Form/fields.html.twig #} {% extends 'form_div_layout.html.twig' %} {% block form_widget_compound %} <div {{ block('widget_container_attributes') }}> {% if form.parent is empty %} {{ form_errors(form) }} {% endif %} {% if form.vars.groups is defined %} {% for group,items in form.vars.groups %} <fieldset> <legend>{{ group|title|trans({}, translation_domain) }}</legend> {% for item in items %} {{ form_row(form[item]) }} {% endfor %} </fieldset> {% endfor %} {% endif %} {{ form_rest(form) }} </div> {% endblock form_widget_compound %} 

After registering the extension in the dependency container, you can start using it. Create a new form:
 <?php namespace Acme\DemoBundle\Form\Extension; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class PersonType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('name', 'text', array( 'group' => 'fio' )) ->add('surname', 'text', array( 'group' => 'fio' )) ->add('midname', 'text', array( 'group' => 'fio' )) ->add('phone', 'text', array( 'group' => 'contacts' )) ->add('skype', 'text', array( 'group' => 'contacts' )) ->add('email', 'text', array( 'group' => 'contacts' )) ; } } 

And a template for her:
 {# src Acme/DemoBundle/Resources/views/Person/new.html.twig #} {% form_theme form 'AcmeDemoBundle:Form:fields.html.twig' %} <form action="{{ path('person_create') }}" > {{ form_widget(form) }} </form> 

So the extension works, but the implementation is not convenient enough, in my opinion. Let's try to simplify and add some syntactic sugar. To do this, create a wrapper class:
 <?php namespace Acme\DemoBundle\Form; use Symfony\Component\Form\FormBuilder; class FormMapper { /** * Form builder * @var FormBuidler */ private $builder; /** * Active group * @var mixed null|string */ private $group = null; public function __construct(FormBuilder $builder) { $this->builder = $builder; } /** * Add child to builder with group option */ public function add($child, $type = null, array $options = array()) { if (!array_key_exists('group', $options) and null !== $this->group) { $options['group'] = $this->group; } $this->builder->add($child, $type, $options); return $this; } /** * Set active group */ public function with($group) { $this->group = $group; return $this; } } 

Now managing groups is easier:
 public function buildForm(FormBuilderInterface $builder, array $options) { $mapper = new FormMapper($builder); $mapper ->with('fio') ->add('name', 'text') ->add('surname', 'text') ->add('midname', 'text') ->with('contacts') ->add('phone', 'text') ->add('skype', 'text') ->add('email', 'text') ; } 

The final


In preparing the article used materials:
  1. Symfony 2 Form Component Documentation
  2. Symfony 2 Forms Recipes

')

Source: https://habr.com/ru/post/184484/


All Articles