LoginSignup
218
207

More than 5 years have passed since last update.

フロー図で理解するLaravelバリデータの仕組みと、チーム開発でのケーススタディ

Last updated at Posted at 2018-12-22

目的

Laravelのバリデーションについてはいろんな記事がありますし、自分もいくつか書きましたが、意外と「仕組み」や「全体像」についての解説記事がないので、いっちょ書いてみるか、という穴埋め係根性でまとめた、勉強用の資料です。

  • 公式ドキュメントに「いくつかの方法」が書かれているけど、なにが違うのか?
  • 独自ルールの追加方法もいろいろあるけど、どこに書いたらうまく管理できるのか?
  • なんとなく書き始めたけど、気づいたらカオスになっていた…。

そんな半年前の自分のような方に贈ります。

目次

こんな内容で構成されています。

基礎編

  • HTML INPUT フォームを送信したら、入力値とエラーが表示されるまで
  • CONTROLLER バリデーションを実行する3つの方法
  • INSIDE VALIDATOR バリデータ本体は例外を飛ばすだけで、偉いのはエラーハンドラ

応用編

  • CUSTOM RULE 一括でテストできるカスタムルール管理クラス
  • FORM REQUEST 大量になっても大丈夫 フォームリクエストクラスの実例
  • CUSTOME MESSAGE やらなくてもいいけど どうせやるなら多言語化も見据えて

前提

  • バリデータは触ったことがある(詳しい使い方は省略します…)
  • RESTfulっていうカルチャーがあることは知ってる(説明なしに関連した単語が出てきますがわからなくてもスルー可能な程度)
  • セッションデータって聞いたら「あぁ、サーバに保存さてるユーザー情報のことね」くらいはわかる
  • 例外ってなんか聞いたことある(関連した機能はできるだけ詳しく解説します)
  • PHPUnit?なにそれおいしいの?(書いてますけど読み飛ばしてください…)

今回はごめんなさいな読者様

  • バリデータは全く触ったことがない!
    → すみません。ホントの基礎は書いてません。良書がほかにありますので…。

  • エラーや前回値の入ったフォームのBLADEでの書き方
    → すみません。これを追求しだすと全10回くらいのシリーズ記事がかけます……。

逆にいつかちゃんと書きたい 更に斜め上をいく方法

・どこでもバリデーション → モデルバリデーション
・逆に独自例外をバリデーションエラーにする
・ロジックを全部置き換える
・バリデーション前に値を変換する

基礎編

そもそもバリデータってなに?

一般的に言うと、フォームに入力されたデータなどの「あるデータ」が、「指定されたルール」に適合しているかをチェックする仕組みです。

しかしLaravelのバリデータはもう少し気が利いていて、下図のような動作が、ほとんど中身を実装しなくてもカンタンに実現できます。

1.jpg

ここでポイントは3つあります。
フォームに値を入力して送信すると、

  1. 送信結果画面には進まずに(元のページのURLのまま)
  2. 適切な場所にエラーが表示され
  3. 入力した値が入った状態

元のフォームが表示し直されます。

[HTML INPUT] フォームを送信したら、入力値とエラーが表示されるまで

このとき、Laravelがどう動いているのか、コントローラ周りを覗いてみた図がこちら。

2.jpg

  1. まず初期状態のフォームが表示。このときのURLは /resource/42/edit で、コントローラメソッドは edit が担当しました。
  2. フォームを送信します。送信先URLは POST メソッドで、/resource/42 で、コントローラメソッドは update が引き受けます。
  3. しかし検査の結果バリデーションエラーが発覚
  4. Laravelは通常の処理を中断
  5. このとき発生したバリデーションエラーと、フォームに入力されていた値をセッションに保存
  6. ブラウザに対して、前回のページ /resource/42/edit にリダイレクトするよう指示
  7. ブラウザは /resource/42/edit を改めて開く
  8. コントローラメソッド edit が担当を引き継ぐ
  9. そのままだったら前回と同じフォームをレンダリングするところ、セッションに「エラー」と「前回の値」が保存されているので、それを使ってフォームをレンダリングする。
  10. エラーと前回の値が入ったフォームがブラウザに表示される

