[Symfony] input type="month"で年月を入力できるFormTypeの作り方
はじめに
SymfonyのFormTypeで、HTML5の <input type="month">
をレンダリングしてくれるようなフォームフィールドを作る方法を説明します。
背景をちょっとだけ詳しく
今どきのブラウザは日付系のフォームをいい感じにカレンダーから入力できるようにレンダリングしてくれるので、Symfonyで DateType
を使って日付系のフィールドを扱うときも、あえて 年
月
日
をそれぞれセレクトさせるようなUIにするより、'widget' => 'single_text'
を指定して <input type="date">
とかをレンダリングしてしまったほうが使いやすいことが多いですよね。
年月日の場合は 'widget' => 'single_text'
を指定するだけで <input type="date">
になってくれるので特に何も考えなくていいのですが、 年月 を扱いたい場合にSymfonyのデフォルトの機能だけでは実現できないので、ちょっと工夫が必要になります。
Symfonyのコードを読んでみる
そもそも、 DateType
が <input type="date">
をレンダリングしてくれるカラクリはどうなっているのでしょうか?
ソースを見てみる と、 DateType
クラスの finishView()
メソッドの中で $view->vars['type'] = 'date';
という処理によって input type="date"
が指定されているようです。
ということは、 DateType
を継承した MonthType
とかを作って、 finishView()
メソッドで $view->vars['type'] = 'month';
としてあげればイケそうな気がしますね。
finishView()
メソッドをオーバーライドする
MonthTypeを作って やってみましょう。
class MonthType extends DateType
{
public function finishView(FormView $view, FormInterface $form, array $options)
{
parent::finishView($view, $form, $options);
$view->vars['type'] = 'month';
}
}
class FooType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('yearMonth', MonthType::class, [
'label' => '年月',
'widget' => 'single_text',
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Foo::class,
]);
}
}
class Foo
{
/**
* @ORM\Column(type="date")
*/
private $yearMonth;
// 略
}
実行してみると、以下のように <input type="month">
がレンダリングされました。よさそうですね。
しかし、送信してみると以下のようにバリデーションでエラーになってしまいました🙄
年月の値をDateTimeオブジェクトと変換できるようにする
<input type="month">
の値は yyyy-MM
形式の文字列になるので、そのままだとPHP側で DateTime
オブジェクトとマッピングできず、エラーになります。
format オプションを明示してあげることでこの問題は解決できます。
class FooType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('yearMonth', MonthType::class, [
'label' => '年月',
'widget' => 'single_text',
+ 'format' => 'yyyy-MM',
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Foo::class,
]);
}
}
これで、例えばPHP側が 2020/07/01 00:00:00
の DateTime
だった場合にはフォームの値は 2020-07
になり、フォームで 2020-07
をセットして送信した場合はPHP側では 2020/07/01 00:00:00
の DateTime
として扱われるようになります👍
最終的なMonthTypeのコード
今回作った MonthType
は 'widget' => 'single_text'
'format' => 'yyyy-MM'
とセットで使うことを想定しているので、これらのオプションの指定は MonthType
自身に持たせてしまったほうが使い回すときに楽そうですね。
というわけで MonthType
のコードは以下のようにしておくとよいかなと思います。
class MonthType extends DateType
{
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'widget' => 'single_text',
'format' => 'yyyy-MM',
]);
}
public function finishView(FormView $view, FormInterface $form, array $options)
{
parent::finishView($view, $form, $options);
$view->vars['type'] = 'month';
}
}
これで、使う側で widget
format
をいちいち指定しなくてよくなります。
->add('yearMonth', MonthType::class, [
'label' => '年月',
- 'widget' => 'single_text',
- 'format' => 'yyyy-MM',
])
Discussion