BeginnerEngineerBlog
中の人
中の人

【php symfony validation ConstraintViolationList】バリデートしたエラーを合体させたい

公開: 2023-10-01 23:30
更新: 2023-10-04 00:24
128
php symfony5.x validation ConstraintViolationList merge
symfonyのvalidatorクラスで検証結果を合体させる方法です。

こんにちは。

中の人です。

symfonyのvalidatorクラスですが、あれ意外と縦に長くなりますよね
で、検証結果の一つ一つが量産されて、一個にまとめたいなーって時ありますよね。
laravelを扱ってると、formRequestだったり、普通のvalidatorだったり、うまいことやってくれるので、symfonyのvalidatorが若干扱いにくいって感じることあるかと思います。(formTypeでバリデーションをかけるとそこまで気にならないかもしれませんが。)
ということでvalidatorの結果を一つにまとめる方法をメモとして紹介します。

2023-10-02追記
まとめ方でもっといい方法わかったので「やり方」に追記しました。

どういうことか


※ Eccube4を利用しています。

Template


📁 root/ec-cube/app/template/default/Hoge/index.twig

{% extends 'default_frame.twig' %}


{% block main %}
    <div class="ec-layoutRole__main">
        <form method="POST", action="{{url('hoge')}}">
            <input type="text" name="name"/>
            <input type="submit" value="送信"/>
        </form>
    </div>
{% endblock %}


Controller


📁 root/ec-cube/app/Customize/Controller/HogeController.php

<?php

namespace Customize\Controller;

use Eccube\Controller\AbstractController;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\Constraints as Assert;

class HogeController extends AbstractController
{
    private $validator;

    public function __construct(
        ValidatorInterface $validator
    )
    {
        $this->validator = $validator;
    }

    /**
     * @Route("/hoge", name="hoge", methods={"GET", "POST"})
     * @Template("Hoge/index.twig")
     */
    public function index(Request $request)
    {
        if ($request->getMethod() === 'POST') {
            $name = $request->get('name');
            
            // 👇 これ
            $notBlankErrors = $this->validator->validate(
                $name,
                [
                    new Assert\NotBlank(),
                ]
            );
            // 👇 これ
            $greaterThanErrors = $this->validator->validate(
                $name,
                [
                    new Assert\GreaterThan(4),
                ]
            );
            // 👇 これたち
            if ($notBlankErrors->count() > 0 || $greaterThanErrors->count() > 0) {
                throw new \Exception();
            }
        }
        return [

        ];
    } 
}

これたちの結果を一つにまとめたいということです。

実際、これらの検証は

            $name = $request->get('name');
            
            // 👇 これ
            $errors = $this->validator->validate(
                $name,
                [
                    new Assert\NotBlank(),
                    new Assert\GreaterThan(4),
                ]
            );
            
            if ($errors->count() > 0) {
                throw new \Exception();
            }

こんな感じでまとめることできますが、今回はそうじゃないということです。


やり方


その1


chat_gptが教えてくれました。

<?php

namespace Customize\Controller;

use Eccube\Controller\AbstractController;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ConstraintViolationList;// 👈 追加

class HogeController extends AbstractController
{
    private $validator;

    public function __construct(
        ValidatorInterface $validator
    )
    {
        $this->validator = $validator;
    }

    /**
     * @Route("/hoge", name="hoge", methods={"GET", "POST"})
     * @Template("Hoge/index.twig")
     */
    public function index(Request $request)
    {
        if ($request->getMethod() === 'POST') {
            $name = $request->get('name');

            $notBlankErrors = $this->validator->validate(
                $name,
                [
                    new Assert\NotBlank(),
                ]
            );
            $greaterThanErrors = $this->validator->validate(
                $name,
                [
                    new Assert\GreaterThan(4),
                ]
            );
            // 👇 これでまとめられる
            $errors = new ConstraintViolationList([...$notBlankErrors, ...$greaterThanErrors]);
            if ($errors->count() > 0) {
                throw new \Exception();
            }
        }
        return [

        ];
    } 
}

その2 (2023-10-02追記)


正直こっちが欲しかった
ソースコード見たらあるじゃーんて
おすおす

<?php

namespace Customize\Controller;

use Eccube\Controller\AbstractController;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ConstraintViolationList;// 👈 追加

class HogeController extends AbstractController
{
    private $validator;

    public function __construct(
        ValidatorInterface $validator
    )
    {
        $this->validator = $validator;
    }