個人的にわかりにくくて実装しにくかったのが、9 のレンダリングの仕組み……。だけど、今回は「9. レンダリング方法」については端折らせてください。Laravel標準だと、ほぼ各個撃破で手書きしていく必要があることを書き添えておきます…。

それをスルーした上で、この中で重要そうに見える、「3.バリデーション検査の方法」を詳しく見てみます。点線で囲っているエリアです。

[CONTROLLER] バリデーションの3つの方法

公式ドキュメントの「バリデーション」を見ると、大きく3つの方法が書かれています。
それぞれ、やり方は詳しく書かれていますが、違いと、どういうときに使い分けるのかがイマイチわかりにくいので、個人的見解で解説を試みます。

1 . ワンタッチバリデーション

3.jpg

SampleController.php
public function store(Request $request)
{
    $validatedData = $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ]);

    // $request は安心安全

小規模プロジェクト用

  • ☑ 1人または少人数の開発
  • ☑ 1リクエストの変数の数が5~6個程度
  • ☑ いくつかのコントローラとモデルだけで事足りる規模のアプリ
  • ☑ 複雑なルールは使わない
  • ☑ 多少スルーして内部エラーが起きてもまぁいいか
  • ❌ 欲張るとあっという間にカオスに…

特に何も準備しなくてもイキナリ使える方法です。最もカンタンなので、ドキュメントでも最初に紹介されています。
ただ、コントローラに処理を書くのであっという間にコントローラが肥大化してカオスになってしまうのがデメリット。ちょっとしんどいなーと思い始めたら、速やかにフォームリクエストに。

4.jpg

ちなみに、Laravel5.5から、コントローラメソッドからRequestのメソッドに移管されたので呼び出し方がちょっと変わりました。
Laravel5.4以前はコントローラのメソッドです。

SampleController.php
public function store(Request $request)
{
    $validatedData = $this->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ]);

    // $request は安心安全

2 . フォームリクエスト

