NTTドコモ様_dカーシェア
NTTドコモ様_dカーシェア
2017.07.07
#25
バックエンドへの道

洗練された『Laravel』のバリデーションについてご紹介します

エリカ

こんにちは、エリカです。

今回も『 Laravel 』です。

 
『 Laravel 』では入力値チェックも洗練されています。

 

入力値の受け取り

まずは画面(ユーザー)からの入力値の受け取り方についてです。お問い合わせフォームからの入力を受け取るとしましょう。

コントローラクラスを準備します。

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;

class FormController extends Controller
{
    public function form()
    {
        return view('form');
    }

    public function send(Request $request)
    {
        dd($request->all()); // 入力値を表示
    }
}

ビューを準備します。

@extends('layouts.app')

@section('content')
    <div class="container">

        @if (count($errors) > 0)
            <div class="alert alert-danger">
                <ul>
                    @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                    @endforeach
                </ul>
            </div>
        @endif

        <form action="{{url('/send')}}" method="post">
                        {{ csrf_field() }}
            <div class="form-group">
                <label>Name</label>
                <input type="text" class="form-control" name="name" value="{{old('name')}}" placeholder="name">
            </div>

            <div class="form-group">
                <label>Level</label>
                <input type="text" class="form-control" name="level" value="{{old('level')}}" placeholder="level">
            </div>


            <input type="submit" value="submit">
        </form>
    </div>
@endsection

次に、作成したコントローラを Route に登録します。

Route::get('/form', 'FormController@form');
Route::post('/send', 'FormController@send');

これで、/form にアクセスすると、入力フォームが表示されるようになります。そして、そのフォームを送信をすれば、画面上に送信された値が表示されます。

これで、入力値を受け取る準備ができました。

 

基本のバリデーション

では、基本のバリデーションを実装してみます。バリデーションに成功すれば入力値を表示し、もしも、失敗した場合はエラーとともにフォーム画面に戻すようにします。 なお、バリデーションのルールは「入力を必須にする」ということにしたいと思います。

Form コントローラーの send メソッドに、以下のコードを追記します。

public function send(Request $request)
    {
        $this->validate($request, [
            'name'  => 'required',
            'level' => 'required',
        ]);

        dd($request->all()); // 入力値を表示
    }

入力値が格納されている $request と、バリデーションのルールを定義した配列を引数として、$this->validate() を実行しているだけです。

これだけで、それぞれの入力フィールドに値を入れて送信すれば、その内容が表示され、どちらか一方でも空の状態で送信すると、エラーが表示されます。

『 Laravel 』ではあらかじめ多くの便利なルールが定義されており、”required" もそのひとつです。

この他にも多くのバリデーションルールがあります。

>> Laravel5.4 で使用可能なバリデーションルール

 

複数のバリデーションルールを指定

必須に続いて、”name" はアルファベットのみ。”level" は整数のみで、1 から 99 の範囲までというルールにしてみましょう。

$this->validate($request, [
            'name'  => 'required|alpha',
            'level' => 'required|numeric|between:1,99',
        ]);

バリデーションに失敗した場合、どのルールに失敗したかがエラーのメッセージとして表示されます。 例えば、level に 1 から 99 以外の整数を入力した際のメッセージは 「The level must be between 1 and 99. のようなものですね。

 

エラーメッセージの変更

エラーメッセージを変更する方法はいくつかありますが、ここでは直接メッセージを指定してみます。

$this->validate()に、第 3、第 4 の引数を渡します。

$this->validate($request, [
            'name'  => 'required|alpha',
            'level' => 'required|integer|between:1,99',
        ], [
            'level.required' => ':attributeは必須やねん',
            'level.integer'  => ':attributeは整数やねん',
            'level.between'  => ':attributeは:minから:maxまでやねん',
        ], [
            'level' => 'レべル',
        ]);

第 3 引数では、どのフィールド( attribute )のどのルールかを “.” を結合した添え字に、ルールに合致しない場合のエラーメッセージを要素とする配列を用意します。要素にあるコロンを含む記述( :attribute,:min,:max など)には、実行時の値がそれぞれ割り当てられます。この場合は、”:attribute” には “level” が割り当てられます。そして、第 4 引数ではその “:attribute” の内容をさらに置き換えることができます。この指定であれば “:attribute” に、”level” が割り当てられる場合は、それを “レベル” に置き換えます。

