0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SymfonyFormで郵便番号や電話番号の分割フィールド

Posted at

こんなよくありそうな入力フォーム
スクリーンショット 2021-09-23 23.39.03.png

想定するEntityは以下の様

    /**
     * @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とTwig

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のデータを親Typeに結合して渡している

            ->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の単一文字列とフォームで使うデータ配列を変換する。

まとめ

こんな面倒くさいことしないといかんのか。もっとスマートな方法がありそうな気がするけど教えて偉い人。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?