Symfony3でフォーム入力値をごにょごにょしたい

# 概要
フォーム入力値をPOSTする前に扱いたいときは
 ```Symfony\Component\Form\CallbackTransformer``` を利用する。
公式ドキュメント：https://symfony.com/doc/current/form/data_transformers.html

# 経緯
EC-CUBE4のプラグイン開発中に、チェックボックスから入力する値を関係テーブルを作らずに1カラムにカンマ区切りで保存したいという場面に出会った。
当初は入力値がPOSTされた後にPOST値からパラメータを取り出してカンマ区切りの文字列に丸めてもう一度セットして保存、のような実装を行っていたがオブジェクトに出入りする値の型が変わりまくるため非常に気持ち悪く可読性に乏しい。
また、DBから取り出した文字列を配列にパースする処理も同一のアクションに含まれていたため、一目で見てわかるような処理でなかった。
そのため、フォームイベントなどを利用した実装はないかと考えて辿り着いた実装方法。

ちなみに当初の実装は↓のような感じ。
対象のパラメータは ```Config``` というエンティティの ```param``` というプロパティの想定で記載する。
また、 ```param``` はフォームではチェックボックス(配列)、DBではカンマ区切りの文字列として扱われるものとする。


```:ConfigController.php

public function index(Request $request) {
    $Config = $this->configReposiory->get();    // ← ここでとれるparamはカンマ区切りの文字列。
    $param = $Config->getParam();
    $param = explode(',', $param);    // ← フォーム初期表示に使うために文字列 → 配列にパースする。
    $Config->setParam($param);
                                                
    $form = $this->createForm(ConfigType::class, $Config);
    $form = handleRequest($request);

    $if ($form->isSubmitted() && $form->isValid()) {
        $Config = $form->getData();    // ← ここでとれるparamはフォーム入力値なので配列
        $param = $Config->getParam();
        $param = implode(',', $param);    // ← DB登録のために配列 → 文字列に丸める。
        $Config->setParam($param);
    }

    ~~ 省略 ~~

    $this->em->persist($Config);
    $this->em->flush($Config);
}
```

上記ソースにコメントで示したようにそれぞれ前処理する必要があり、あまり親切でない。
実際には省略部でもっと値を利用したい場合も多いため、結構面倒。

# 解決策
フォーム入力は文字列、取り出しは配列で行うことができればよいわけだから、
1. ```Symfony\Component\Form\CallbackTransformer``` を利用して、配列 → 文字列 としてPOSTされるようにする。
2. エンティティに ```parseParam()``` というメソッドを定義して、```getParam()``` の代わりに利用する。
   
※ゲッタを単にプロパティを返すだけのメソッドでなく、文字列をパースする機能とするのはアンチパターンと考えたため２のようにしたが、本当は取り出しも意識せずに行えるようにしたい。なんかうまい方法あったら教えてください。

* 入力側

```:Form/Type/ConfigType.php

use Symfony\Component\Form\CallbackTransformer;    // コンポーネント読み込みを忘れずに！

class ConfigType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $transformer = new CallbackTransformer(
            function($string) {
                $decode = explode(',', $string);
            },
            function($array) {
                $encode = implode(',', $array);
            }
        )

        $builder
            ->add('param',ChoiceType::class, [
                'choices' => $choices,
                'multiple' => true,
                'expanded' => true,
            ]);

        $builder
            ->get('param')
            ->addModelTransformer($transformer);
    }
}
```

* 出力側

```:Entity/Config.php

/*
* @var string
*
* @ORM\Column(name="param", type="string")
*/
private $param;

public setParam($param) {
    $this->param = $param;
    return $this;
}

public getParam() {
    return $this->param;
}

// ↓こいつを新しく実装した。
public parseParam() {
    $ret = explode(',', $this->param);
    return $ret
}
```

これによってコントローラは下記のようになった。

```:ConfigController.php

public function index(Request $request) {
    $Config = $this->configReposiory->get();
                                                
    $form = $this->createForm(ConfigType::class, $Config);    // transformerで 文字列 → 配列 としてくれる！！ 
    $form = handleRequest($request);

    $if ($form->isSubmitted() && $form->isValid()) {
        $Config = $form->getData();    // ← 入力時も 配列 → 文字列 としてくれるので心配無用。

        $parse= $Config->parseParam();    // 文字列をパースしながら取得する
        ~~ ↑ ビジネスロジックで扱うときは配列の方が都合がよいケースが多い ~~        

        $this->em->persist($Config);
        $this->em->flush($Config);
    }
}
```

# あとがき
スッキリ！
もっといい実装や指摘あったらお待ちしてます！



# 宣伝
EC-CUBE、Laravelの開発お任せください！！