Как создать расширение типа формы

Расширения типов формы невероятно мощные: они повзоляют вам изменять любые существующие типы полей формы по всей системе.

Они имеют 2 основных применения:

  1. Вы хотите добавить особенную функцию к единственному типу формы (как добавление функции "скачать" к типу поля FileType);
  2. Вы хотите добавить общую функцию к нескольким типам (как добавление текста "помощь" к каждому типу вроде "ввода текста").

Представьте, что у вас есть сущность Media, и что каждое медиа ассоциируется с файлом. Ваша форма Media использует тип файла, но при редактировании сущности, вы хотите, чтобы её изображение автоматически отображалось рядом с вводом файла.

Определение расширения типа формы

Для начала, создайте класс расширения типа формы:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// src/AppBundle/Form/Extension/ImageTypeExtension.php
namespace AppBundle\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FileType;

class ImageTypeExtension extends AbstractTypeExtension
{
    /**
     * Возвращается имя расширяемого типа
     *
     * @return строку имени расширяемого типа
     */
    public function getExtendedType()
    {
        // use FormType::class to modify (nearly) every field in the system
        return FileType::class;
    }
}

Единственный метод, который вы должны реализовать - это функция getExtendedType(). Она используется для конфигурации того, какое поля или типы полей вы хотите изменить.

В дополнение к функции getExtendedType(), вы скорее всего захотите переопределить один из следующих методов:

  • buildForm()
  • buildView()
  • configureOptions()
  • finishView()

Чтобы узнать больше о том, что делают эти методы, смотрите статью пользовательский тип поля формы.

Регистрация вашего расширения типа формы в качестве сервиса

Следующий шаг - предупредить Symfony о вашем расширении. Сделайте это путём регистрации вашего класса в качестве сервиса и используя тег form.type_extension:

  • YAML
    1
    2
    3
    4
    5
    6
    services:
        # ...
    
        AppBundle\Form\Extension\ImageTypeExtension:
            tags:
                - { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FileType }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <service id="AppBundle\Form\Extension\ImageTypeExtension">
                <tag name="form.type_extension" extended-type="Symfony\Component\Form\Extension\Core\Type\FileType" />
            </service>
        </services>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    use AppBundle\Form\Extension\ImageTypeExtension;
    use Symfony\Component\Form\Extension\Core\Type\FileType;
    
    $container->autowire(ImageTypeExtension::class)
        ->addTag('form.type_extension', array(
            'extended_type' => FileType::class
        ))
    ;
    

Ключ extended_type тега должен совпадать с классом, который вы возвращаете из метода getExtendedType(). Как только вы это сделаете, любой метод, который вы переопределили (например, buildForm()), будет вызван каждый раз, когда любое поле данного типа (FileType) будет построено. Давайте рассмотрим пример.

New in version 3.3: До Symfony 3.3, вам нужно было определять сервисы расширения типа, как public. Начиная с Symfony 3.3, вы можете также определять их, как private.

Tip

Существует необязательный атрибут тега под названием priority, который по умолчанию имеет значение 0, и контролирует порядок, в котором загружаются расширения типов формы (чем выше приоритет - тем раньше загружается расширение). Это полезно, когда вам нужно гарантировать, что одно расширение будет загружено перед другим.

New in version 3.2: Атрибут priority был представлен в Symfony 3.2.

Добавление расширений логики предметной области

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/AppBundle/Entity/Media.php
namespace AppBundle\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Media
{
    // ...

    /**
     * @var string The path - typically stored in the database
     */
    private $path;

    // ...

    public function getWebPath()
    {
        // ... $webPath как полный URL изображения, будет использован в шаблоне

        return $webPath;
    }
}

Ваш класс расширения типа формы должен будет сделать две вещи, чтобы расширить тип формы FileType::class:

  1. Переопределить метод configureOptions(), чтобы любое поле FileType могло иметь опцию image_property;
  2. Переопределить методы buildView(), чтобы передать URL изображения к просмотру.

Например:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// src/AppBundle/Form/Extension/ImageTypeExtension.php
namespace AppBundle\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;

class ImageTypeExtension extends AbstractTypeExtension
{
    public function getExtendedType()
    {
        return FileType::class;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        // позволяет полям FileType иметь опцию image_property
        $resolver->setDefined(array('image_property'));
    }

    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        if (isset($options['image_property'])) {
            // это будет тем классом или сущностью, которая привязана к вашей форме (например, Медиа)
            $parentData = $form->getParent()->getData();

            $imageUrl = null;
            if (null !== $parentData) {
                $accessor = PropertyAccess::createPropertyAccessor();
                $imageUrl = $accessor->getValue($parentData, $options['image_property']);
            }

            // установите переменную "image_url", которая будет доступна при отображении этого поля
            $view->vars['image_url'] = $imageUrl;
        }
    }

}

Переопределите фрагмент шаблона виджета файла

Каждый тип поля отображается фрагментом шаблона. Эти фрагменты шаблонов могут быть пеоепределены, чтобы настроить отображение формы. Чтобы узнать больше, вы можете обратиться к статье Что такое темы форм?.

В вашем классе расширения, вы добавили новую переменную (image_url), но вам всё ещё надо воспользоваться преимуществом этой новой переменной в ваших шаблонах. Особенно вам надо переопределить блок file_widget:

  • Twig
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    {# app/Resources/fields.html.twig #}
    {% extends 'form_div_layout.html.twig' %}
    
    {% block file_widget %}
        {% spaceless %}
    
        {{ block('form_widget') }}
        {% if image_url is not null %}
            <img src="{{ asset(image_url) }}"/>
        {% endif %}
    
        {% endspaceless %}
    {% endblock %}
    
  • PHP
    1
    2
    3
    4
    5
    <!-- app/Resources/file_widget.html.php -->
    <?php echo $view['form']->widget($form) ?>
    <?php if (null !== $image_url): ?>
        <img src="<?php echo $view['assets']->getUrl($image_url) ?>"/>
    <?php endif ?>
    

Обязательно сконфигурируйте шаблон темы этой формы так, чтобы система форм видела его.

Использование расширения типа формы

Теперь, при добавлении типа поля FileType::class к вашей форме, вы можете указать опцию image_property, которая будет использована для отображения изображения рядом с полем файла. Например:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// src/AppBundle/Form/Type/MediaType.php
namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\FileType;

class MediaType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class)
            ->add('file', FileType::class, array('image_property' => 'webPath'));
    }
}

При отображении формы, если основная модель уже была ассоциирована с изображением, вы увидите его, отображённым рядом с вводом файла.

Общие расширения типа форм

Вы можете изменять несколько типов форм одновременно, указав их общего родителя (Form Types Reference). Например, несколько типов форм наследуют из типа формы TextType (такие как``EmailType``, SearchType, UrlType, и т.д.). Расширение типа формы, применённое к TextType (т.е. метод getExtendedType() возвращает TextType::class) будет применяться ко всем типам этой формы.

Таким же образом, так как большинство типов форм доступных в Symfony наследуют из типа формы FormType, расширение типа формы, применённое к FormType, будет применено ко всем (заметным исключением являются типы формы ButtonType). Также имейте в виду, что если вы создали (или используете) пользовательский тип формы, то может быть так, что он не расширяет FormType, и тогда ваше расширение типа формы нельзя будет применить к нему.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.