Symfony Advent Calendar 2018 7日目の記事(遅刻)です。
昨日も私の記事でした
巨大なプロジェクトの構成要素を小さなコンポーネントに分けて開発するようになると、バリデーションをSymfonyプロジェクト外で書くことも増えます。
こういうときSymfonyがコンポーネントごとに分かれていて、独立して使用することもできるのが生きてきます。
昔のsymfony1時代との大きな違いですね!
実際にSymfony Validatorでバリデーションしてみる
下記のような Term
クラスがあるとします。
<?php
namespace Quartetcom\DecBlogDemo\Entity;
class Term
{
/**
* @var \DateTime
*/
private $from;
/**
* @var \DateTime
*/
private $to;
public function __construct(\DateTime $from, \DateTime $to)
{
$this->from = $from;
$this->to = $to;
}
public function validateLessThan90Days()
{
if ($this->to->diff($this->from)->days > 90) {
throw new \LogicException('Term is too long.');
}
}
// ...
}
このTermクラスのバリデーションをSymfonyのValidatorコンポーネントを使って書き換えてみましょう。
まず、composerで symfony/validator
への依存を追加します。
$ composer require symfony/validator
そしてvalidateLessThan90days()
メソッドをSymfonyのValidator用の書き方に変更します。
<?php
//...
+use Symfony\Component\Validator\Constraints as Assert;
+use Symfony\Component\Validator\ExecutionContextInterface;
class Term
{
// ...
+ /**
+ * @Assert\Callback
+ */
+ public function validateLessThan90Days(ExecutionContextInterface $context)
+ {
+ if ($this->to->diff($this->from)->days > 90) {
+ $context->buildViolation('term.too_long')
+ ->atPath('to')
+ ->addViolation()
+ ;
+ }
+ }
- public function validateLessThan90Days()
- {
- if ($to->diff($from)->days > 90) {
- throw new \LogicException('Term is too long.');
- }
- }
// ...
}
テストを書いて、このバリデーションが期待通りに動くかどうか確認してみましょう。
<?php
namespace Quartetcom\DecBlogDemo\Entity;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Validation;
class TermTest extends TestCase
{
public function test_validate_90days()
{
$term = new Term(new \DateTime('2018-11-01'), new \DateTime('2019-01-30'));
$this->assertValidTerm($term);
}
public function test_validate_91days()
{
$term = new Term(new \DateTime('2018-11-01'), new \DateTime('2019-01-31'));
$this->assertInvalidTerm($term);
}
private function assertValidTerm(Term $term)
{
$this->assertEquals(0, $this->getViolationCount($term));
}
private function assertInvalidTerm(Term $term)
{
$this->assertGreaterThan(0, $this->getViolationCount($term));
}
/**
* @param Term $term
* @return int length of errors
*/
private function getViolationCount(Term $term): int
{
$validator = Validation::createValidator();
return count($validator->validate($term));
}
}
バリデーションが行われなかったようです…
おっと、Validator初期化時にアノテーションによるバリデーション設定を有効化する必要があるのを忘れていました。
<?php
namespace Quartetcom\DecBlogDemo;
// ...
class TermTest extends TestCase
{
// ...
private function getViolationCount(Term $term)
{
- $validator = Validation::createValidator();
+ $validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();
return count($validator->validate($term));
}
}
今度こそバリデーションできるはず!
今度は doctrine/annotations
と doctrine/cache
が必要だというエラーが出てしまいました。
メッセージに従い、追加します。
$ composer require doctrine/annotations doctrine/cache
今度こそバリデーションできるはず!
Symfony\Component\Validator\Constraints\Callback
がクラスとして読み込めていないようです
AnnotationRegistryにautoloadできるクラスが存在すればAnnotationとして利用できる設定を追加します。
※ この設定は https://github.com/doctrine/annotations/issues/103 によるとDoctrine v3から不要になるようです。
<?php
namespace Quartetcom\DecBlogDemo;
// ...
+use Doctrine\Common\Annotations\AnnotationRegistry;
class TermTest extends TestCase
{
// ...
private function getViolationCount(Term $term)
{
+ AnnotationRegistry::registerLoader('class_exists');
$validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();
return count($validator->validate($term));
}
}
ついにバリデーションが動きました!
まとめ
私はガッチリ密結合だったsymfony1.0の頃からsymfonyを使っているので、Symfonyのバージョンが1→2→3→4と進むにつれて、疎結合がどんどん加速しているのを感じます。
マイクロサービス志向・コンポーネント志向が進む中、安心して使うことができるSymfony Componentの役割はますます大きくなりそうです。
※ 本記事のサンプルコードを こちらのレポジトリ で公開しています。ご参考にどうぞ