    /**
     * @Route("/hoge", name="hoge", methods={"GET", "POST"})
     * @Template("Hoge/index.twig")
     */
    public function index(Request $request)
    {
        if ($request->getMethod() === 'POST') {
            $name = $request->get('name');

            $main_errors = new ConstraintViolationList();// 👈 ConstraintViolationListをnewする(下の$notBlankErrorsでもいいけどとりあえず)

            $notBlankErrors = $this->validator->validate(
                $name,
                [
                    new Assert\NotBlank(),
                ]
            );

            // 👇 これでもまとめられる(追加できる)
            foreach ($notBlankErrors as $notBlankError) {
                $main_errors->add($notBlankError);
            }
            $greaterThanErrors = $this->validator->validate(
                $name,
                [
                    new Assert\GreaterThan(4),
                ]
            );
            
            // 👇 上記に同じ
            foreach ($greaterThanErrors as $greaterThanError) {
                $main_errors->add($greaterThanError);
            }

            if ($errors->count() > 0) {
                throw new \Exception();
            }
        }
        return [


        ];
    } 
}

これで検証結果をまとめることができます。

挙動




nameを空で送信すると


$errorsにviolatinsが2個セットされています


ちなみに


一つの検証の中に複数の検証があった場合どうなるか見てみます

<?php

namespace Customize\Controller;

use Eccube\Controller\AbstractController;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ConstraintViolationList;

class HogeController extends AbstractController
{
    private $validator;

    public function __construct(
        ValidatorInterface $validator
    )
    {
        $this->validator = $validator;
    }

    /**
     * @Route("/hoge", name="hoge", methods={"GET", "POST"})
     * @Template("Hoge/index.twig")
     */
    public function index(Request $request)
    {
        if ($request->getMethod() === 'POST') {
            $name = $request->get('name');

            $notBlankErrors = $this->validator->validate(
                $name,
                [
                    new Assert\NotBlank(),
                    new Assert\Type("alnum"),// 👈 こいつを追加
                ]
            );
            $greaterThanErrors = $this->validator->validate(
                $name,
                [
                    new Assert\GreaterThan(4),
                ]
            );
            $errors = new ConstraintViolationList([...$notBlankErrors, ...$greaterThanErrors]);
            // if ($nameErrors->count() > 0) {
            //     throw new \Exception();
            // }
        }
        return [
            'errors' => $errors ?? [],
        ];
    } 
}



ちゃんと3つ入りました


終わりに


まぁsymfonyの場合はformTypeでのバリデートが基本(もしくはentity)となる気がしますが、個人的にformType超嫌いです。
ロジックで悩む時間よりformTypeの実装で悩む時間の方が圧倒的に長くなってしまうためです。(ちゃんと仕様読んで勉強すればformTypeの方が便利なんでしょうけど)

あとformTypeだと、あらかじめそのform用のhtmlを作ってから"form_widget"でformを作成すると思いますが、そうすると例えばフロントエンジニアさんが「どこでどうすればフォームの形変えられるねん!」となる場合もある気がして、要するにフロントエンジニアさんなんかも、symfonyやtwigの仕様をある程度知らないと効率が悪くなる気がするんですよね

要するに、htmlはhtmlとして存在させたいということです。(forとかifとかはただ表示させるhtmlのハンドリングですが、form_widgetとかは勝手にhtml作成されるし引数とかでclassとか設定できるけどサーバー側(formType)でも設定できちゃうしおぉ??とフラストレーション溜まる人意外といるんじゃないかと感じるのです。)
フォームのテンプレート変えるために新たにform_themeを作成とかも個人的に面倒だし仕様も正直よくわからん

あとRequest以外のバリデートでも使えますしね

validatorクラスあるならあえてformTypeでバリデートする必要ないよね!
と感じる同志は真似してみるといいかもです!
ただ、フォームバリデートの場合でエラーメッセージを表示させるには、多少の工夫が必要になりますけどね!(formTypeでのバリデートの場合、form_errorsで勝手に表示してくれますからね)
つまりはやっぱりベーシックはformTypeってことかなってことですね
でも俺は嫌いやねん!多少の工夫が必要ならそっち選ぶわ!て感じちゃいます自分は

ということで

お疲れ様でした


20231004追記

なんの値をバリデートしたか判定する方法をこちらに記載しました。
0
0
0
0
通信エラーが発生しました。
【広告】
似たような記事