[EC-CUBE4(symfony)]createNamedBuilderでinput要素を配列で取得するChoiceTypeのformを作成(初心者向け)

こんにちは！

ようやく新型コロナ収束してきましたね！
新型コロナが騒がれ始めた頃は、まさかここまで猛威を振るうとは思っていませんでした・・
被害にあわれた方や、身内や職場で被害があった方は本当に辛い思いをされたかと思いますが、一刻も早い収束と新型コロナ以前の日常の復活を願っています。

激動の2020年がんばって乗り切っていきましょう！

あと、いわゆる「コロナ差別」や「コロナいじめ」はやめましょう。


（ここから記事）
symfonyて難しいですよね！
情報も英語ばっかだし・・


久しぶりの投稿になります。
kamotetuと申します。


最近めっきりec-cube４を触っているのですが、
タイトル通りcreateNamedBuilderでinput要素を配列で取得するformの作成方法を紹介したいと思います！

(※entityは作成しません。formTypeの作成の紹介になります。あと、ec-cube4の環境で作成するので、プレーンなsymfonyではないのでご了承ください。でも、基本一緒だと思います)

よくわからないと思うので、完成イメージは

![zakoType2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480146/5d2225a0-c580-e32a-ba37-dcf198fa2998.gif)



こんな感じで(cssはec-cube4のデフォルトを使用してますので異様に長いformは無視してください)

欲しいhtmlは

```
<form method="post">
    <label class="" for="zako_question_question1">あなたはザコですか？</label>
        <select id="zako_question_question1" name="zako_question[question1]" class="form-control">
            <option value="">選択してください</option>
            <option value="1">はい</option>
            <option value="2">いいえ</option>
        </select>
    <label class="" for="zako_question_question2">私はザコですか？</label>
        <select id="zako_question_question2" name="zako_question[question2]" class="form-control">
            <option value="">選択してください</option>
            <option value="1">はい</option>
            <option value="2">いいえ</option>
            <option value="3">知らん</option>
        </select>
    <button type="submit" id="submit" name="submit" class="btn-primary btn">送信する</button>
</form>
```

こんな感じで(記事に必要ない要素を省いてます)

欲しいデータの形は

![スクリーンショット 2020-05-23 23.08.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480146/3e437aa8-5cb0-b04d-ca60-9f997da2fa8f.png)

こんな感じ。


要するにselect要素のnameが

```
name="zako_question[question1]"
```

こういうのが欲しいときにやるやり方の紹介になります！


ec-cube4(symfony)触り初めてまもないので、もっと簡単な方法あるよ！という方はご教示願います！

ということで紹介していきますよ！

## コントローラを作成

```ZakoController.php
<?php

namespace Customize\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Response;
use Eccube\Controller\AbstractController;
use Customize\Form\ZakoType;
use Symfony\Component\HttpFoundation\Request;


class ZakoController extends AbstractController
{
    /**
     * @Route("zako/choice", name="zako_choice")
     * @Template("zako/zako_choice.twig")
     */
    public function zako(Request $request)
    {
        $question = 
        [
            'question1' => 
            [
                'title' => 'あなたはザコですか？',
                'choice' => 
                [
                    'はい' => 1,
                    'いいえ' => 2,
                ]
            ],
            'question2' =>
            [
                'title' => '私はザコですか？',
                'choice' => 
                [
                    'はい' => 1,
                    'いいえ' => 2,
                    '知らん' => 3,
                ]
            ],

        ];

        $builder = $this->formFactory->createNamedBuilder(
            '',
            ZakoType::class,
            [
                'option_data' => $question,
            ]
        );

        $form = $builder->getForm();

        $form->handleRequest($request);

        if($form->isSubmitted() && $form->isValid()){
            return [
                'result' => 'succese',
                'form' => $form->createView(),
            ];
        }elseif($form->isSubmitted()) {
            return [
                'result' => 'failure',
                'form' => $form->createView(),
            ];
        }

        return [
            'form' => $form->createView(),
        ];
    }
}
```

```
$builder = $this->formFactory->createNamedBuilder(
            '',
            ZakoType::class,
            [
                'option_data' => $question,
            ]
        );
```
第１引数の'',に名前を入力するとhtmlでは
例)'zakozako',

```
<select id="zakozako_zako_question_question1" name="zakozako[zako_question][question1]" class="form-control">
```

こんな感じで違うnameやidなどを設定できます。
第２引数はformType,第３引数はformTypeに渡すoptionになります。


```
'option_data' => $question,
```
$questionにアンケートのデータを持たせて、ZakoTypeに渡します。

## 親要素のZakoTypeを作成

```ZakoType.php
<?php

namespace Customize\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
use Customize\Form\ZakoChoiceType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;


class ZakoType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add(
            'zako_question',
            ZakoChoiceType::class, 
            [
                'label' => 'アンケート',
                'required' => false,
                'option_data' => $options['data']['option_data'],
            ]
        );

        $builder->add(
            'submit',
            SubmitType::class,
            [
                'label' => '送信する',
            ]
        );
    }
}
```

