SymfonyForm에서 우편 번호 및 전화 번호 분할 필드

이런 흔한 것 같은 입력 양식


가정하는 엔티티는 다음과 같습니다.
    /**
     * @ORM\Column(type="string", length=8, nullable=true)
     */
    private $postal;

즉 Entity에서는 $postal 의 캐릭터 라인만으로, 입력 폼은 분할하고 싶다.
의외로 찾아도 정보가 없었기 때문에 할 수 있었던 방법을 메모한다

우편 번호에 대한 FormType 만들기


<?php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;

class PostalType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('postal1', TextType::class, [
                "label" => false,
                "mapped" => false,
                "attr" => [
                    "class" => "p-postal-code",
                    "maxlength" => 3,
                ],
                "constraints" => [
                    new NotBlank([
                        "message" => "郵便番号が未入力です"
                    ]),
                    new Length([
                        "max" => 3,
                        "maxMessage" => "郵便番号が文字数オーバーです"
                    ])
                ]
            ])
            ->add('postal2', TextType::class, [
                "label" => false,
                "mapped" => false,
                "attr" => [
                    "class" => "p-postal-code",
                    "maxlength" => 4,
                ],
                "constraints" => [
                    new NotBlank([
                        "message" => "郵便番号が未入力です"
                    ]),
                    new Length([
                        "max" => 4,
                        "maxMessage" => "郵便番号が文字数オーバーです"
                    ])
                ]
            ])
        ;
        $builder
            ->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) use ($builder) {
                if(!is_array($event->getData())) return null;
                $parent = $event->getForm()->getParent();
                $parent->get($builder->getName())->setData(
                    implode("-", $event->getData())
                );
            })
            ->addModelTransformer(new CallbackTransformer(
                function ($var) {
                    if(null === $var) return [];
                    $exploded = explode("-", $var);
                    if(!isset($exploded[1])) {
                        return [];
                    }
                    return [
                        "postal1" => $exploded[0],
                        "postal2" => $exploded[1]
                    ];
                },
                function ($var) {
                    return implode("-", $var);
                }
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
        ]);
    }
}

이용측 FormType


<?php

namespace App\Form;

use App\Entity\Inquiry;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class InquiryType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('postal', PostalType::class, [
                "parent_name" => "postal",
                "label" => "郵便番号",
            ])
        ;
    }
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Inquiry::class,
        ]);
    }
}

컨트롤러와 트위그



Controller는 보통
public function form() {
    return $this->render('inquiry/form.html.twig, [
        "form" => $this->createForm(InquiryType::class, new Inquiry())->createView()
    ]);
}

Twig에서는 이런 느낌
<div>
  {{ form_label(form.postal) }}
  {{ form_widget(form.postal.postal1) }}-{{ form_widget(form.postal.postal2) }}
</div>

PostalType 이벤트 및 DataTransformer



이것들을 사용하여 어떻게 했습니까?
            ->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) use ($builder) {
                if(!is_array($event->getData())) return null; // 最初のアクセス時など初期データが無い場合の対応
                $parent = $event->getForm()->getParent(); // 利用側にデータを渡す
                $parent->get($builder->getName())->setData(
                    implode("-", $event->getData())
                );
            })

이벤트 리스너에서 PRE_SUBMIT의 이벤트에서 PostalType
            ->addModelTransformer(new CallbackTransformer(
                // Entityのデータが文字列で渡される
                function ($var) {
                    if(null === $var) return []; // データない時とかは空配列を返しておく
                    $exploded = explode("-", $var); // ハイフンで分割
                    if(!isset($exploded[1])) {
                        return []; // データがおかしい時とかは空配列を返しておく
                    }
                                         // PostalTypeのフィールド名に差し替えて返す
                    return [
                        "postal1" => $exploded[0],
                        "postal2" => $exploded[1]
                    ];
                },
                // フォームデータが配列で渡されるので結合して返す
                function ($var) {
                    return implode("-", $var);
                }
            ))
        ;

이 데이터 트랜스포머로 Entity의 단일 캐릭터 라인과 폼으로 사용하는 데이터 배열을 변환한다.

요약



이런 귀찮게 하지 않으면 어떨까. 더 스마트한 방법이 있을 것 같은 생각이 들지만 가르쳐 위대한 사람.

좋은 웹페이지 즐겨찾기