How to Create a Form Type Extension

Form type extensions are incredibly powerful: they allow you to modify any existing form field types across the entire system.

フォーム タイプ拡張機能は非常に強力です。システム全体で既存のフォーム フィールド タイプを変更できます。

They have 2 main use-cases:

主なユースケースは 2 つあります。
  1. You want to add a specific feature to a single form type (such as adding a "download" feature to the FileType field type);
    特定の機能を単一のフォーム タイプに追加したい (「ダウンロード」機能を FileType フィールド タイプに追加するなど)。
  2. You want to add a generic feature to several types (such as adding a "help" text to every "input text"-like type).
    いくつかの型に汎用機能を追加したい (「ヘルプ」テキストをすべての「入力テキスト」のような型に追加するなど)。

Imagine that you have a Media entity, and that each media is associated to a file. Your Media form uses a file type, but when editing the entity, you would like to see its image automatically rendered next to the file input.

Media エンティティがあり、各メディアがファイルに関連付けられているとします。メディア フォームはファイル タイプを使用していますが、エンティティを編集するときに、ファイル入力の横にその画像が自動的にレンダリングされるようにしたいと考えています。

Defining the Form Type Extension

First, create the form type extension class extending from AbstractTypeExtension (you can implement FormTypeExtensionInterface instead if you prefer):

最初に、fromAbstractTypeExtension を拡張するフォーム タイプ拡張クラスを作成します (必要に応じて代わりに FormTypeExtensionInterface を実装できます)。
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
{
    /**
     * Returns an array of extended types.
     */
    public static function getExtendedTypes(): iterable
    {
        // return [FormType::class] to modify (nearly) every field in the system
        return [FileType::class];
    }
}

The only method you must implement is getExtendedTypes(), which is used to configure which field types you want to modify.

実装する必要がある唯一のメソッドは getExtendedTypes() で、変更するフィールド タイプを構成するために使用されます。

Depending on your use case, you may need to override some of the following methods:

ユースケースによっては、次のメソッドのいくつかをオーバーライドする必要がある場合があります。
  • buildForm()
    buildForm()
  • buildView()
    buildView()
  • configureOptions()
    configureOptions()
  • finishView()
    FinishView()

For more information on what those methods do, see the custom form field type article.

これらのメソッドの詳細については、カスタム フォーム フィールド タイプの記事を参照してください。

Registering your Form Type Extension as a Service

Form type extensions must be registered as services and tagged with the form.type_extension tag. If you're using the default services.yaml configuration, this is already done for you, thanks to autoconfiguration.

フォーム タイプの拡張機能は、サービスとして登録し、form.type_extension タグでタグ付けする必要があります。デフォルトの services.yaml 構成を使用している場合、自動構成により、これは既に行われています。

Tip

ヒント

There is an optional tag attribute called priority, which defaults to 0 and controls the order in which the form type extensions are loaded (the higher the priority, the earlier an extension is loaded). This is useful when you need to guarantee that one extension is loaded before or after another extension. Using this attribute requires you to add the service configuration explicitly.

優先度と呼ばれるオプションのタグ属性があり、デフォルトは 0 で、フォーム タイプの拡張機能が読み込まれる順序を制御します (優先度が高いほど、拡張機能が早く読み込まれます)。これは、ある拡張機能が別の拡張機能の前または後にロードされることを保証する必要がある場合に役立ちます。この属性を使用するには、サービス構成を明示的に追加する必要があります。

Once the extension is registered, any method that you've overridden (e.g. buildForm()) will be called whenever any field of the given type (FileType) is built.

拡張機能が登録されると、オーバーライドしたメソッド (buildForm() など) は、指定されたタイプ (FileType) のフィールドが構築されるたびに呼び出されます。

Tip

ヒント

Run the following command to verify that the form type extension was successfully registered in the application:

次のコマンドを実行して、フォーム タイプ拡張がアプリケーションに正常に登録されたことを確認します。
1
$ php bin/console debug:form

Adding the extension Business Logic

The goal of your extension is to display a nice image next to file input (when the underlying model contains images). For that purpose, suppose that you use an approach similar to the one described in How to handle File Uploads with Doctrine: you have a Media model with a path property, corresponding to the image path in the database:

拡張機能の目的は、ファイル入力の横に素敵な画像を表示することです (基になるモデルに画像が含まれている場合)。そのために、How to handle File Uploads with 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 being the full image URL, to be used in templates

        return $webPath;
    }
}