```
$builder->add(
            'zako_question',
            ZakoChoiceType::class, 
            [
                'label' => 'アンケート',
                'required' => false,
                'option_data' => $options['data']['option_data'],
            ]
        );

```

$optionsの'data'にコントローラで渡したデータが入ってますので、ZakoChoiceTypeに'option_data'としてデータを渡します。

## 子要素のZakoChoiceTypeを作成

```ZakoChoiceType.php
<?php

namespace Customize\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Customize\Form\ZakoChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;


class ZakoChoiceType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        foreach($options['option_data'] as $key => $value){
            $builder->add(
                $key,
                ChoiceType::class,
                [
                    'placeholder' => '選択してください',
                    'expanded' => false,
                    'multiple' => false,
                    'label' => $value['title'],
                    'choices' => $value['choice'],
                    'constraints' => 
                    [
                        new Assert\NotBlank(),
                    ]
                ]
            );
        }
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'option_data' => null,
        ]);
    }
}
```

foreachでアンケートがある分回します。

```
'choices' => $value['choice'],
```

ChoiceTypeのoptionの'choices'に選択肢の配列を渡します。
![スクリーンショット 2020-05-23 23.20.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480146/f0d0d1ec-9e61-ba8b-c020-ccfaed2acf89.png)

```
public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'option_data' => null,
        ]);
    }
```

ここではoptionsへ渡してもいいデータの名前を設定しています。
ここら辺がまだよくわかっていないところなのですが、
親のZakoTypeでは設定していなくても'data'にコントローラで渡したデータが入ってくるのですが、ここでは許可するoptionを設定しないとエラーになります。
(ちなみにnullを設定してますが、正直あってるかわからん。とりあえずいけたのでnullにしてる。)

ちなみに、コントローラでcreateFormでフォームを作成しようとすると、'data'にはデータが入ってきません。これと同じように許可するデータの名前を設定する必要があります。(と、思う。違ったらすいません！)

あと余談ですが、

```
'expanded' => false,
'multiple' => false,
```
これをtrueとfalseを組み合わせるとラジオボタンとかセレクトボックスとかにすることができます


## Viewに表示

```zako_choice.twig
{% extends 'default_frame.twig' %}

{% set body_class = 'zako_page' %}

{% block main %}

    {{form(form)}}

    {% if result is defined %}
        {% if result == 'success' %}
            <img src="{{ asset('assets/img/success.png') }}" style="height: 100px; width: auto;">
        {% else %}
            <img src="{{ asset('assets/img/failure.png') }}" style="height: 100px; width: auto;">
        {% endif %}
    {% endif %}
    
{% endblock %}
```

これで完成！

## 背景

```
{{form(form)}}
```

正直これでフォームを表示させるということはフロント泣かせでしかないので、実際には

```
{{form_label(form.zako)}}
{{form_widget(form.zako)}}
{{form_errors(form.zako)}}
```
こんな感じで書くことが多いと思います。


今回の場合だと

```
{{form_label(form.zako_question)}}
{{form_widget(form.zako_question)}}
{{form_errors(form.zako_question)}}
```
こんな感じで書くと思いますが、
当初ZakoTypeでforeachで回してformを作成しようと思ったのですが、twig上で

```
{%for key, question in question %} //コントローラでquestionのデータを渡すと仮定してます
    {{form_widget(form.key)}} //keyには"question1"などのnameが入っています
{% endfor %}
```

こんな感じで取り出そうとすると、「そんなformありません」的なエラーになってしまいます。
いろいろググったところtwigではforeachでformを表示することはできないみたいです。
（entityでいろいろ設定すればできるみたいですが・・）

なので、

```
{{form_widget(form.zako_question)}}
```

と個別でformを指定したくて今回の記事にしました。

あと、もしこれ冒頭で説明した親、子のinput要素では無かった場合、アンケートの数が変更するたび

```
{{form_label(form.zako_question1)}}
{{form_widget(form.zako_question1)}}
{{form_errors(form.zako_question1)}}

{{form_label(form.zako_question2)}}
{{form_widget(form.zako_question2)}}
{{form_errors(form.zako_question2)}}

{{form_label(form.zako_question3)}}
{{form_widget(form.zako_question3)}}
{{form_errors(form.zako_question3)}}
```
こんな感じでその都度twigのformを記載しなければならなくなります。
面倒ですよね？

## formがオブジェクト

記事の趣旨とは外れますが、

```
{{form(form)}}
```
これだけで冒頭のイメージのフォームが作成されるてすごいですよね・・

formがオブジェクト・・

表示するformをフロントの外で作るて考えが新鮮でした。

でも今考えるとrailsのform_forやform_withとかこんなイメージだったのかな？

まぁ

symfonyてすげー

てことですね！