エンティティを作ったら、フォームの作成です。なんだかんだで、ウェブサイトの開発ではフォームの取扱が大きいです。
フレームワークの勉強のため、Symfony3でタスク管理アプリ作ってみたのパート3です。
Symfonyでのフォーム取扱
Symfonyでフォームの取扱はFormsを利用します。
エンティティからセッターを削除したので、エンティティをSymfonyのフォームに使えなくなってしまいました。そこで、フォーム用のDTOクラスを作成しました。
フォームだけのために、エンティティと同じようなクラスをわざわざ作るのは面倒、というか妙な気がします。セッター削除したのは失敗だったかと思ったのですが、おもったより簡単だったのと、フォームだけで利用するアノテーションがあるので、結果的にはエンティティがスッキリするのではと思ってます。
フォーム用DTO
フォームDTOのTaskDTO
クラスです。
<?php
namespace AppBundle\Controller\CrudService;
use AppBundle\Entity\EntityTrait;
use Symfony\Component\Validator\Constraints as Assert;
class TaskDTO
{
use EntityTrait;
/**
* @var string
* @Assert\NotBlank()
*/
public $title;
/**
* @var string
*/
public $details;
/**
* @var \DateTime
* @Assert\Type("\DateTime")
*/
public $doneBy;
}
フォームから登録する値はパブリックのプロパティとして定義しました。フォーム内でしか使わないDTOならこれで十分じゃないか、という話をどこかで読んだので採用しました。今のところ問題なし。
ここでもアノテーションが活躍します。タスクのタイトルは必須ということで@Assert\NotBlank()
アノテーションを使って指定しています。
フォームオブジェクトの作成
Symfonyでのフォームオブジェクトの作成例です。Controller内であれば、$this->createFormBuilder()
でフォーム作成を始められます。また、同じフォームを2度利用するので、別メソッドに切り分けておきます。
private function getCreateForm()
{
$task = new TaskDTO();
return $this->createFormBuilder($task)
->add('title', TextType::class, ['label' => 'Task name', 'required' => true])
->add('doneBy', DateType::class, ['widget' => 'single_text', 'required' => false, 'label' => 'done by'])
->add('details', TextareaType::class, ['required' => false, 'label' => 'details'])
->getForm();
}
ここでのrequired
パラメターですが、あくまでHTMLのJavaScript上でチェックするだけなので注意が必要です。サーバー側でチェックするには(例えば)先のフォーム用DTOでアノテーションを使う必要があります。
フォームの描画
このメソッドで構築した$form
をtwigテンプレートに渡して、フォームを描画します。
return $this->render('task/project/create.html.twig', [
'form' => $this->getCreateForm(),
]);
そしてtwigテンプレート内で、$form
オブジェクトを描画します。
{{ form_start(form) }}
{{ form_errors(form) }}
<div class="form-group">
{{ form_label(form.title) }}
{{ form_widget(form.title, {"attr": "placeholder":"task name"}}) }}
{{ form_errors(form.title) }}
</div>
...
bootstrap3レイアウトを利用
今回はBootstrap3を使っているので、フォームの表現をデフォルトで変更してしまいます。app/config/config.yml
内のtwigセクションで次の設定を加えます。
# Twig Configuration
twig:
form_themes:
- 'bootstrap_3_layout.html.twig'
すると、Bootstrap3用にレンダリングされました。とても便利。
フォームデータの受取
フォームからサブミットされたデータで、タスクを登録します。まずSymfonyが作成するRequestオブジェクトを利用します。コードとしては、こんな感じになりました。
public function insertAction(Request $request): Response
{
$form = $this->getCreateForm()
$form = $form->handleRequest($request);
if (!$form->isValid()) {
return $this->render('task/project/create.html.twig', [
'form' => $this->getCreateForm(),
]);
}
$dto = $form->getData();
$task = new Task($dto->toArray());
$em = $this->getDoctrine()->getManager();
$em->persist($task);
$em->flush();
}
Callbackの導入
ちょっと複雑なバリデーションを行う方法として、Callbackという機能がSymfonyにはあります。例えば、TaskのdoneByに過去の日付けを入れたくない、と言ったケースを考えて見ます。
先のTaskDTOにバリデーション用のメソッドを追加して、@Assert\Callback()
とアノテートするだけ。これでエラーチェックを行うことができます。こんなコードになります。
class TaskDTO
{
/**
* @param \DateTime $now
*/
public function __construct(\DateTime $now = null)
{
$this->now = $now ? clone($now): null;
if ($this->now) {
$this->now->setTime(0,0,0);
}
}
/**
* @param ExecutionContextInterface $context
* @Assert\Callback()
*/
public function validateDoneByHasFutureDate(ExecutionContextInterface $context)
{
if ($this->doneBy && $this->now && $this->doneBy < $this->now) {
$context->buildViolation('please select future date as done-by.')
->atPath('doneBy')
->addViolation();
}
}
}
アノテーションやバリデーションが入り込んでくると考えると、エンティティとフォーム用DTOを分けてよかった気がします。