Symfony Form Events を利用した処理

# はじめに
自分の備忘録をかねて、symfonyの機能(特にform)関連をまとめています。
今回は一番便利だけど、なかなか扱いづらいForm Eventsを利用した処理についてまとめていきたいと思います。

### 今まで書いたsymfonyの記事
- symfony でのform作成の基本
  - http://qiita.com/yutaChaos/items/0ae0d1797db4cb16466c
- symfony validation処理の方法
  - http://qiita.com/yutaChaos/items/95e70ef96608809bf475
- symfony カスタムバリデーションの作り方 
  - http://qiita.com/yutaChaos/items/e7b42589253d3235ae6c
- Symfony 複数項目に依存したvalidationの作成
  - http://qiita.com/yutaChaos/items/641290aa650170290fe5

今回はform eventsについてまとめたいと思います。

## Form Events

symfonyはFormの機能が非常に強力です。
その中でもEvent Dispatcherを利用したForm Eventは実際の実装の上で役立つことが多いと思います。

| Event         | Formの操作 |     Data     | タイミング |
|:------------- |:---------:|:------------:|:--------- |
| PRE_SET_DATA  |      ◯    |     Model    |formを生成した時 |
| POST_SET_DATA |      ◯    |     Model    |formを生成し、setDataした時 |
| PRE_SUBMIT    |      ◯    | Request Data | handleRequest時、request dataがmodelに変換される前|
| SUBMIT        |      ☓    |     Model    | handleRequest時、request dataがmodelに変換された後 |
| POST_SUBMIT   |      ☓    |     Model    | handleRequest時、submitが完了した後のタイミングで実行される |

##　実装例
- ~~かなり~~少し無理やりですが下記のような実装のケースを想定してform eventを利用して実装してみます。
- 本来は正規化を行い子テーブルを持つはずチェックボックスの複数選択肢を、一つのカラムにカンマ区切りで実装する場合

### コード
- Entityは下記のような形、typeカラムにチェックボックスの中にはカンマ区切りで選択された文字列が入る

```php
<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use AppBundle\Validator\Constraint as Custom;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Mapping\ClassMetadata;

/**
 * ToDo.
 *
 * @ORM\Table(name="to_do")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\ToDoRepository")
 */
class ToDo
{

    const TASK_LIST = [
        '1' => 'work',
        '2' => 'hobby',
    ];

    const TYPE_DELIMITER = ',';
    const TYPE_LIST = [
        '1' => 'Hobby',
        '2' => 'Work',
        '3' => 'Study',
    ];

    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     * @ORM\Column(name="task", type="integer", length=1)
     */
    private $task;

    /**
     * @var string
     * @Assert\NotBlank()
     * @Assert\Length(min = 2,max = 255)
     * @ORM\Column(name="memo", type="string", length=255, nullable=true)
     */
    private $memo;

    /**
     * @var string
     * @ORM\Column(name="type", type="string", length=255, nullable=true)
     */
    private $type;

    /**
     * @var string
     * @ORM\Column(name="date", type="datetime",nullable=false)
     */
    private $date;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="r_datetime", type="datetime", nullable=true)
     */
    private $rDatetime;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="u_datetime", type="datetime", nullable=true)
     */
    private $uDatetime;

    /**
     * Get id.
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set task.
     *
     * @param string $task
     *
     * @return ToDo
     */
    public function setTask($task)
    {
        $this->task = $task;

        return $this;
    }

    /**
     * Get task.
     *
     * @return string
     */
    public function getTask()
    {
        return $this->task;
    }

    /**
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * @param string $type
     *
     * @return ToDo
     */
    public function setType($type)
    {
        $this->type = $type;

        return $this;
    }

    /**
     * Set memo.
     *
     * @param string $memo
     *
     * @return ToDo
     */
    public function setMemo($memo)
    {
        $this->memo = $memo;

        return $this;
    }

    /**
     * Get memo.
     *
     * @return date
     */
    public function getMemo()
    {
        return $this->memo;
    }

    /**
     * @return date
     */
    public function getDate()
    {
        return $this->date;

        return $this;
    }

    /**
     * @param string $date
     */
    public function setDate($date)
    {
        $this->date = $date;
    }

    /**
     * Set rDatetime.
     *
     * @param \DateTime $rDatetime
     *
     * @return ToDo
     */
    public function setRDatetime($rDatetime)
    {
        $this->rDatetime = $rDatetime;

        return $this;
    }

    /**
     * Get rDatetime.
     *
     * @return \DateTime
     */
    public function getRDatetime()
    {
        return $this->rDatetime;
    }

    /**
     * Set uDatetime.
     *
     * @param \DateTime $uDatetime
     *
     * @return ToDo
     */
    public function setUDatetime($uDatetime)
    {
        $this->uDatetime = $uDatetime;

        return $this;
    }

    /**
     * Get uDatetime.
     *
     * @return \DateTime
     */
    public function getUDatetime()
    {
        return $this->uDatetime;
    }

    /**
     * @ORM\PrePersist
     */
    public function refreshRDatetime()
    {
        $this->setRDatetime(new \Datetime());
    }

    /**
     * @ORM\PrePersist
     * @ORM\PreUpdate
     */
    public function refreshUDatetime()
    {
        $this->setUDatetime(new \Datetime());
    }

    /**
     * taskの表示値を取得
     */
    public function getTaskName()
    {
        $task = $this->getTask();
        return self::TASK_LIST[$task];
    }

    /**
     * typeの表示値を取得
     */
    public function getTypeNames()
    {
        $types = explode(self::TYPE_DELIMITER, $this->getType());
        $typeNames = [];
        foreach ($types as $type) {
            $typeNames[] = self::TYPE_LIST[$type];
        }

        return implode(self::TYPE_DELIMITER, $typeNames);
    }


}
```

- Formsは下記のような形、typeカラムにチェックボックスの中にはカンマ区切りで選択された文字列が入る
- `type`のフォームの設定は下記のようになっていて、ChoiceType(選択式)かつ、multiple、expandedをtrueに設定することでチェックボックスの表示になる。

```php
                'type',
                InputType\ChoiceType::class,
                [
                    'choices' => array(
                        '1' => 'Hobby',
                        '2' => 'Work',
                        '3' => 'Study',
                    ),
                    'multiple' => true,
                    'expanded' => true,
                ]
```

- `FormEvents`の実際の設定はbuildFormの中でaddEventListenerを行うことで設定する。functionの中でそのeventの中でファンクションを呼び出し、変換した値をentityにセットし直しています。

```php
        $builder
            ->addEventListener(
                FormEvents::PRE_SET_DATA,
                function (FormEvent $event) {
                    $form = $event->getForm();
                    $entity = $event->getData();
                    $convertedType = $this->convertValueToArr($entity->getType());
                    $entity->setType($convertedType);
                }
            )
            ->addEventListener(
                FormEvents::POST_SUBMIT,
                function (FormEvent $event) {
                    $entity = $event->getData();
                    $convertedType = $this->parseArrToStr($entity->getType());
                    $entity->setType($convertedType);
                }
            );
    }


    public function convertValueToArr($value)
    {
        return explode(',', $value);
    }

    public function parseArrToStr($type)
    {
        return implode(',', $type);
    }
```