PHP
laravel

LravelでValidationを実装した話

概要

  • バリデーションエラーログのフォーマットをカスタマイズ

バージョンとか

  • Laravel 5.6
  • PHP7.2

シナリオ

Webアプリケーションを作成する案件で、フレームワークや言語等は問わない。
今回は、 PHPの Laravel で開発することにしました。

社内では GolangNodeJs で行うのがデフォルトっぽく、
特に Golang をメインで開発を行っているみたいです。

管理方法

管理管轄
 |
 +- DockerContainer(App) Golangだったり
 |
 +- DockerContainer(App) Nodejsだったり
 |
 +- DockerContainer(本App)
    |
    +- src
       |
       [Laravel Project]

Dockerでまとめて管理する方法で、エラーログをAPPごとに標準出力で出すことにより、
管理側で一括でログを確認できるような構造になっています。

フォーム内容

form-data 形式でフォームデータをPOSTでリクエストします。

name        = (必須,英数字+[.-_],255文字以内)
id          = (必須,英数字20~24桁) 
recorded_at = (必須,タイムスタンプ数値10桁)

バリデーション

バリデーションのフォーマットは仕様書により固定されており、
以下がバリデーションエラー時のフォーマットである。

Key: {keyname}, Value: {values}, Violation: {violation}

API

バリデーション時のAPIレスポンスは基本400エラーとします
エラーレスポンスはJSONで返します。
バリデーションエラーではないものは、メッセージをそのまま返します。
(以下の表はすべてサンプルで作成した仕様です)

エラーコード フォーマット 内容
A0001 Key:, Value:, Violation: バリデーションエラー時
A0005 File Extension Error ファイル拡張子エラー時
{
    "errors":[
        {
            "code":"A0001",
            "message":"Key: {keyname}, Value: {values}, Violation: {violation}"
        },
        {
            "code":"A0005",
            "message":"File Extension Error"
        },
    ]
}

本題

Laravelでバリデーションエラーが出た場合、自動的にメッセージが作成されますが、
今回は指定されたメッセージに変更しないといけないので、変更していきます。

基本

RouteやControllerなどの説明は省きます。

基本的なバリデーションはこちらのコードで行なえます

$validator = Validator::make($request->all(), [
            "name"        => "required|max:255|not_regex:/[^a-zA-Z0-9._-]/",
            "id"          => "required|not_regex:/[^a-zA-Z0-9]/",
            "recorded_at" => "required|digits:10"
        ]);

エラーハンドリングをする際は、以下のコードでハンドリングが行えます。

if ($validator->fails()) {
    foreach ($validator->errors()->all() as $error_text) {
           // $error_textにエラー内容が入る
    }
}

カスタムエラーメッセージ

カスタムエラーメッセージを使用したい場合は、Validator::make()の第三引数に配列で指定できます。
指定の方法はたくさんありますので公式を御覧ください。
https://laravel.com/docs/5.6/validation#custom-error-messages

$err_msg = "Key: :attribute, Value: :values, Violation: ";
$custom_error_msg = [
    "required"            => $err_msg . "required",
    "max"                 => $err_msg . "maximum length",
    "alpha_num"           => $err_msg . "alphabet&number",
    "digits"              => $err_msg . "number length",
    "device_id.not_regex" => $err_msg . "alphabet&number",
    "name.not_regex"      => $err_msg . "alphabet&number+[._-]"
];

$validator = Validator::make($request->all(), [
    "name"        => "required|max:255|not_regex:/[^a-zA-Z0-9._-]/",
    "device_id"   => "required|not_regex:/[^a-zA-Z0-9]/",
    "recorded_at" => "required|digits:10"
    ], $custom_error_msg);

ですが、これだとエラー出力時にこう出力されます。

{
    "errors":[
        {
            "code":"A0001",
            "message":"Key: name, Value: :values, Violation: alphabet&number+[._-]"
        },

:valuesが置換されていません。
LaravelのValidatorは標準で :attribute をKey名に置換されて出力されるのですが、
それ以外のプレースホルダーは自分で実装しなければいけません。

カスタムプレースホルダー

まず Illuminate\Validation\Validator を継承したサービスモジュールを作成します。

app/Service/TestValidator.php
<?php
namespace App\Service;
use Illuminate\Validation\Validator;

class TestValidator extends Validator
{
    public function __construct($translator, $data, $rules, $messages = [])
    {
        parent::__construct($translator, $data, $rules, $messages);

        // ここに記述
    }
}

次に、Replacerを実装します。

app/Service/TestValidator.php
<?php
namespace App\Service;

use Illuminate\Validation\Validator;

class TestValidator extends Validator
{
    public function __construct($translator, $data, $rules, $messages = [])
    {
        parent::__construct($translator, $data, $rules, $messages);

         // $message    = カスタムエラーメッセージの内容
         // $attribute  = エラーが発生したキー名
         // $rule       = バリデーションのルール 例:required
         // $parameters = 場合によって変わるが、digitsであれば文字数などが配列で入る
         //
         // クロージャなので、$dataを値渡ししてあげる。
         // $data = リクエストデータが入る
        $replacer = function($message, $attribute, $rule, $parameters) use ($data) {

            // $message 内の :values を $data[$attribute] に置換する
            return str_replace(':values', $data[$attribute], $message);
        };

         // 上記のクロージャを適応するルール
        $this->addReplacers([
            "required"=>$replacer,
            "max" => $replacer,
            "alpha_num" => $replacer,
            "digits" => $replacer,
            "mimes" => $replacer,
            "file" => $replacer,
            "not_regex" => $replacer
        ]);

    }
}

$this->addReplacers() により、置換が行われます。

ValidatorServiceProviderを作成

このままではValidator::makeを実行しても上記の TestValidator を読み取ってくれないので、
ValidatorServiceProviderを作成する。

app/Providers/ValidatorServiceProvider.php
<?php

namespace App\Providers;

use App\Service\TestValidation;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Validator;

class ValidatorServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        Validator::resolver(function ($translator, $data, $rules, $messages) {
            return new TestValidation($translator, $data, $rules, $messages);
        });
    }

    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

これでしっかりエラーメッセージが表示されます。

{
    "errors":[
        {
            "code":"A0001",
            "message":"Key: name, Value: 全角文字ダメ!, Violation: alphabet&number+[._-]"
        },

おわりに

やり方がわかるまで苦労しましたが、そこまで変更点もなく自由にエラーメッセージを変更できたのでとても楽でした。
ずっとLaravel使ってるので、エラーハンドリングとかをもっとマスターしていきたいなと。

以上