2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Symfony ComponentAdvent Calendar 2022

Day 15

データをチェック、"Validator"

Last updated at Posted at 2022-12-14

Symfony Component Advent Calendar 2022の15日目の記事です。

最初に

SymfonyはPHPのフレームワークのひとつです。しかし、公式サイトの説明文には

Symfony is a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony.
(SymfonyはPHPコンポーネントのセットで、Webアプリケーションフレームワークで、哲学、そしてコミュニティです。それらがハーモニーを奏でながら動作しています。)

と書かれている通り、PHPコンポーネントのセットで、たくさんのコンポーネントを提供しており、それらを組み合わせてひとつのフレームワークとして動作しています。Symfonyのコンポーネントは、Symfony上だけで動作するのではなく、他のPHPフレームワークやアプリケーションでも動作している強力なものが揃っています。

今回はそれらの中から、役立ちそうなもの・お薦めしたいものを紹介していきたいと思います。

※記事内ではautoloadのインポートは省略します。

データをチェック、"Validator"

Validatorは、データに誤りがないか・このまま使って大丈夫か、検証するコンポーネントです。

インストール

composer require symfony/validator

検査項目の設定

ValidatorにはConstraintsという検査項目があらかじめ用意されています。どのプロパティがどのような検証を行う必要があるかアトリビュートオブジェクトで設定していきます。

このValidatorのサンプルコードで、よくEntityに対しての検査の設定を目にするので、誤解されがちですが、Entityだけではなく、他のクラスや変数・配列に対しても設定できます。
実際、自分も最近までEntityだけだと誤解してました。

アトリビュート指定の場合
Item.php

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class Item
{
    #[Assert\NotBlank] // 空ではないかチェック
    #[Assert\Length(max: 100)] // 文字列長をチェック
    private string $name;
    
    #[Assert\NotBlank]
    #[Assert\Positive] // 正の値かチェック
    private int $price;
}
オブジェクト指定の場合

use Symfony\Component\Validator\Constraints as Assert;

class SomeClass
{
    public function someMethod(ValidatorInterface $validator)
    {
        // 変数に対して
        $email = 'メールアドレス';
        // 検査オブジェクトの作成
        $emailConstraint = new Assert\Email(); // メールアドレスかチェック

        $validator->validate($email, $emailConstraint); // 後述

        // 配列に対して
        $row = [
            'name' => 'ほげ',
            'email' => 'メールアドレス',
            'date_of_birth' => '1979-03-28',
        ];
        
        $constraint = new Assert\Collection([ // 検査オブジェクトのセット作成
            'name' => [new Assert\NotBlank(), new Assert\Length(max:200)],
            'email' => new Assert\Email(),
            'date_of_birth' => new Assert\Date(), // 日付型かチェック
        ]);
        
        $validator->validate($row, $constraint);
    }
}

データの検証

すでに上記で記述していますが、検証を行うにはValidatorオブジェクトのvalidate()メソッドを利用します。このメソッドはConstraintViolationListオブジェクトを返しますが、これはエラー内容ConstraintViolationInterface`のリストになっています。


use Symfony\Component\Validator\Validator\ValidatorInterface;

public function someMethod(ValidatorInterface $validator)
{
    $item = new Item();

    ...

    $errors = $validator->validate($item);
    if (count($errors) > 0) { // データになにかしらの誤りがあれば、件数は1件以上になる
        array_map(function (ConstraintViolationInterface $violation) {
            echo($violation->getMessage()); // エラーメッセージ出力
        }, $errors);
    }
    
}

グループ化

『登録時にはチェックしたいけど、更新時は無視したい』などといった、検査する場所で検査内容を変えたい場合があります。その場合はgroupsを指定することで、対応できます。


class Item
{
    #[Assert\NotBlank] // グループ指定なし(常にチェック)
    #[Assert\Length(max: 200, groups: ['register', 'update']) // 'register', 'update'時のみチェック
    #[Assert\Length(min: 50, groups: ['update']) // 文字列長チェック(50文字以上)。'update'時のみチェック
    private string $name;
}

$item = new Item();
...

# 'register'グループとして実施するので、50文字以上チェックは実施されない
$validator->validate($item, groups: 'register'); 

独自チェック

アプリケーションによっては、デフォルトで用意された検査内容ではチェックし切れないものもあります。その場合は独自のConstraint, Validatorクラスを作ることで対応できます。

Constraintには、エラー時のメッセージやオプションの設定、Validatorには検査ロジックを作成します。基本的にConstraintクラスの名前に Validatorをつけたクラスの名前が対応するValidatorクラスになります。

Constraint
src\Validator\ValidEmailDomain.php

namespace App\Validator;

use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;

#[\Attribute]
class ValidEmailDomain extends Constraint
{
    public $message = '"{{ string }}"は許可されていないドメインを利用しています';

    #[HasNamedArguments]
    public function __construct(public readonly array $validaDomains, array $groups = null, mixed $payload = null)
    {
        parent::__construct([], $groups, $payload);
    }
}
Validator
src/Validator/ValidEmailDomainValidator.php

namespace App\Validator;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;

class ValidEmailDomainValidator extends ConstraintValidator
{
    public function validate($value, Constraint $constraint): void
    {
        assert($constraint instanceof ValidEmailDomain);

        if ($value === null || $value === '') {
            return; // 空の場合はチェックをかけない
        }

        foreach ($constraint->validDomains as $domain)
        {
            $pattern = sptintf("/\@%s\$/", $domain);
            if (preg_match($pattern, $value) {
                return; // 許可ドメインと一致したら終了
            }
        }

        // 一致しなかった場合の処理
        $this->context
            ->buildViolation($constraint->message)
            ->setParameter('{{ string }}', $value)
            ->addViolation()
        ;
    }
}

独自チェックの追加
User.php

use App\Validator as AppAssert;
use Symfony\Component\Validator\Constraints as Assert;

class User
{
    #[Assert\NotBlank]
    #[AppAssert\ValidEmailDomain(['somedomain.co.jp', 'somedomain.com'])] // 独自チェック
    private $email;
}

まとめ

今回はValidatorを紹介しました。データの検証はアプリケーションの中でも重要な機能の一つですが、このコンポーネントではアトリビュートでどのプロパティがどのような検査項目を持っているのか一目瞭然なので、便利です。また、あくまでアトリビュートだけなので、検査項目の設定をしてもプログラム的な依存がないところもおすすめです。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?