Posted at
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行だけで美しいフォームが出ます。


最後に

いつものように書きながら調べて、初めて気が付いたことが多いです。

アウトプット大事。

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