SampleController.php
public function store(SampleFormRequest $request)
{
    // $request は安心安全
SampleFormRequest.php
public function rules()
{
    return [
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ];
}

中規模プロジェクト用

5.jpg

  • ☑ コントローラがカオスになってきた!
  • ☑ バリデーションで埋め尽くされていて処理がかけない
  • ☑ 人によって書く場所が違う
  • ❌ やたらと数が増える!ので管理方法をよく考えて

バリデーションなど、WEBからの入力値に対する処理を専用クラスにまとめて書いておくための仕組みです。バリデーションを書く場所ががっちり決まります。エラーが起きるとコントローラに処理フローが流れてこないので、コントローラではその後の処理を書くことに集中できます。

ちょっと想像するとわかりますが、ファイル数がやたらと増えるので(コントローラ数の1.5~2倍くらい)その管理方法は考えておかなきゃいけないのと、「1行程度のバリデーションくらいワンタッチで書いたらいいか」という欲望といかに戦うかが運用上のポイント。

ちなみにFormRequestでは、先程のワンタッチバリデーション $request->validate が(別メソッドに置き換わっていて)使用できないので、ワンタッチと混在することもないのもメリットです。

3 . どこでもなんでもバリデーション

マニア用

6.jpg

SampleController.php
public function store(Request $request)
{
    validator()->validate( $request->all(), [
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ]);

    // $request は安心安全

Laravelのバリデータの心臓部であるValidatorインスタンスを直接叩く方法。上記要件で足りなくなってきたらここに手を出すときです。バリデータ自体はとても小さく取り回しの聞くモジュールなので、賢く使えばとてもコンパクトに実装することができます。

ちなみに、ファクトリー、ファサード、ヘルパーとかいろんな方法が用意されているのでたくさん種類があるように見えますが、下記はすべて同じです。

// Factoryを直接呼び出す
Illuminate\Validation\Factory::make( ........... )->validate()

// ファサードを使う
Validator::make( ........... )->validate();
Validator::validate( ........... );

// ヘルパ関数を使う
validator()->make( ........... )->validate();
validator( ........... )->validate();

[INSIDE] バリデータの正体

ついに登場したバリデータ!
しれっと出てきてさらっとvalidateメソッドを実行しているけど、その後なにも条件判断を書いていません。エラーでもそのまま処理が続行しそうな雰囲気です。
こいつは一体何をしているのでしょうか?

X.png

それがこの図。
必要な機能だけに絞って一番シンプルにすると、これだけのことしかしていません。

バリデータは、
配列をルールでチェックし
エラーがあれば「例外」を飛ばす。

それを、エラーハンドラがキャッチして
Webならリダイレクト、APIならJSONでエラー出力しています。

先に登場した3通りのバリデーションの方法も、基本的にはすべてこれを利用しています。

もう少し詳しく

もう少し詳しく見てみましょう。
これがその図。

9.jpg

まず、メイン材料は2つ。
① テスト対象のデータと、② 検査するルールです。検査対象なので毎回必要です。

さらにサブ材料があります。
1つは、実際に検査を実行する ③バリデーションロジック。
もう1つはエラーメッセージを作るための メッセージデータで、これはさらに、④本文と⑤変数名で構成されています。
このサブ材料は標準で十分用意してくれているので、特に用意しなくても大丈夫。必要になったら追加します。

次にバリデータ本体。
本体は Validation / Validator ⑥というクラスですが、生成に便利なFactory ⑦が用意されていて、アクセスする時はこの Factory を利用します。

Factory は初期化用に make ⑧というメソッドが用意されていて、先のメイン材料2種と、オプションでメッセージ2種を受け取ります。

生成された Validator には大きく2種のメソッドがあって、1つはシンプルに成否だけ判定する passes メソッド ⑨。もう1つがその後の自動処理も起動する validate メソッド ⑩です。

バリデーションの実際の処理は、passes メソッドの中に書かれています。fails や valid といった判定メソッドもありますが、中で passes を呼んでいます。この passes は、

  1. ルールを解釈し
  2. 検査値をチェックして
  3. エラーがあればメッセージを生成して
  4. 内部に保持(成否判定はこのエラーになにか入っているか?で判断しています。)

ということをするので、passes した Validator のインスタンスは、それ単体で変数情報もルールも成否結果もエラー情報もすべて保持しているオブジェクトになります。

仕上げはエラーハンドラ

こうして完成した Validator のインスタンスを使えば、エラー処理やエラー表示など何にでも使えるわけですが、Laravelがすごいのはここから。

Validator の validate メソッドを実行すると、失敗しているときに、ValidationException ⑪という例外を飛ばします。しかもこの ValidationException には、Validator のインスタンスがまるごと搭載されています。

例外を飛ばすとどうなるか?
Laravelはその後の処理をすべて中断して、共通のエラーハンドラ ⑫に処理を移します。
このエラーハンドラの中に、この ValidationException の処理方法 ⑬が用意されていて、WEBフォームの場合、

  1. 搭載されている validator を降ろす
  2. validator から エラーメッセージ を摘出する
  3. PHPのセッション情報に保存する
  4. 入力値も保存する
  5. 前に開いていたページのURLを調べる(Laravelが覚えてる)
  6. ブラウザにそのページを開く指示「リダイレクトレスポンス」を返す

ということをしています。

つまり、Laravelのバリデーションの「処理」はエラーハンドラに書かれていて、Laravelアプリケーションを動かしている限り、いつでもどこでも、validator(...) を実行すれば、あとはLaravelがうまいこと処理してくれるという仕組みになっています。

ここまで長いこと書いてきたけど、要約すると1行です。

\Validator::validate( $this->attributes, $rules );

Laravelのバリデーションに必要なのは、たったこれだけ。

応用編

仕組みを解説して、最終的に超簡単な1行でOK!ではもったいない…。
ここからは、触れていなかった フォームリクエスト と、カスタムルールの追加方法の解説を兼ねて、実践例を紹介します。

実際にそこそこの規模のチーム開発に適用した実装を、思い出しながら、少しブラッシュアップ+シンプルにしたものです。

仕様

7.jpg

  • フォームやAPIの入力は、入力されるパターンごとにFormRequestクラスを定義して以下を書く
    • バリデーションルール
    • そのフォームでしか現れない特殊なメッセージ、変数名
  • 共通で使用できるロジックは専用クラスに取りまとめ、 テスト を書く
  • メッセージもきちんと定義しておきたい

実装してくベースはLaravel5.5のまっさらプロジェクト。
ターミナルは、Windows上で動いているLaravel Homesteadです。

やらないこと

やりたい!っていう声はあったけど、実装が複雑になるので黙殺。

  • FormRequestで個別にバリデーションルールを実装
    → 固有のルールはバリデーションよりも内部ロジックで判定したほうが良い(これはプロジェクトによっては必要と思うのでまた次の機会に……)
  • 上限下限値など変数名以外を差し込むカスタムメッセージ
    → 専用の固定メッセージを作ったほうが早い

[CUSTOM RULE] 一括テストできるカスタムロジックの書き方

順番が前後しますが、まずはカスタムロジックを実装します。

※本稿ではルール名とその実装を呼び分けるため「ロジック」と言っていますが非公式です…。公式ドキュメントでは単に Rule と呼ばれています。

公式ドキュメントにカスタムロジックの追加方法がいくつか書かれていますが、一旦全部忘れてください。
なぜって?
それをそのまま書いていくと、あっというまにカオスなコードが出来上がるからです。

カオスじゃない書き方の要件は下記のような感じでしょうか。

  • 書く場所が決まっている
  • どんなルールが追加されているかひと目で分かる
  • 複雑な実装をしたり数が増えてきてもファイルが巨大にならない(適宜分割できる)
  • テストできる

専用のクラスを用意する

そこで、下記のようなクラスを用意しました。
このクラスはバリデーションのルールを宣言しているだけです。

app/Extensions/ValidationRules.php
<?php

namespace App\Extensions;

class ValidationRules
{
    use ValidationLogics\BasicPatterns;

    public function getRules()
    {
        $class = get_class($this) . '@';

        return [
            // 'rule名' => ['メソッド', 'メッセージ'], と書いていく 
            'number'   => [$class . 'number',   ":attributeには半角数字(0~9)のみが使用できます。"],
            'katakana' => [$class . 'katakana', ":attributeには全角カタカナのみが使用できます。"],
        ];
    }
}

ロジックを書く

ロジックはこちらに書いていきます。サンプルとして2つ用意しました。

app/Extensions/ValidationLogics/BasicPatterns.php
<?php

namespace App\Extensions\ValidationLogics;

trait BasicPatterns
{
    /**
     * 半角数字しか含まれないことをバリデート
     * integer だと '001' を通さないので、代わりに使う
     *
     * @param $attribute  変数名               (ほぼ使わないけど順序的に省略できない...)
     * @param $value      値                   ★通常のチェック対象
     * @param $parameters ルールに与えられた変数(使わないなら省略していいかと)
     * @param $validator  バリデータインスタンス(まず使わないので省略しましょ)
     */
    public function number($attribute, $value, $parameters, $validator)
    {
        return preg_match('/^\s*[0-9]*\s*$/', $value);
    }

    /**
     * カタカナ
     */
    public function katakana($attribute, $value)
    {
        return preg_match('/^[ァ-ヺーヽヾ\s]+$/u', $value); // Unicodeになってカタカナの範囲が広がったらしい 「ヺ」なにこれ?いつ使うの?
    }
}

そう。トレイトにして先程のValidationRulesに載せています。
これはLaravelバリデータがそうなっているのでそれに倣ったものですが、こうするとルールを定義するだけのValidationRulesクラスとロジックを実装しているTraitで役割分担がはっきりするし、ロジックが増えても好きな単位でファイル分割して管理することができるのでメンテナンス性が格段に上がります。

バリデータを拡張してルールを有効化する

ここまでだと、ただロジックを書いただけなのでLaravelは使ってくれません。
どうするか。公式ドキュメントのこれを使います。

Validator::extend('foo', 'FooValidator@validate');

今回は先程作ったクラスを利用して、AppServiceProviderにこう書き加えます。

app/Providers/AppServiceProvider.php
use App\Extensions\ValidationRules; // 追記

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     * @return void
     */
    public function boot()
    {
        // 以下追記
        // バリデーションルールの拡張
        foreach( app(ValidationRules::class)->getRules() as $rule => list($method, $message) ){
            \Validator::extend($rule, $method, $message);
        }
    }

実は、ValidationRules にルールを定義!と偉そうなことをノタマッていましたが、単にこのextendメソッドに渡すデータを配列で持たせているだけでした。

Tinkerでカンタンなテスト

とりあえず、動かしてみましょう。
コマンドプロンプトでtinkerを実行してみます。

#tinkerはLaravelのプログラムを対話型で実行できるテスターです。artisanコマンドで立ち上げます。

$ php artisan tinker
Psy Shell v0.9.7 (PHP 7.2.8-1+ubuntu18.04.1+deb.sury.org+1  cli) by Justin Hileman
>>>

こうします。

>>> $v = validator(['name'=>'12345'],['name'=>'katakana']) // [検査する配列]と[ルール]を渡す
=> Illuminate\Validation\Validator {#2871
     +customMessages: [],
     +fallbackMessages: [
       "number" => ":attributeには半角数字(0~9)のみが使用できます。", // これと
       "katakana" => ":attributeには全角カタカナのみが使用できます。",   // これはカスタムメッセージ
     ],
     +customAttributes: [],
     +customValues: [],
     +extensions: [
       "number" => "App\Extensions\ValidationRules@number",     // これと
       "katakana" => "App\Extensions\ValidationRules@katakana", // これがカスタムルールが組み込まれている証拠
     ],
     +replacers: [],
   }

バリデータインスタンスが生成されます。

>>> $v->passes()
=> false

バリデーションを実行。結果は false。失敗しました。(カタカナなのに、数字を入れているので)
エラーは生成されているでしょうか?

>>> $v->errors()->messages()
 [
     "name" => [
       "nameには全角カタカナのみが使用できます。",
     ],
 ]

うまくいきました!

変数名が英語

なにゆーてんねん!「nameには」ってなんでここだけ英語やねん!って思った方は、スルドイです。ユーザー目線です。あと関西人ですね。
ここは後ほど解決策をいくつか提案しますが、とりあえずでよければ、ここ。

>>> $v = validator(['name'=>'12345'],['name'=>'katakana'],[],['name'=>'ユーザー名']) // 第4引数に 変数名=>表示名 のペアを与える
=> Illuminate\Validation\Validator {#2850
     +customMessages: [],
     +fallbackMessages: [
       "number" => ":attributeには半角数字(0~9)のみが使用できます。",
       "katakana" => ":attributeには全角カタカナのみが使用できます。",
     ],
     +customAttributes: [
       "name" => "ユーザー名", // セットされました
     ],
     +customValues: [],
     +extensions: [
       "number" => "App\Extensions\ValidationRules@number",
       "katakana" => "App\Extensions\ValidationRules@katakana",
     ],
     +replacers: [],
   }

結果も変わります。

>>> $v->passes()
=> false
>>> $v->errors()->messages()
=> [
     "name" => [
       "ユーザー名には全角カタカナのみが使用できます。",
     ],
   ]

できました。

ルールはアプリケーションのどこでもあまり変わらず適用されるけど、それが適用される変数名は、バリデーションする箇所によって都度変わることが多いので、このように、バリデーションするときどきで、個別に変数名を与えても良いんじゃないかなぁと思います。

いやいや、うちは変数名もバリバリ管理してるんで大丈夫、という敏腕プロマネさんにオススメな集中管理プランは、後ほどFormRequestのところで。

本気テスト PHPUnit

今回、あえてこのように、専用クラスにまとめたのは、兎にも角にも「テストが書きたかったから」です。
公式ドキュメントのようにクロージャーを使ってルールを書くと、テストができません。Ruleクラスも、できるにはできますがバラバラ過ぎて煩雑。
今回のValidationRulesクラスだと、テストはこんな感じにかけます。

※Laravel Homesteadは phpunit というコマンドエイリアスが入っています。Linuxやmacのプロンプトでは vendor/bin/phpunit と読み替えてください。

tests/Unit/Extensions/ValidationRulesTest.php
<?php
// phpunit tests/Unit/Extensions/ValidationRulesTest  ← ターミナルで単体実行するコマンドを書いておくと便利
namespace Tests\Unit\Extensions;

class ValidationRulesTest extends \Tests\TestCase
{
    const O = true; // ← 下部テストケース一覧がとても見やすくなるのでオススメ
    const X = false;

    /**
     * @test
     * @dataProvider ruleProvider
     */
    public function rule( $ex_result, $rule, $value )
    {
        $validator = validator()->make( // ← さりげなく「マニア用どこでもバリデータ」の応用例です
            ['attr'=>$value],
            ['attr'=>$rule]
        );
        $result = $validator->passes();

        $this->assertEquals($ex_result, $result, '期待した結果とマッチ');
        if( !$ex_result ){
            $this->assertNotEmpty( $validator->messages()->first(), 'エラー時にはメッセージが1つ以上ある');
        }
    }
    public function ruleProvider()
    {
        return [
            [self::O, 'number', '0123456789'],
            [self::X, 'number', '1,234'],
            [self::X, 'number', '1234'],
            [self::X, 'number', '-1234'],

            [self::O, 'katakana', 'カタカナテスト'],
            [self::X, 'katakana', 'ひらがな'],
            [self::X, 'katakana', '漢字'],
            [self::X, 'katakana', 'ABC'],
        ];
    }
}

実行してみます。
ターミナルで……

$ phpunit tests/Unit/Extensions/ValidationRulesTest
PHPUnit 6.5.12 by Sebastian Bergmann and contributors.

........       8 / 8 (100%)

Time: 1.94 seconds, Memory: 12.00MB

OK (8 tests, 14 assertions)

すべてのルールの動作チェックが1つのテストメソッドで一気に書けるので便利ー♫ と個人的にとても気に入っています。
もちろん、カスタムルールだけでなく標準のルールもテストできるので、たとえばややこしい存在系のテストも書いておくと良いかもです。

参考:Laravelの標準バリデーションのわかりにくい挙動を実験して確かめたまとめ

    [self::O, 'required', 'データがある'],
    [self::X, 'required', ''],
    [self::X, 'required', null],

    [self::O, 'filled', 'データがある'],
    [self::X, 'filled', ''],
    [self::X, 'filled', null],

    [self::O, 'present', 'データがある'],
    [self::O, 'present', ''],
    [self::O, 'present', null],

[FORM REQUEST] ルールを書く場所を決め打ちする

8.jpg

FormRequestとはなにか?というと、「フォームから送信されてきたリクエストデータを、コントローラに渡す前に、データをいろいろするクラス」で、これがあると、コントローラは、入ってきたリクエストデータが安心安全なものだと保証されて、一切のチェック無しでロジックを書き進めていくことができます。

作る

さぁ、Artisanコマンドの出番です! となりますが、最初の1回だけでOKです。以降は出来上がったものをコピーするほうが早いです。

$ artisan make:request BaseRequest
Request created successfully.
$ artisan make:request FirstRequest
Request created successfully.

そういえば、Homestead使ってるって前提しているから、phpをアタマにつけなくても良かった。

できたBaseRequestは基底クラスにしてあとで共通機能を載せられるようにしておきます。
FormRequestの認可機能(authorizeメソッド)は使えばとても強力ですが、今回は主題がずれるので説明も利用も省略。フリーパスにしておきます。

app/Http/Requests/BaseRequest.php
<?php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

// ↓ このクラスを使うことは想定していないので抽象クラスにしておきます
abstract class BaseRequest extends FormRequest
{
    public function authorize()
    {
        return true; // true に変更
    }
    // その他に書いてあることはすべて削除
}

実際に使うクラスはこちら。

app/Http/Requests/FirstRequest.php
<?php
namespace App\Http\Requests;

// use Illuminate\Foundation\Http\FormRequest; // 削除

class FirstRequest extends BaseRequest // 基底クラスを変更
{
    // 以下、このように書き換える
    public function rules()
    {
        // 変数名 => ルール名|ルール名|ルール名
        return [];
    }

    public function messages()
    {
        // 変数名.ルール名 => メッセージ
        return [];
    }

    public function attributes()
    {
        // 変数名 => 表示名
        return [];
    }
}

リクエストクラスの名前ですが、「コントローラ名+フォーム名」的なものにするのがおすすめです。コントローラ単位で並ぶので。
例えば、PostController に使う場合、PostSearchRequest, PostEditRequest。
新規作成フォームと更新フォームがほぼ同じなら、一緒くたにしてしまったほうが管理が楽です。

最初はコントローラと同じディレクトリに配置したほうが良いかと思いましたが、コントローラとはそれほど同時に編集しないし、コントローラ触るとき邪魔だし、そもそもコントローラ触るときにリクエストを意識しないことが目的だし、名前が似ていればファイル名検索で関連ファイルとして出てくるので、フォームリクエストクラスは一箇所にドカッと入れておくのが結局はカンタンでした。

というわけで、コピーして書き換えてみます。

app/Http/Requests/PostEditRequest.php
<?php
namespace App\Http\Requests;

class PostEditRequest extends BaseRequest
{
    public function rules()
    {
        // 雑談TIPS:これがprotectedなプロパティじゃなくてメソッドなのは、
        // 例えば新規登録と更新でルールを差し替えるというように
        // リクエストの値をチェックして動的にルールを変更できる、ということだと思う。
        return [
            'id'    => 'nullable|integer',
            'title' => 'required|string|max:192',
            'body'  => 'string|max:5000',
            'delpw' => 'requied_unless:body,|password'
        ];
    }

    public function messages()
    {
        return [
            // このフォーム専用のメッセージを書くところ
            // required_if などは標準のメッセージが自然な日本語にならなかったりするので
            'delpw.requied_unless' => '本文が入力されている時は:attributeが必要です。',
        ];
    }

    public function attributes()
    {
        return [
            // このフォームに使用している変数の表示名を書く
            'id'    => 'ID',
            'title' => 'タイトル',
            'body'  => '本文',
            'delpw' => '削除用パスワード',
        ];
    }
}

使う

これをコントローラに適用します。

app/Http/Controllers/PostController.php
namespace App\Http\Controllers;

use App\Post;
// use Illuminate\Http\Request; 全部置き換え終わったら要らない
use App\Http\Requests\PostEditRequest; // 追加

class PostController extends Controller
{
    // ...
    public function store( /* 置き換える Request */ PostEditRequest $request)
    {
        $request->all(); // ここに到達した時点でバリデーションに合格していて、キレイなことが保証されている
        // ...

[CUSTOME MESSAGE] 無駄に多言語化するカスタムメッセージ

今までの方法で、エラーメッセージはほぼ完成しているので、ここはオプション、おまけです。
多言語化と書いてありますが、イチバンのメリットは、多言語化よりも、変数名の共通化です。

変数名の共通化

resource/lang ディレクトリにある、 validation.php に書きます。標準では resource/lang/en/validation.php が用意されていますが、日本語ロケールに変更している場合は resource/lang/ja/validation.php になります。

※Laravelは、このlang以下のディレクトリ単位で表示言語を切り替えます。ロケールと多言語化について詳しくはLaravel 多言語化を参照してください。

resources/lang/en/validation.php
    /*
    |--------------------------------------------------------------------------
    | Custom Validation Attributes
    |--------------------------------------------------------------------------
    | ......
    */

    'attributes' => [
        'name' => '名称', // ここに追加(わかるようにさっきと違う名前にしてみた)
    ],

Tinkerでテストしてみます。

>>> $v = validator(['name'=>'12345'],['name'=>'katakana']) // 第4引数を省略
=> Illuminate\Validation\Validator {#2872
     +customMessages: [],
     +fallbackMessages: [
       "number" => ":attributeには半角数字(0~9)のみが使用できます。",
       "katakana" => ":attributeには全角カタカナのみが使用できます。",
     ],
     +customAttributes: [], // なくなりました
     +customValues: [],
     +extensions: [
       "number" => "App\Extensions\ValidationRules@number",
       "katakana" => "App\Extensions\ValidationRules@katakana",
     ],
     +replacers: [],
   }
>>> $v->passes()
=> false
>>> $v->errors()->messages()
=> [
     "name" => [
       "名称には全角カタカナのみが使用できます。", // できた!
     ],
   ]
>>>

できました!
ここに書いておけば、FormRequestに個別指定する手間が省けます。
よく使う変数名はここに書いておくと良いかと思います。

メッセージの多言語化

メッセージ本体もここに書いておくことができます。

resources/lang/en/validation.php
    /*
    |--------------------------------------------------------------------------
    | Custom Validation Language Lines
    |--------------------------------------------------------------------------
    | ......
    */

    // どこに書いても良いのですがここに書いておきます
    // 直下に ルール名 => メッセージ と書きます
    'number'  => ":attributeには半角数字(0~9)のみが使用できますヨ。",
    'katakana'  =>0 ":attributeには全角カタカナのみが使用できますヨ。",

    'custom' => [
        'attribute-name' => [

2重定義になるので、ValidationRulesに書いたメッセージは省略するか……

app/Extensions/ValidationRules.php
        return [
            'number'   => [$class . 'number',   null], // 省略しちゃいましょう
            'katakana' => [$class . 'katakana', null],
        ];

ここに書くな!と矯正するために書けないようにしましょう。

app/Extensions/ValidationRules.php
        return [
            'number'   => $class . 'number', // メソッドだけに
            'katakana' => $class . 'katakana',
        ];
app/Providers/AppServiceProvider.php
    public function boot()
    {
        // バリデーションルールの拡張
        foreach( app(ValidationRules::class)->getRules() as $rule => $method ) ){
            \Validator::extend($rule, $method);
        }
    }

テスト!

>>> $v = validator(['name'=>'12345'],['name'=>'katakana'])
=> Illuminate\Validation\Validator {#2866
     +customMessages: [],
     +fallbackMessages: [], // なくなりました
     +customAttributes: [],
     +customValues: [],
     +extensions: [
       "number" => "App\Extensions\ValidationRules@number",
       "katakana" => "App\Extensions\ValidationRules@katakana",
     ],
     +replacers: [],
   }
>>> $v->passes()
=> false
>>> $v->errors()->messages()
=> [
     "name" => [
       "名称には全角カタカナのみが使用できますヨ。", // できた!
     ],
   ]

あとがき

バリデーションについてはいままで色々書いてきましたし、これからももっとマニアックなテクニックをいろいろ紹介したいなーと思ってはいるのですが、Advent Calendar をきっかけに、まとめ記事書いておくかーと思って頑張ってみました。

Laravelは、1つの機能に、マニアックなフルカスタム実装だけでなく、ワンステップですぐに使える超カンタンな使い方も用意されていて、だからこそ初心者にもとっつきやすいのが大きな人気の理由ですが、逆に色々用意されていて、結局どうするのが良いのか?がよくわからない、好きにしろって言われても困る、ということをよく感じます。もっと実装例を見ろ!ということか。そうですよねー。

もっと勉強して、もっと良いテクニックを紹介していければと思います。
こうしたほうがいいよ!といった改善提案も大歓迎ですのでご指摘お待ちしております(^^)

こんな記事も書いています

Laravelのちょっとマニアックな視点から、誰も書かない記事を書いています(笑
合わせてご覧いただけると幸いです(^^)

218
207
2

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
218
207