PHP
Symfony
Symfony4
SymfonyDay 8

SymfonyのForm:自分のやり方(My Practice)

Symfony Advent Calendar 2018の8日目が空いていたので、参加することにしました。SymfonyのFormコンポーネントでの自分の使い方です。

DTOを定義する。

データのエンティティは直接使わないで、DTOクラスを定義して使ってます。まず、ユーザー用DTOクラス、UserDTOと、そこに「埋め込まれた(Embed)」された住所用DTOクラス、AddressDTOを定義。

use Symfony\Component\Validator\Constraints as Assert;

class UserDTO 
{
    /**
     * @var string
     * @Assert\NotBlank()
     */
    public $email;
    /**
     * @var AddressDTO
     * @Assert\Valid()    // ←(1)Validをつけること!
     */
    public $address;
}

class AddressDTO
{
    /**
     * @var string
     * @Assert\NotBlank()
     */
    public $post_code;
    /**
     * @var string
     * @Assert\NotBlank()
     */
    pubic $address;
}

直接エンティティにバリデーションのアノテーションを使うこともできますが、個人的にDTOを別途用意するのが好きです。

DTOなので、プロパティはpublicにしてます。

ちなみにDTOを使う理由ですが…

  • エラーがあった時でも不正な状態のエンティティを作らないで済みます。
  • ValidationはDTOを見ればいいので、すぐわかります。
  • エンティティからセッターを外せます。
  • データのバリデーションは、エンティティ内かDBで行ってます。

バリデーションのConstraints

@Assert\NotBlank()はSymfony/Validationの機能でConstraintsと呼ばれてます。

Assert\Valid()タグ

別フォームを埋め込むときは、埋め込むクラス(ここではUserDTO)にAssert\Valid()アノテーションを書いておくこと。

これがないと、埋め込まれたクラスのバリデーションが効きません

FormTypeを定義する

AbstractTypeを継承して、フォームを定義します。

use Symfony\Component\Form\AbstractType;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class, [
                'label' => '確認先名',
            ])
            ->add('email', MailType::class, [
                'label' => 'メールアドレス',
            ])
            ->add('address', AddressType::class, [
                'error_bubbling' => false,
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => UserDTO::class,
        ));
    }
}

class AddressType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('post_code', TextType::class, [
                'label' => '郵便番号',
            ])
            ->add('address', TextType::class, [
                'label' => '住所',
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => AddressDTO::class,
        ));
    }
}

TextType::classEmailType::classビルドインフィールドタイプと呼んでいて、いろいろなオプションを選べます。

configureOptionsメソッド

configureOptionsで利用するDTOあるいはエンティティを指定します。指定がなくても動いてしまいますが、これがないとエラーがバブルしてしまいます。

また、指定がないと埋め込んだフォームのDTOに対して配列でアクセスしようとするようです。

要は「指定しとけ」っていうことです。

Controller/Twigで利用する

ここまで書いておけば、Controllerで簡単にフォームを利用できます。

class MyController extends Controller
{
    public function form()
    {
        $form = $this->createForm(UserType::class);
        return $this->render('form.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

Twigテンプレート

{{ form_start(form) }}
    {{ form_widget(form) }}
    <input type="submit" class="btn" value="保存">
{{ form_end(form) }}

Bootstrapテーマなどを設定しておけば、上の4行だけで美しいフォームが出ます。

最後に

いつものように書きながら調べて、初めて気が付いたことが多いです。
アウトプット大事。

もっと言えば、ちゃんとマニュアル通りにコーディングすればいいだけ、という話だったりします。ただドキュメント量が多いのと、あちこちに散らかっているので分かりずらいです。