🎻

[Symfony] EasyAdminBundleで普通のテキストプロパティを選択形式にする

2020/04/26に公開

Symfonyで管理画面を実装するなら EasyAdminBundle を使うのが定番です。

今回は、EasyAdminBundleの管理画面で普通のテキストプロパティを選択形式で表示する方法を解説します。

何もしなければどうなるか

例として、以下のようなエンティティをEasyAdminBundleで出力してみます。

class Todo
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $type;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="boolean")
     */
    private $isDone;

    // ... getters and setters
}

こんなエンティティを用意して、 easy_admin.yaml は以下のとおり最小限の設定をします。

easy_admin:
  entities:
    - App\Entity\Todo

これを実際に動かすと、以下のような画面ができます。

テキストプロパティを選択式にする

この Todo エンティティの type プロパティを、以下のような感じで決められた値しか保存できないようにしたいケースを考えます。

/**
 * @ORM\Column(type="string", length=255)
 *
 * @Assert\Choice(choices={"家庭", "趣味", "仕事"})
 */
private $type;

symfony/validator の Choice 制約で、決められた値しか保存できなくしました。

これで、管理画面の type の入力欄に 家庭 趣味 仕事 以外の値を入れてもエラーになるようにはなります。

ただ、できれば入力欄自体をテキストフィールドではなく <select> にしたいと思いますよね。

その場合、 easy_admin.yaml を以下のように設定すれば実現できます👍

easy_admin:
  entities:
    Todo:
      class: App\Entity\Todo
      form:
        fields:
          - { property: type, type: choice, type_options: { choices: { 家庭: 家庭, 趣味: 趣味, 仕事: 仕事 } } }
          - { property: name }
          - { property: isDone }

EasyAdminBundleのドキュメントの以下の箇所を見ると、 form.fields.typeform.fields.type_options でレンダリングに使いたいFormTypeとそこに渡すoptionsを設定できるということなので、これを使ってFormTypeを choice にして、 choices オプションで選択肢を渡したわけです。

type (optional): the Symfony Form type used to render this field. In addition to its fully qualified class name (e.g. Symfony\Component\Form\Extension\Core\Type\EmailType), you can also use the short type name (e.g. email) (the map between names and classes is done internally by the bundle).

type_options (optional), a hash with the options passed to the Symfony Form type used to render the field.
https://symfony.com/doc/2.x/bundles/EasyAdminBundle/book/edit-new-configuration.html#customize-the-form-fields

ちなみに、僕は最初このように type でFormTypeを指定できるという仕様を理解してなくて、 ここで説明されている方法 でテンプレートを上書きして対応しようとして四苦八苦してました😓

選択肢をハードコードしたくない

さて、上記の方法で一応選択式にはできましたが、選択肢がすでに2箇所にハードコードされていて保守性が心配です。

管理画面以外にもアプリ内にはTodoの作成・編集フォームを作ることになるでしょうから、このままだとそこにもまた選択肢のハードコードが発生することが容易に想像されます。

そこで、以下のように選択肢の定義を一箇所に集めることにします。

/**
 * @ORM\Column(type="string", length=255)
 *
 * @Assert\Choice(callback={"App\Entity\Todo", "getValidTypes"})
 */
private $type;

// ...

/**
 * @return string[]
 */
public static function getValidTypes(): array
{
    return [
        '家庭',
        '趣味',
        '仕事',
    ];
}

Choice 制約にコールバックを渡すパターン ですね。

これで、アプリのコード上でTodoのフォームを作るときには、

$choices = array_combine(Todo::getValidTypes(), Todo::getValidTypes());

とかやればよくなりそうです。

では、管理画面はどうすればよいでしょうか?🤔

もちろんyamlからPHPのコードを呼んで実行結果を得るみたいな魔法はないので、 ChoiceType を継承したFormTypeを作ることで対応します。

class TodoTypeChoiceType extends ChoiceType
{
    public function configureOptions(OptionsResolver $resolver)
    {
        parent::configureOptions($resolver);
    
        $resolver->setDefaults([
            'choices' => array_combine(Todo::getValidTypes(), Todo::getValidTypes()),
        ]);
    }
}

このように、 ChoiceType を継承して choices オプションにデフォルト値を設定した TodoTypeChoiceType を作り、 easy_admin.yaml で以下のようにこの TodoTypeChoiceType を使うようにすればOKです。

easy_admin:
  entities:
    Todo:
      class: App\Entity\Todo
      form:
        fields:
          - { property: type, type: App\Form\TodoTypeChoiceType }
          - { property: name }
          - { property: isDone }

なるほど簡単ですね!

しかもこの TodoTypeChoiceType を作っておけば、アプリからTodoのフォームを作るときにも ChoiceType を使う代わりに TodoTypeChoiceType を使えばいちいち選択肢を毎回セットしなくていいのでちょっと楽にもなります。

ちなみにselect2を有効にするには

ちなみにですが、以下のように type_options{ attr: { data-widget: select2 } } を渡してあげると select2 を適用できます。

easy_admin:
  entities:
    Todo:
      class: App\Entity\Todo
      form:
        fields:
          - property: type
            type: App\Form\TodoTypeChoiceType
            type_options:
              attr:
                data-widget: select2
          - { property: name }
          - { property: isDone }

easy_admin.yaml に書くのが面倒なら、FormTypeにデフォルト値として持たせてしまってもいいかもしれません。

class TodoTypeChoiceType extends ChoiceType
{
    public function configureOptions(OptionsResolver $resolver)
    {
        parent::configureOptions($resolver);
    
        $resolver->setDefaults([
            'choices' => array_combine(Todo::getValidTypes(), Todo::getValidTypes()),
            'attr' => [
                'data-widget' => 'select2',
            ],
        ]);
    }
}
easy_admin:
  entities:
    Todo:
      class: App\Entity\Todo
      form:
        fields:
          - { property: type, type: App\Form\TodoTypeChoiceType }
          - { property: name }
          - { property: isDone }

まとめ

  • EasyAdminBundleの form.fields.type はFormTypeを指定する項目
  • form.fields.type_options でオプションを渡せる
  • type: choice, type_options: { { 選択肢1: 選択肢1, 選択肢2: 選択肢2, 選択肢3: 選択肢3 } } のように指定すれば普通のテキストプロパティも <select> でレンダリングできる
  • 選択肢をハードコードしたくない場合は ChoiceType を継承して choices を設定済みにしたFormTypeを自作して、それを使うようにすればいい
GitHubで編集を提案

Discussion