🎻
[Symfony] エンティティのプロパティが定数をとるときに僕がよくやる実装
僕がよくやる実装を紹介していくシリーズです😇
エンティティのプロパティが定数をとるとき
例えば、 ユーザー
が 部署
という文字列プロパティを持っていて、そこには 営業部
開発部
総務部
という3つの値しか入らないことを想定しているとします。
厳密にやるなら 部署
をエンティティにしたほうがいいのかもしれませんが、
- そう頻繁には変更されなさそう
- アプリ利用者が自分で変更できる必要もない
ようなものなら、定数にしてしまったほうがコードがスッキリします。
僕は試したことがないのですが、myclabs/php-enum のようなEnumっぽい機能を提供してくれるライブラリもあるので、こういうものを活用するのもいいかもしれません。
エンティティの実装
具体的にはこんな感じです。
use Symfony\Component\Validator\Constraints as Assert;
class User
{
const DIVISION_SALES = '営業部';
const DIVISION_DEVELOPMENT = '開発部':
const DIVISION_GENERAL = '総務部';
public static function getValidDivisions(): array
{
return [
self::DIVISION_SALES,
self::DIVISION_DEVELOPMENT,
self::DIVISION_GENERAL,
];
}
/**
* Assert\Choice(callback="getValidDivisions")
*/
private $division;
}
Choice
制約の callback
オプション を使って、適切な部署名しか入力できないようにバリデーションを設定しています。
もっとアグレッシブにやるなら
public function setDivision(string $division): self { if (!in_array($division, self::getValidDivisions()) { throw new \LogicException('Invalid division'); } $this->division = $division; }
のようにsetterで弾くようにしてしまってもいいかもしれません。
FormTypeの実装
部署を入力するためのFormTypeも使い回せるように独立して作っておきます。
// src/Form/User/DivisionChoiceType.php
class DivisionChoiceType extends ChoiceType
{
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'choices' => array_combine(User::getValidDivisions(), User::getValidDivisions()),
'multiple' => false,
]);
}
}
こうしておけば、他のFormTypeから簡単に呼び出せて便利です。
// src/Form/UserType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('divisions', DivisionChoiceType::class, [
'required' => false,
'label' => '部署',
])
}
UserType
からしか使われないようならわざわざ独立したFormTypeにしておく必要はないかもしれませんが、場合によっては他のエンティティが 対象とする部署
のような形で 部署
の情報を持つような仕様も全然あるでしょうし、EasyAdminBundle を使っていて管理画面のフォームもテキストフィールドではなく選択式にしたいという場合にも使えます。
まとめ
こんな感じで実装しておくと、部署のバリエーションや表示順の情報は User
クラスの定数定義と getValidDivisions()
メソッドに閉じ込められるので、とても簡単に変更に対応できます。
class User
{
+ const DIVISION_DEVELOPMENT = '開発部':
const DIVISION_SALES = '営業部';
- const DIVISION_DEVELOPMENT = '開発部':
+ const DIVISION_PLANNING = '企画部';
const DIVISION_GENERAL = '総務部';
public static function getValidDivisions(): array
{
return [
+ self::DIVISION_DEVELOPMENT,
self::DIVISION_SALES,
- self::DIVISION_DEVELOPMENT,
+ self::DIVISION_PLANNING,
self::DIVISION_GENERAL,
];
}
Discussion