[Symfony] EasyAdminBundleで普通のテキストプロパティを選択形式にする
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.type
と form.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 [
'家庭',
'趣味',
'仕事',
];
}
これで、アプリのコード上で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を自作して、それを使うようにすればいい
Discussion