EC-CUBE4　管理画面のCRUD　脱初心者三歩目　其ノ三　FormType編

#はじめに
前回の[Repository編](https://qiita.com/dev_belltzel/items/b766b7000fdfd936e669)に続き、FormType編をやっていきます。

FormTypeはその名の通り、フォームを作成する元になるファイルです。
登録・編集フォームや検索フォームなど様々なFormTypeが作成されています。

FormTypeをマスターしようとすると、膨大な記事になりますし、僕もすべて使いこなせてるかと聞かれれば、「No！！」と答えます。

この記事では、基本的な実装方法と、拡張方法を説明します。

#FormTypeの作成

参考にする本体ソース：[Eccube/Form/Type/Front/EntryType.php](https://github.com/EC-CUBE/ec-cube/blob/4.0/src/Eccube/Form/Type/Front/EntryType.php)

参考ソースを元に必要最低限な部分を取り出してみます。

```php
namespace Eccube\Form\Type\Front;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class EntryType extends AbstractType
{

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

    }

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

基本的には、この形に肉付けをしていけば良いかなと思います。
`getBlockPrefix`でリターンされてる文字列はname属性になり、`name="entry[***]"`という感じになります。

##フィールド設定

さて、肉付けですが、`buildForm`内に`add`で追加していきます。

基本的なパーツとしては`company_name`の設定が分かり易いかと。

```php
$builder
    ->add('company_name', TextType::class, [
        'required' => false,
        'constraints' => [
            new Assert\Length([
                'max' => $this->eccubeConfig['eccube_stext_len'],
            ]),
        ],
    ]);
```

`$builder`に`add`メソッドで、第1引数に名前、第2引数にフィールドタイプ、第3引数にoptionを設定できます。

第1引数の名前は好きにつけて良いのですが、後ほど説明するEntityと紐づけする設定を行うと、Entityに自動で値をセットしてくれるようになります。
そのため、紐づけたEntityのプロパティ名と同じ名前を付けないとエラーになってしまいます。

オプションで`mapped`を調べてみると幸せになる可能性もあります。

フィールドタイプやオプションは公式リファレンスをお読みください。
[https://symfony.com/doc/3.4/reference/forms/types.html](https://symfony.com/doc/3.4/reference/forms/types.html)
symfony3.4のページですが、必要に応じてバージョン選択してください。

##フィールドのグループ化
では、他に`add`されたフィールドを見てみましょう。
その中に`NameType`や`KanaType` `AddressType`といったリファレンスにないフィールドが設定されています。

これは何か？

それは、useされている物を見れば分かりますが、`Eccube\Form\Type\`に作成されているFormTypeです。
例）[Eccube/Form/Type/NameType](https://github.com/EC-CUBE/ec-cube/blob/4.0/src/Eccube/Form/Type/NameType.php)

パッと見た感じ理解しがたいですが、`name01`と`name02`が設定されています。
出来上がったテキストフィールド名は`name="entry[name][name01]"`と`name="entry[name][name02]"`になっています。

オプションの設定をすれば、ある程度柔軟な使い回しが出来るように作られています。

頭がパンクしそうになるかもですが、本体ソースのFormTypeを追ってみてください。
結構利用できそうなものが作られていますよ。

##Entityに紐づけ

`Eccube/Form/Type/Front/EntryType`は新規会員登録とマイページの登録情報編集で使われているFormTypeです。
ここで入力された情報は会員情報なので、`Eccube/Entity/Customer`にセットしなければなりません。

ですので、下記のメソッドでEntityクラスを紐づけます。

```php
/**
 * {@inheritdoc}
 */
public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'data_class' => 'Eccube\Entity\Customer',
    ]);
}
```

少しだけControllerを見てみましょう。
[Eccube/Controller/EntryController](https://github.com/EC-CUBE/ec-cube/blob/4.0/src/Eccube/Controller/EntryController.php)

```php
/** @var $Customer \Eccube\Entity\Customer */
$Customer = $this->customerRepository->newCustomer();