“between:x,x”などのようなバリデーションに対するエラーメッセージについては、resources/lang/en/validation.php を参照すれば、どのような記述ができるかわかると思います。そして、翻訳ということであれば、例えば resources/lang/ja/validation.php などの言語ごとのファイルを用意することで実現可能です。

 

ところで$this->validate()は何をしてる?

余談になりますが、元のフォーム画面に戻したり、エラーのメッセージの送信などの処理が一切見当たりません。それは $this->validate() がすべてを行なってくれているからです。では、$this->validate() は、どのような処理なのでしょうか。

Form コントローラは、AppHttpControllersController クラスを継承しています。さらに、このクラスは IlluminateFoundationValidationValidatesRequests トレイトを利用しおり、下記の validate メソッドが実行されている、というわけです。

/**
     * Validate the given request with the given rules.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  array  $rules
     * @param  array  $messages
     * @param  array  $customAttributes
     * @return void
     */
    public function validate(Request $request, array $rules, array $messages = [], array $customAttributes = [])
    {
        $validator = $this->getValidationFactory()->make($request->all(), $rules, $messages, $customAttributes);

        if ($validator->fails()) {
            $this->throwValidationException($request, $validator);
        }
    }

 

入力値を格納した$requestと、バリデーションのルールを定義した配列から、バリデーションを行うインスタンスが生成されます。そして、そのインスタンスのfailsメソッドの結果によって、例外が投げらます。 次に、Laravelは投げられた例外に応じて様々な処理を自動で行うようになっているので、この場合は送信元のフォームへリダイレクトしてくれます。その際に、入力された内容や、エラーのメッセージを保持する処理が施されているので、エラーの表示や、元の入力値の復元などを実現することができるのです。

 

RequestClassによるバリデーション

バリデーションを専用のリクエストクラスに任せてしまうこともできます。

Artisan CLI で簡単に準備できます。

"php artisan make:request FormSendRequest"

上記を実行すると、下記のような Request クラスを継承したクラスが作成されます。

<?php

namespace AppHttpRequests;

use IlluminateFoundationHttpFormRequest;

class FormSendRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

この rules メソッドに先ほどコントローラに記述していたバリデーションルールや、メッセージを設定していきます。

先ほどコントローラで設定した内容を、すべてリクエストクラスに移していきます。rules メソッドは、バリデーションルールの配列を返すようにします。

public function rules()
    {
        return [
            'name'  => 'required|alpha',
            'level' => 'required|integer|between:1,99',
        ];
    }

 

messages メソッドと attributes メソッドも定義します。

public function messages()
    {
        return [
            'level.required' => ':attributeは必須やねん',
            'level.integer'  => ':attributeは整数やねん',
            'level.between'  => ':attributeは:minから:maxまでやねん',
        ];
    }

    public function attributes()
    {
        return [
            'level' => 'レべル',
        ];
    }

 

最後に、authorize メソッドが true を返すようにしておきます。

この authorize メソッドでは、例えば、データの更新などで変更可能なデータ以外への操作を許可しないといった処理を差し込むことができるようになっています。

結果として、下記のようなクラスになります。

<?php

namespace AppHttpRequests;

use IlluminateFoundationHttpFormRequest;

class FormSendRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name'  => 'required|alpha',
            'level' => 'required|integer|between:1,99',
        ];
    }

    public function messages()
    {
        return [
            'level.required' => ':attributeは必須やねん',
            'level.integer'  => ':attributeは整数やねん',
            'level.between'  => ':attributeは:minから:maxまでやねん',
        ];
    }

    public function attributes()
    {
        return [
            'level' => 'レべル',
        ];
    }
}

 

次に、コントローラクラスでこのリクエストクラスを利用するようにします。

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;
use AppHttpRequestsFormSendRequest;

class FormController extends Controller
{
    public function form()
    {
        return view('form');
    }

    public function send(FormSendRequest $request)
    {
        dd($request->all()); // 入力値を表示
    }
}

"use AppHttpRequestsFormSendRequest;" で、専用のリクエストクラスを利用できるようにし、send メソッドが FormSendRequest インスタンスを受け取るように、タイプヒントで指定するだけです。こうすることにより、コントローラのメソッドが呼び出される前に、リクエストクラスでバリデーション処理を自動的に行ってくれます。

コントローラがすっきりしましたね。

 

RequestClassによるサニタイズ

全角で送信されて来た内容を自動で半角に変換する場面などはよくあると思います。

これもリクエストクラスで処理できるようにしてみます。validator メソッドと sanitize メソッドを以下のように追記します。

