SymfonyのFormで年月をプルダウンで選択するフィールドの作り方

:point_down: のような年月のみを選択する場合向け。 `DateTime` オブジェクトとマッピングする前提。

<img width="143" alt="スクリーンショット 2017-02-22 20.01.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/766/58f04bd3-4ace-b160-5968-ec8df2bf2553.png">

どうやるのが正解かはわからないけど、 `DateType` をそのまま使うと苦しいのでとりあえず雑に作って動いた拡張 FormType を載せておきます。

```php
<?php
namespace Fivestar\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\ReversedTransformer;
use Symfony\Component\OptionsResolver\OptionsResolver;

class YearMonthType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function getParent()
    {
        return DateType::class;
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if ($options['widget'] === 'choice') {
            $builder
                ->resetViewTransformers()
                ->addViewTransformer(new DateTimeToArrayTransformer(
                    $options['model_timezone'], $options['view_timezone'], array('year', 'month')
                ))
            ;
        }

        if ($options['input'] === 'array') {
            $builder
                ->resetModelTransformers()
                ->addModelTransformer(new ReversedTransformer(
                    new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], array('year', 'month'))
                ))
            ;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'widget' => 'choice',
        ]);
    }

    /**
     * {@inheritdoc}
     */
     public function finishView(FormView $view, FormInterface $form, array $options)
     {
         if ($options['widget'] === 'choice') {
             $view['day']->vars['attr']['style'] = 'display:none;';
         }
     }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'year_month';
    }
}

```

`DateType` をベースにプルダウン形式を使う前提として、 `day` を `style="display:none;"` で消すという雑な対応で。フィールドの存在ごと消したいけどおそらく無理っぽい。

ポイントは Transformer 系を一度リセットして再設定してるところ。`DateTime` オブジェクトで年月のみを扱う場合、内部的には日や時分秒も補完され、1度オブジェクトを経由すると `day` は `null` ではなく `1` 扱いになるため、 Transformer の第3引数にて `year` と `month` のみを参照するようにしておくと平和です。

```console
$ php -r "var_dump((new DateTime('2015-12'))->format('Y-m-d H:i:s'));"
string(19) "2015-12-01 00:00:00"
```

入力必須要素なら `finishView` あたりで適当な `value` 値を `day` にぶっこんどきゃいいんだけど、入力が任意の場合は空のままサブミットすると　`(null)-(null)-01` とかなって死ぬし、検索してると入力必須の場合しか考慮してなさそうなコードも結構多かったので、諸々考慮されたやつが標準なりで入ってるといいなと思いました。僕はしません。


## 表示結果

```php
$builder->add('yearMonth', YearMonthType::class);
```

とりあえずそのまま出した場合: 

<img width="143" alt="スクリーンショット 2017-02-22 20.01.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/766/58f04bd3-4ace-b160-5968-ec8df2bf2553.png">


```django
{{ form_row(form.yearMonth) }}
```

クレカの有効年月用:

<img width="146" alt="スクリーンショット 2017-02-22 20.05.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/766/32611beb-4bdb-b3f2-8768-0eed64ed0c02.png">

```django
{{ form_row(form.yearMonth, {
  'date_pattern': '{{ month }}/{{ year }}',
}) }}
```