Edited at

Symfonyでタスク管理アプリ作ってみた(フォーム編)

More than 1 year has passed since last update.

エンティティを作ったら、フォームの作成です。なんだかんだで、ウェブサイトの開発ではフォームの取扱が大きいです。


フレームワークの勉強のため、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を分けてよかった気がします。