public function validator(Factory $factory)
    {
        return $factory->make($this->sanitize(), $this->container->call([
            $this,
            'rules',
        ]), $this->messages(), $this->attributes());
    }

    public function sanitize()
    {
        return $this->all();   
    }

 

validator メソッドは、バリデーション実行用のインスタンスを生成する部分で、本来であればリクエストデータをそのまま受け渡します。そこを、sanitize メソッドを経由させることで、データの変換などを事前に行うことができるようになります。

全角を半角に変換する処理を実装します。

public function sanitize()
    {
        $input = $this->all();

        if (isset($input['level'])) {
            $input['level'] = mb_convert_kana($input['level'],'n');
        }

        $this->replace($input);
            return $input;
    }

 

これで “level” のフィールドに全角数字が入力された場合でも、自動で半角数字に変換して処理をするようになりました。

 

カスタムバリデーションルール

もしも、あらかじめ用意されているバリデーションルールでも不足する場合は、独自に用意することができます。

こちらも、Artisan CLI で準備します。

"php artisan make:provider ValidatorServiceProvidor"

上記を実行すると、app/Providors 以下に ValidatorServiceProvisor クラスが作成されます。

そのクラスの boot メソッドにカスタムバリデーションルールを追記します。

<?php

namespace AppProviders;

use IlluminateSupportServiceProvider;
use IlluminateSupportFacadesValidator;

class ValidatorServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        Validator::extend('foo', function ($attribute, $value, $parameters, $validator) {
            return preg_match('/^foo/', $value);
        });
    }

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

 

ここでは “foo” というカスタムバリデーションルールを定義しています。”foo” という文字で始まっているかをチェックするだけです。

これで、”required” や “alpha” などのバリデーションのルールと同様に、”foo”というルールを指定することができるようになりました。

リクエストクラスで指定してみます。

public function rules()
    {
        return [
            'name'  => 'required|alpha|foo',
            'level' => 'required|integer|between:1,99',
        ];
    }

 

“name” フィールドの入力が “foo” から始まらない場合は、バリーデーションエラーとなるようになりました。

しかし、エラーメッセージには、”validation.foo” と表示されてしまいます。カスタムバリデーションルールのエラーメッセージについても messages メソッドで指定することももちろんできますが、ここではまた別の方法で設定してみたいと思います。

 

エラーメッセージ

カスタムバリデーションのエラーメッセージを resouces/lang/en/validation.php で設定してみます。

バリデーションルールと、そのエラーメッセージを定義した配列に、カスタムバリデーションルールも追記していく形になります。

// fooルールに違反した場合
    'foo' => ':attributeはfooで始まるねん',

 

このような形で、エラーメッセージを言語ファイルにまとめて定義しておけば、翻訳の対応も容易になります。

 

バリデーションのユニットテスト

最後に、バリデーションのテストです。

Artisan CLI で雛形を作ります。

"php artisan make:test FormSendRequestTest --unit"

tests/Unit/FormSendRequestTest.php ファイルが作成されますので、テストを書いてみます。

<?php

namespace TestsUnit;

use TestsTestCase;
use IlluminateFoundationTestingWithoutMiddleware;
use IlluminateSupportFacadesValidator;
use AppHttpRequestsFormSendRequest;

class FormSendRequestTest extends TestCase
{
    /**
     * @dataProvider inputDataProvider
     */
    public function testFormSendRequestRule(string $key, string $value, bool $expect)
    {
        $request   = new FormSendRequest();
        $input     = [$key => $value];
        $rules     = $request->rules();
        $rules     = array_only($rules, $key);
        $validator = Validator::make($input, $rules);
        $result    = $validator->passes();

        $this->assertEquals($expect, $result);
    }

    public function inputDataProvider()
    {
        return [
            'name成功' => [
                'name',
                'foo',
                true,
            ],
            'name失敗' => [
                'name',
                'bar',
                false,
            ],
            'level成功' => [
                'level',
                99
                true,
            ],
            'level失敗' => [
                'level',
                100
                false,
            ],

}

データプロバイダを利用して、attribute、値、期待する結果をテストしていっています。範囲のバリデーションや、文字数のチェックなども、簡単にテストを書いておけますね。

 

とにかく洗練されている

このように感じました。簡単に使う分にはもちろん、少し複雑な処理が必要になる場合でも、非常に自由度が高いと感じました。

いかがでしたか? また『 Laravel 』の洗練された機能をご紹介できればと思います。