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

Дата обновления перевода 2022-01-17

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

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

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

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

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

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

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

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

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

class ImageTypeExtension extends AbstractTypeExtension
{
    /**
     * Возвращает массив расширенных типов.
     */
    public static function getExtendedTypes(): iterable
    {
        // вернуть [FormType::class], чтобы изменить (почти) каждое поле в системе
        return [FileType::class];
    }
}

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

В зависимости от вашего случая применения, вам может понадобиться переопределить некоторые из следующих методов:

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

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

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

Расширения типа формы должны быть зарегистрированы как сервисы и тегированы тегом form.type_extension. Если вы используете конфигурацию services.yaml по умолчанию , это уже сделано за вас, благодаря автоконфигурации .

Tip

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

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

Tip

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

1
$ php bin/console debug:form

Добавление расширений бизнес-логики

Целью вашего расширения является отображение красивого изображения радом с вводом файла (когда базовая модель содержит изображения). Для этой цели, представьте, что вы используете подход, схожий с описанным в Как управлять загрузками файлов с 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/Entity/Media.php
namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Media
{
    // ...

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

    // ...

    public function getWebPath(): string
    {
        // ... $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
42
// src/Form/Extension/ImageTypeExtension.php
namespace App\Form\Extension;

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

class ImageTypeExtension extends AbstractTypeExtension
{
    public static function getExtendedTypes(): iterable
    {
        // вернуть [FormType::class], чтобы изменить (почти) все поля в системе
        return [FileType::class];
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        // легализирует то, что поля FileType меют опцию image_property
        $resolver->setDefined(['image_property']);
    }

    public function buildView(FormView $view, FormInterface $form, array $options): void
    {
        if (isset($options['image_property'])) {
            // это будет тем классом/сущностью, который привязан к вашей форме (например, Media)
            $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
{# templates/form/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 %}

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

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

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

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

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

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

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

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

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

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

Другая опция - вернуть несколько типов форм в методе getExtendedTypes(), чтобы расширить их все:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Form/Extension/DateTimeExtension.php
namespace App\Form\Extension;
// ...
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TimeType;

class DateTimeExtension extends AbstractTypeExtension
{
    // ...

    public static function getExtendedTypes(): iterable
    {
        return [DateTimeType::class, DateType::class, TimeType::class];
    }
}