[Перевод] Symfony2: How to Create a Custom Form Field Type

Как создать пользовательский тип поля в форме

Перевод статьи: How to Create a Custom Form Field Type

Symfony2 поставляется с кучей основных типов полей, доступных для построения форм. Однако, бывают ситуации, когда необходимы кастомные типы полей формы с определенным назначением (выбор страны, тип платежной системы, etc..). Предположим, вам необходимо определить поле, которое содержит пол человека, на основе существующего choice поля. В этом разделе вы узнаете, как определить тип поля, как можно кастомизировать его расположение и зарегистрировать его для использования в Вашем приложении.

Определение типов полей

Для того чтобы создать пользовательский тип поля, сначала нужно создать класс, представляющий поле. В нашем случае (создаем поле с выбором пола М/Ж) класс, содержащий тип поля, будет называться GenderType. Сохраним его в каталог определенный по умолчанию для классов, которые описывают типы полей формы %BundleName%/Form/Type/.

FormType классы должны расширять 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';
    }
}

На самом деле расположение классов с типами кастомных полей формы не важно, Form\Type каталог это всего лишь соглашение.

Значение возвращаемое методом getParent() указывает, что вы расширяете choice типа поля. Это означает, что, по умолчанию, наследуются вся его логика и рендеринг базового типа поля (choice). Чтобы увидеть некоторую логику, загляните в класс ChoiceType класс.

Есть три важных метода в классе *FormType:
buildForm() — каждый тип поля имеет метод buildForm , в котором собираются и настраиваются любые поля. Этот же метод используется, для настройки стандартной формы, и он работает так же для кастомных типов полей.
buildView() — этот метод используется для установки любых дополнительных переменных, которые могут понадобится при рендеринге полей формы в шаблоне. Например, в ChoiceType, устанавливается переменная multiple и она же используется в шаблоне, для установки multiple атрибута для полей выбора. См. Создание шаблона для поля для более подробной информации.
setDefaultOptions () — метод определяет варианты выбора для вашего типа формы, которые могут быть использованы в buildForm() и buildView() . Есть много опций, общих для всех полей (см. Тип поля формы), но вы можете создать любые другие, которые необходимы в приложении.

Если вы создаете кастомный тип поля, которое состоит из многих полей, то не забудьте установить родительский (parent) тип формы или что-то, что расширяет форму. Кроме того, если вам нужно изменить отображение для любых дочерних типов, используйте метод finishView().

Метод getName() возвращает идентификатор, который должен быть уникальным в вашем приложении. Он используется в различных местах, например, при настройке рендеринга/отображения вашего типа поля(ей).
Целью нашего поля было расширить тип выбора, для выбор пола. Для этого нужно определить доступные опции выбора в списке возможных полов.

Создание шаблона для поля

Каждый тип поля рендерится/отображается как фрагмент шаблона, который определяется в части значения метода getName(). Для получения дополнительной информации см. Что такое темы формы?
В этом случае, так как родительский тип поле — choice, вам не нужно ничего делать, так как кастомное поле будет автоматически рендерится подобно типу поля choice. Однако для этого примера, давайте предположим, что, когда ваше choise поле «развернуто» (expanded) (как-то переключатели или флажки, вместо select типа), вы захотите, всегда рендерить его в ul элементе. В шаблоне темы формы, создайте gender_widget блок для реализации этого:


{# 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 %}
      {# just let the choice widget render the select tag #}
      {{ block('choice_widget') }}
    {% endif %}
  {% endspaceless %}
{% endblock %}

Убедитесь, что используется правильный префикс виджета. В этом примере имя должно быть gender_widget , в соответствии со значением возвращаемым getName() методом. Кроме того, основной конфигурационный файл должен указывать на некий созданный шаблон формы, который будет использоваться для рендеринга всех форм.

# app/config/config.yml
twig:
  form:
    resources:
      - 'AcmeDemoBundle:Form:fields.html.twig'

Использование типа поля

Теперь вы можете использовать пользовательский тип поля сразу, просто создав новый экземпляр типа в одной из ваших форм:

// 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',
    ));
  }
}

Но это работает только потому, что тип GenderType() очень прост. Что делать, если код или ID опции поля хранится в конфигурации или в базе данных? В следующем разделе объясняется, как более сложные типы полей решают эту проблему.

Создание поля типа как сервиса

До сих пор мы описывали очень простой пользовательский тип поля. Но если внутри типа поля нужен доступ к конфигурации, подключение к базе данных или другой службе, чтобы зарегистрировать свой пользовательский тип в качестве службы. Предположим, что вы храните параметры пола в файле конфигурации:

# app/config/config.yml
  parameters:
    genders:
      m: Male
      f: Female

При использовании параметра, определите кастомный тип поля в качестве сервиса, заинжектив первым параметром в конструкторе ( __construct() ) доступные значения опций выбора пола:

# 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 }

Убедитесь, что файл служб импортируется. См. Импортирование конфигурации с помощью import в деталях.

Проверьте, что алиас атрибута tag соответствует значению, которое возвращает определенный ранее метод getName(). Вы увидите важность этого момента при использовании кастомного типа поля. Но сначала, добавим классу GenderType конструктор, который получает конфигурацию пола:

// 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,
    ));
  }

  // ...
}

Отлично! GenderType теперь получает параметры конфигурации и зарегистрирован в качестве сервиса. Кроме того, поскольку вы использовали алиас form.type в конфигурации, теперь использовать поле гораздо проще:

// 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',
    ));
  }
}

Заметьте, что вместо создания нового экземпляра, вы можете просто ссылаться на его алиас, используемый в конфигурации службы gender.

Развлекайтесь!

<< Как внедрить коллекцию форм

Как создать расширение Form Type (Extension) >>