フォームイベントの実行タイミング

EC-CUBE3.0ではフォームの入出力やバリデーションにはFormTypeで実装されています。
FormTypeの使い方は[Symfonyのドキュメント](http://docs.symfony.gr.jp/symfony2/reference/forms/types.html)にもありますが、その中でもちょっと理解するのが難しい、フォームイベントについてまとめてみます。

# フォームイベントって？
フォームにかぎらずなのですが、symfonyではイベントという概念で、処理の途中で別の処理を差し込める仕組みが有ります。

FormTypeで用意されているイベントは、以下のとおりです。

- PRE_SET_DATA
- POST_SET_DATA
- PRE_SUBMIT
- SUBMIT
- POST_SUBMIT

また、フォームイベントのフローも公式ドキュメントで提供されています。
http://symfony.com/doc/current/components/form/form_events.html

# 実際に動かしてみる

前述のSymfonyの公式ドキュメントを参照するのが一番手っ取り早いのですが、実際に動かしてみるのが一番理解しやすいかと思います。

EC-CUBE3.0のソースコードにフォームイベントを追加して、動かしてみましょう。

管理画面の特定商取引法の設定画面のソースコードを少しいじってみます。

src/Eccube/Controller/Admin/Shop/TradelawController.php

```php
    public function index(Application $app)
    {
        $Help = $app['eccube.repository.help']->get();

        // ★ フォーム生成前
        echo "CONTROLLER: CREATE FORM BEFORE".PHP_EOL;

        $form = $app['form.factory']
            ->createBuilder('tradelaw', $Help)
            ->getForm();
        // ★ フォーム生成後
        echo "CREATE FORM AFTER".PHP_EOL;

        if ('POST' === $app['request']->getMethod()) {
            // ★ handleRequest実行前
            echo "CONTROLLER: HANDLE REQUEST BEFORE".PHP_EOL;

            $form->handleRequest($app['request']);

            // ★ handleRequest実行後
            echo "CONTROLLER: HANDLE REQUEST AFTER".PHP_EOL;

            if ($form->isValid()) {
                $Help = $form->getData();
                $app['orm.em']->persist($Help);
                $app['orm.em']->flush();
                $app->addSuccess('admin.register.complete', 'admin');
                return $app->redirect($app->url('admin_setting_shop_tradelaw'));
            } else {
                $app->addError('admin.register.failed', 'admin');
            }
        }

        return $app->render('Setting/Shop/tradelaw.twig', array(
                        'form' => $form->createView(),
        ));
    }
```

src/Eccube/Form/Type/Admin/TradelawType.php

```php
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('law_company', 'text', array(
                'label' => '販売業者',
                'required' => true,
                'constraints' => array(
                    new Assert\NotBlank(),
                ),
            ))

  ...

            ->addEventSubscriber(new \Eccube\Event\FormEventSubscriber());

        // ★イベントを設定
        $builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
            echo "FORM: PRE_SET_DATA".PHP_EOL;
        });

        $builder->addEventListener(FormEvents::POST_SET_DATA, function(FormEvent $event) {
            echo "FORM: POST_SET_DATA".PHP_EOL;
        });

        $builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
            echo "FORM: PRE_SUBMIT".PHP_EOL;
        });

        $builder->addEventListener(FormEvents::SUBMIT, function(FormEvent $event) {
            echo "FORM: SUBMIT".PHP_EOL;
        });

        $builder->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {
            echo "FORM: POST_SUBMIT".PHP_EOL;
        });
    }
```

ソースコードを書き換えた状態で、特定商取引法の登録を実行すると、以下が出力されます。

```
CONTROLLER: CREATE FORM BEFORE
FORM      : PRE_SET_DATA
FORM      : POST_SET_DATA
CONTROLLER: CREATE FORM AFTER
CONTROLLER: HANDLE REQUEST BEFORE
FORM      : PRE_SUBMIT
FORM      : SUBMIT
FORM      : POST_SUBMIT
CONTROLLER: HANDLE REQUEST AFTER
```


SET_DATA系のイベントはフォーム生成時、SUBMIT系のイベントは、handleRequestが実行されたタイミングで実行されます。

PREとかPOSTとかはどう違うの？という点では、FormTypeのコードをみてみるのがよいかもしれません。

PRE_SET_DATA
https://github.com/symfony/form/blob/2.7/Form.php#L342

POST_SET_DATA
https://github.com/symfony/form/blob/2.7/Form.php#L391

PRE_SUBMIT
https://github.com/symfony/form/blob/2.7/Form.php#L546

SUBMIT
https://github.com/symfony/form/blob/2.7/Form.php#L633

POST_SUBMIT
https://github.com/symfony/form/blob/2.7/Form.php#L660

# なにができるの？

主に動的にフォームを生成する必要がある場合によく使います。
EC-CUBE3.0では、[このへん](https://github.com/EC-CUBE/ec-cube/search?utf8=%E2%9C%93&q=%24builder-%3EaddEventListener%28FormEvents%3A%3A)で利用されています。

ぜひ参考にしてみてください。

12/3は、 @shinichi-takahashi さんです！