/* @var $builder \Symfony\Component\Form\FormBuilderInterface */
$builder = $this->formFactory->createBuilder(EntryType::class, $Customer);
```
`createBuilder`の時に`EntryType::class`と`Customer`のEntityオブジェクトを設定していますね。

EntityのないContactだと
[Eccube/Controller/ContactController](https://github.com/EC-CUBE/ec-cube/blob/4.0/src/Eccube/Controller/ContactController.php)

```php
$builder = $this->formFactory->createBuilder(ContactType::class);
```
`createBuilder`の時に`ContactType::class`だけ設定されています。
ContactTypeにもEntityを紐づけるような記述はないです。
[Eccube/Form/Type/Front/ContactType](https://github.com/EC-CUBE/ec-cube/blob/4.0/src/Eccube/Form/Type/Front/ContactType.php)

##Form Events
`Eccube/Form/Type/Front/EntryType`では、`$builder->addEventListener`が二つ設定されています。
[symfony公式ドキュメント（Form Events）](https://symfony.com/doc/current/form/events.html)

```php
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
    $Customer = $event->getData();
    if ($Customer instanceof Customer && !$Customer->getId()) {
        $form = $event->getForm();

        $form->add('user_policy_check', CheckboxType::class, [
                'required' => true,
                'label' => null,
                'mapped' => false,
                'constraints' => [
                     new Assert\NotBlank(),
                ],
            ]);
    }
}
);

$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
    $form = $event->getForm();
    /** @var Customer $Customer */
    $Customer = $event->getData();
    if ($Customer->getPassword() != '' && $Customer->getPassword() == $Customer->getEmail()) {
        $form['password']['first']->addError(new FormError(trans('common.password_eq_email')));
    }
});
```
この部分ですね。
`FormEvents`の種類は公式ドキュメントを見て頂きたいのですが、ここでは`PRE_SET_DATA`と`POST_SUBMIT`が設定されています。

###FormEvents::PRE_SET_DATA
ドキュメントの翻訳ですが、

- 事前入力中に指定されたデータを変更します。
- 事前入力されたデータに応じてフォームを変更します（フィールドを動的に追加または削除します）。

上記目的で使用されます。
`EntryType`は会員新規登録とマイページの情報編集で利用されています。
そして`PRE_SET_DATA`の処理は、`Entity/Customer`のインスタンスであり、IDが否定された場合（nullだったら）`user_policy_check`を追加するようになっています。

新規登録フォームの時は「利用規約に同意してお進みください」のチェックボックスを追加して、マイページの情報編集の時は追加しない処理になっています。

###FormEvents::POST_SUBMIT
タイミング的にはControllerの`$form->handleRequest($request);`の時に呼び出されます。
処理の内容を見てみると、入力されたパスワードが空でなくて、パスワードとメールアドレスが一緒の時、`パスワードはメールアドレスと同じ値を設定できません。`をいうエラーを設定しています。

複雑なバリデーションを追加したい時に使う印象があります。

##FormType作成の説明は以上
FormType作成の説明は以上なのですが、伝えたい事が沢山ありすぎて、この説明でいいのか不安ではあります。
分からなければ、symfony公式を見る、ググル、勉強会へ参加してみる、質問してみるなどしてみましょう。

#FormType拡張

EC-CUBEのドキュメントに拡張方法が記されています。
[FormTypeのカスタマイズ](https://doc4.ec-cube.net/customize_formtype)

こちらを読むだけで大丈夫かなと思いますので、ここでの説明は省きます。

#最後に
FromTypeを作成している時に、あーしたいこーしたいで躓くことが多々あります。
そうなった場合は、自分の検索力がものをいうと思いますので、頑張ってください。。。

必要な時、必要な事を検索して身に付けるだけで、気が付けばデキル人になっているものです。

Agentに対してのFormTypeですが、後々すべてを公開しますので、しばらくおまちください。

色々書いてみましたが、FromTypeについての説明はまだまだ足りません。
この記事で逆に迷わすような事になるかもと思いましたが、それでも少しでも参考になればと思います。