Your form type extension class will need to do two things in order to extend the FileType::class form type:

フォーム タイプ拡張クラスは、FileType::class フォーム タイプを拡張するために 2 つのことを行う必要があります。
  1. Override the configureOptions() method so that any FileType field can have an image_property option;
    どの FileType フィールドにも image_property オプションを指定できるように configureOptions() メソッドをオーバーライドします。
  2. Override the buildView() methods to pass the image URL to the view.
    buildView() メソッドをオーバーライドして、画像 URL をビューに渡します。

For example:

例えば:
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
    {
        // return [FormType::class] to modify (nearly) every field in the system
        return [FileType::class];
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        // makes it legal for FileType fields to have an image_property option
        $resolver->setDefined(['image_property']);
    }

    public function buildView(FormView $view, FormInterface $form, array $options): void
    {
        if (isset($options['image_property'])) {
            // this will be whatever class/entity is bound to your form (e.g. Media)
            $parentData = $form->getParent()->getData();

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

            // sets an "image_url" variable that will be available when rendering this field
            $view->vars['image_url'] = $imageUrl;
        }
    }

}

Override the File Widget Template Fragment

Each field type is rendered by a template fragment. Those template fragments can be overridden in order to customize form rendering. For more information, you can refer to the form fragment naming rules.

各フィールド タイプは、テンプレート フラグメントによってレンダリングされます。これらのテンプレート フラグメントは、フォームのレンダリングをカスタマイズするためにオーバーライドできます。詳細については、フォーム フラグメントの命名規則を参照してください。

In your extension class, you added a new variable (image_url), but you still need to take advantage of this new variable in your templates. Specifically, you need to override the file_widget block:

拡張クラスに新しい変数 (image_url) を追加しましたが、テンプレートでこの新しい変数を利用する必要があります。具体的には、file_widget ブロックをオーバーライドする必要があります。
1
2
3
4
5
6
7
8
9
{# templates/form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}

{% block file_widget %}
    {{ block('form_widget') }}
    {% if image_url is defined and image_url is not null %}
        <img src="{{ asset(image_url) }}"/>
    {% endif %}
{% endblock %}

Be sure to configure this form theme template so that the form system sees it.

フォーム システムが認識できるように、このフォーム テーマ テンプレートを必ず構成してください。

Using the Form Type Extension

From now on, when adding a field of type FileType::class to your form, you can specify an image_property option that will be used to display an image next to the file field. For example:

これからは、タイプ 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']);
    }
}

When displaying the form, if the underlying model has already been associated with an image, you will see it displayed next to the file input.

フォームを表示するときに、基になるモデルが既に画像に関連付けられている場合は、ファイル入力の横に表示されます。

Generic Form Type Extensions

You can modify several form types at once by specifying their common parent (Form Types Reference). For example, several form types inherit from the TextType form type (such as EmailType, SearchType, UrlType, etc.). A form type extension applying to TextType (i.e. whose getExtendedType() method returns TextType::class) would apply to all of these form types.

共通の親を指定することで、複数のフォーム タイプを一度に変更できます (フォーム タイプ リファレンス)。たとえば、いくつかのフォーム タイプは、TextType フォーム タイプ (EmailType、SearchType、UrlType など) から継承します。TextType に適用されるフォーム タイプ拡張 (つまり、getExtendedType() メソッドが TextType::class を返す) は、これらすべてに適用されます。フォームの種類。

In the same way, since most form types natively available in Symfony inherit from the FormType form type, a form type extension applying to FormType would apply to all of these (notable exceptions are the ButtonType form types). Also keep in mind that if you created (or are using) a custom form type, it's possible that it does not extend FormType, and so your form type extension may not be applied to it.

同様に、Symfony でネイティブに利用できるほとんどのフォーム タイプは FormType フォーム タイプから継承するため、FormType に適用されるフォーム タイプ拡張はこれらすべてに適用されます (注目すべき例外は ButtonType フォーム タイプです)。また、カスタム フォーム タイプを作成した (または使用している) 場合、それが FormType を拡張しない可能性があるため、フォーム タイプ拡張がそれに適用されない可能性があることに注意してください。

Another option is to return multiple form types in the getExtendedTypes() method to extend all of them:

もう 1 つのオプションは、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];
    }
}