32
27

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 3 years have passed since last update.

LaravelAdvent Calendar 2020

Day 5

【Laravel6.x】バリデーション周りで覚えておきたいこと(まとめ)

Last updated at Posted at 2020-12-04

はじめに

Laravelを触り始めて1年半ほどたったので、自分の健忘録を兼ねて、バリデーション周りで覚えておきたいことをまとめます。

公式ドキュメントや他の記事を参考にしつつ、独自に必要と思うものを追加しています。

Laravelのバージョンは6系です。

間違いなどコメント欄で指摘してもらえると助かります。

1. 基本的な書き方

Laravelのバリデーションは複数の書き方があるが、Validateメソッドを使うとシンプルに書ける。

また、細かな挙動をマニュアルに設定したい場合は、Validatorファサードを使うこともできる。

1-1. Validateメソッドを使ったController

ブログ記事投稿用のバリデーションを例にした簡単なサンプル。

app/Http/Controllers/BlogController.php

<?php

namespace App\Http\Controllers;

use App\Models\Blog;
use Illuminate\Http\Request;

class BlogController extends Controller
{

// ... 省略 ...

/**
 * ブログ記事投稿
 *
 * @param  Request  $request
 * @return Response
 */
public function store(Request $request)
{
    // 簡単なバリデーション
    $validated = $request->validate([
        'title' => 'required|string|max:255',
        'body' => 'required|string|max:1000',
    ]);

    // バリデーション後の処理
    $blog = new Blog();
    $blog->fill($validated)->save();

コントローラーで受け取ったリクエストを$request->validate([ルールの配列])とするだけで、バリデーションが行える。

1-2. ルールの書き方

バリデーションロジックは、デリミタを使う方法か配列での指定が存在している。

// デリミタを使用
'title' => 'required|string|max:255',
'body' => 'required|string|max:1000',

// 配列で指定
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string', 'max:1000'],

インプットされたデータが配列の場合、キーをxxx.yyyの形で記述出来る。

'category.child' => 'string',
'category.parent' => 'string',
'tags.*' => 'string', // * でワイルドカードが使える

1-2. エラーを画面に表示する

バリデーションエラーがある場合、リクエストを送った画面にリダイレクトされ、セッションを使ってエラーが返される。

以下は、エラーをブログ投稿画面に表示する例。

resources/views/post/create.blade.php

<h1>ブログ記事投稿</h1>

<!-- エラーをまとめて表示する記述 -->
@if ($errors->any())
  <div class="alert alert-danger">
      <ul>
          @foreach ($errors->all() as $error)
              <li>{{ $error }}</li>
          @endforeach
      </ul>
  </div>
@endif

<!-- 送信フォーム部分 -->
<form action="/blogs" method="post">
  @csrf
  <p>タイトル:<input type="text" name="title"></p>
  <p>本文:<textarea name="body"></textarea></p>
  <input type="submit" value="送信">
</form>

個別に表示させることも下のような感じで簡単にできる。

resources/views/post/create.blade.php
<!-- 個別にエラーを表示する例 -->
<input type="text" name="title" class="@error('title') is-invalid @enderror">
@error('title')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror

1-3. エラーをJSONで返す(API向け)

Validationメソッドでは内部でリクエストを判定して、APIへのリクエストはエラーをJSONで返すことができる。

※ コントローラーのバリデーション部分は、同じコードを使うことができる。

リクエストヘッダーにAccept:application/jsonを指定すると、下のような形式でエラーが返される。

JSONで返されたバリデーションエラーの例(ステータスは422)
{
    "message": "The given data was invalid.",
    "errors": {
        "title": [
            "The title must be a string."
        ]
    }
}

1-4. さまざまなバリデーションルール

Laravelにはたくさんのバリデーションルールが用意されていて、公式ドキュメントに一覧がある。

以下は、個人的によく使うもののリスト。

// 必須か否かなど
'key' => 'required', // 必須。nullや空文字・空の配列などもエラーとなる
'key' => 'nullable', // 不要。nullや空文字・空の配列などが許される
'key' => 'sometimes', // フィールドがあれば、後述のルールが適応される。なければ適応されない。

// 数値系
'key' => 'boolean', // 0か1
'key' => 'integer', // 整数。'1'や'-2'など
'key' => 'numeric', // 数値。'0.1'や'-0.333'など
'key' => 'min:0', // 0以上。
'key' => 'max:10', // 10以下。

// 文字列系
'key' => 'string', // 文字列。
'key' => 'string|min:3', // 3文字以上。
'key' => 'string|max:10', // 10文字以下。
'key' => 'digits:3', // 数字のみの文字列。この場合3桁。'001'など最初が0でもいい
'key' => 'email', // メールアドレスとして正しいか。

// 日付系
'key' => 'date', // 日時として認識できる文字列。'2000-01-01'や'2020-05-15T01:04:54+09:00'など。PHPのstrtotimeで認識できるフォーマットが許容される。
'key' => 'date_format:Y-m', // 日時のフォーマットを指定。この場合'2000-01'などの年月に限定している
'key' => 'after_or_equal:2000-01-01', // 指定した日時以降の日時
'key' => 'before:2000-01-01', // 指定した日時よりも前の日時

1-5. ユーザー登録で使うバリデーション例(unique,confirmed

unique,confirmedは、下のような感じで使える。

登録画面のインプットタグ例
<input type=text name=email>
<input type=text name=password>
<input type=text name=password_confirmation>
ユーザー登録バリデーションの例
// 指定したテーブルで一意。この場合、Usersテーブルで同じ値がないことが要求される。
'email' => 'unique:users',

// 入力ミスを防ぐための確認用フィールド。この場合、passwordとpassword_confirmationの値が同じであること。
'password' => 'confirmed',

// xxxxx_confirmationのルールは記述しない。
'password_confirmation' => 'confirmed', // これはNG

// 実践的にはこんな感じ
'email' => 'required|unique:users|email|max:255',
'password' => 'required|confirmed|min:8|max:255|alpha_num',

1-6. Validatorファサード使ったバリデーション

$request->validate()の書き方はシンプルでよいが、挙動が内部で決まっているので、エラーの返し方やリダイレクト先などをマニュアルに変更したいなどのときはValidatorファサードを使うことができる。

app/Http/Controllers/BlogController.php

<?php

namespace App\Http\Controllers;

use App\Models\Blog;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator; // 追加

class BlogController extends Controller
{

// ... 省略 ...

    /**
     * ブログ記事投稿
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'title' => 'required|string|max:255',
            'body' => 'required|string|max:1000',
        ]);

        if ($validator->fails()) {
            return redirect('blogs/create')
                        ->withErrors($validator)
                        ->withInput();
        }

        // バリデーション後の処理
        $blog = new Blog();
        $blog->fill($request->validated())->save();
      

こちらの記事により詳しい解説が載っています。
【laravel】Validatorによるバリデーション - Qiita

2. FormRequestを利用したバリデーション

FormRequestクラスを使うと、コントローラーでバリデーションを書かなくて済む(0行になる)。

下は、実際にFormRequestを使ったブログ記事投稿コントローラーの例。

app/Http/Controllers/BlogController.php

/**
 * ブログ記事投稿
 *
 * @param  StoreBlogPost  $request
 * @return Response
 */
public function store(StoreBlogPost $request) // 独自に作成したStoreBlogPostを使っている
{
    // バリデーション後の処理(すでにリクエストはバリデート済み)
    $blog = new Blog();
    $blog->fill($request->validated())->save();

    // ... 省略 ...

2-1. FormRequestの作成

下のコマンドでFormRequestを継承したクラスがapp/Http/Requestsに作成される

php artisan make:request StoreBlogPost

2-2. FormRequestの編集

作成されたFormRequestのrulesメソッドにバリデーションルールを記述する。

authorizeメソッドには、認証ロジックを記述できる。
認証をここで行わない場合はtrueを返すか、メソッドごと削除すればよい。

app/Http/Requests/StoreBlogPost.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreBlogPost extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true; // 認証をここで行わないときはtrueを返せばOK
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        // ここでルールを記述すればOK
        return [
            'title' => 'required|string|max:255',
            'body' => 'required|string|max:1000',
        ];
    }
}

2-3. FormRequestを使ってエラーをJSONで返す(API向け)

FormRequestの場合も、内部でリクエストを判定してAPIへのリクエストにはJSONでエラーを返すことができる。

リクエストのヘッダーにAccept:application/jsonを指定するだけなので、FormRequestのコードは同じものが使える。

2-4. JSONのエラーメッセージをカスタマイズする(API向け)

エラーメッセージの内容やレスポンスフォーマットを変えたい場合は、下のように親クラスを作って継承することもできる。

STEP1. 親クラスを作る

php artisan make:request ApiRequest

STEP2. 親クラスを編集する

app/Http/Requests/ApiRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;

abstract class ApiRequest 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
     */
    abstract public function rules();

    /**
     * Handle a failed validation attempt.
     *
     * @param  \Illuminate\Contracts\Validation\Validator $validator
     * @return void
     *
     * @throws \Illuminate\Http\Exceptions\HttpResponseException
     */
    public function failedValidation(Validator $validator)
    {
        $res = response()->json([
            'message' => 'インプットされたデータが不正です。', // messageの中身を日本語にする例
            'errors' => $validator->errors(),
        ], 422);
        throw new HttpResponseException($res);
    }
}

STEP3. 子クラスを作る

php artisan make:request Api/StoreBlogPost

STEP4. 子クラスを編集する

app/Http/Requests/Api/StoreBlogPost.php
<?php

namespace App\Http\Requests\Api;

use App\Http\Requests\ApiRequest; // 追加

class StoreBlogPost extends ApiRequest // 作成したApiRequestを継承
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules() // rulesの記述でだけでOK
    {
        return [
            'title' => 'required|string|max:255',
            'body' => 'required|string|max:1000',
        ];
    }
}

バリデーションを新しく作るときは、毎回、このApiRequestを継承したclassを作る。
これでrules()メソッドを書くだけで、カスタマイズしたフォーマットでエラーを返せるようになる。

カスタマイズされたエラーメッセージ(JSON)
{
    "message": "インプットされたデータが不正です。",
    "errors": {
        "title": [
            "titleは、必ず指定してください。"
        ]
    }
}

3. その他の必要なテクニック

個人的に、開発で必要になることが多いテクニック。

3-1. 編集リクエストのUniqueルール

登録済みのデータを編集するときに、登録時と同じようにUniqueルールを使うと実はNG。

というのも、編集対象のデータがすでにデータベースに存在しているため、一意でない判定になるから。

下のように、編集対象のデータをUniqueルールの対象外にすると、編集でUniqueが正しく動作するようになる。

// unique:[テーブル名],[カラム名],[除外するID]
'nickname' => 'unique:users,nickname,' . $this->user->id,

上は編集のrouteが/users/{user}で、モデル結合ルートを使っている例なので、注意。

例えば、編集のrouteが/users/{id}で、モデル結合ルートを使っていない場合は下。

'nickname' => 'unique:users,nickname,' . $this->id,

3-2. 編集リクエストのUniqueルール(複合ユニーク)

テーブルに複合ユニークが使われていると、より複雑になる。

salesテーブルで、yearカラムとmonthカラムの両方が同じ場合に、バリデーションエラーを出す例(複合ユニーク)。

// unique:[テーブル名],[カラム名],[除外する値],[除外するカラム名],[where制約カラム名],[where制約値]
'year' => 'unique:sales,year,' . $this->sale->id . ',id,month,' . $this->month,

詳しくは下の記事が参考になります。
【Laravel】複数カラムのユニーク制約のバリデーションを実装する方法 - Qiita

3-3. バリデーション前後に入力されたデータを変換する

バリデートする前に入力されたデータを変換したい場合は、FormRequestのprepareForValidationをオーバーライドすればOK。

電話番号のハイフンを取り除いて10〜11桁の数字であることをバリデートする例
/**
 * Prepare the data for validation.
 *
 * @return void
 */
protected function prepareForValidation()
{
    $this->merge([
        'phone_number' => str_replace("-", "", $this->phone_number),
    ]);
}

/**
 * Get the validation rules that apply to the request.
 *
 * @return array
 */
public function rules()
{
    return [
        ...
        'phone_number' => 'digits_between:10,11',
        ...
    ];
}

バリデーション後にデータ加工したい場合はpassedValidationをオーバーライドしましょう。

3-4. より複雑なバリデーションを追加する

Laravelで用意された標準ルールでは表現できない複雑なバリデーションを行いたい場合は、FormRequestのwithValidatorを使えば、独自のロジックを実装できる。

配列のそれぞれの値が、前の2つの和と同じかバリデートする例
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'array' => 'array|min:3', // 配列の要素が3個以上存在する
        ];
    }

    /**
     * Configure the validator instance.
     *
     * @param  \Illuminate\Validation\Validator  $validator
     * @return void
     */
    public function withValidator($validator) // ここでバリデーションのロジックを定義
    {
        $validator->after(function ($validator) {
            $arr = $this->array;
            for ($i = 2; $i < count($arr); $i++) {
                $sum = $arr[$i - 1] + $arr[$i - 2];
                if ($arr[$i] !== $sum) {
                    // バリデーションエラーを出している
                    $validator->errors()->add('array', 'Something is wrong with this array!');
                    break;
                }
            }
        });
    }

4. アプリ全体で使えるルールの追加・カスタマイズ

用意されたバリデーションルール以外に、アプリケーション全体で使えるルールを追加することもできる。

4-1. ルールを追加する(カタカナに限定するルール)

氏名のフリガナをカタカナで入力してもらいたいときなど、カタカナルールを作ってみる。

方法はいろいろあるが、ここでは公式ドキュメントにある方法(Validator::extend)を使う。

STEP1. ルールをプロバイダに登録

アプリ全体で使えるように、app/Providers/AppServiceProvider.phpを下のように編集。

app/Providers/AppServiceProvider.php

use Illuminate\Support\Facades\Validator; // 追加

// ... 省略 ...

    public function boot()
    {
        // bootメソッドの中に登録したいルールとロジックを記述する
        Validator::extend('katakana', function ($attribute, $value, $parameters, $validator) {
            return preg_match('/^[ァ-ヾー]+$/u', $value); // 正規表現でカタカナのみマッチさせている
        });
    }

STEP2. エラーメッセージを登録

エラーメッセージをresources/lang/en/validation.phpに登録します。

※ ここでは英語。日本語にする方法は後述。

resources/lang/en/validation.php

return [

// ... 省略 ...

    'katakana' => 'The :attribute must be a katakana.', // return内のどこでもいい

STEP3. ルールを使う

いつものように使えます。

'myouzi_katakana' => 'katakana|max:255'
返ってくるエラー(APIの例)
{
    "message": "リクエストデータが不正です。",
    "errors": {
        "myouji_katakana": [
            "The myouji katakana must be a katakana."
        ]
    }
}

4-2. 半角英数ルール(alpha_xxx)をマルチバイト文字に対応させる

Laravelには半角英数系のルールとして、alpha,alpha_num,alpha_dashがあるが、日本語などマルチバイト文字に対応していない。

たとえば、alphaルールを指定したフィールドに日本語が入力されてリクエストされた場合、エラーが出ないので困ったことになってしまう。

これを機能させるには、下のようにValidator::resolverで標準ルールを上書きすることで対応できる。

※ 上書きするのにValidator::extendは使えない。

STEP1. 上書き用のクラスを作成する。

どこに作ってもいいが、今回はapp/Validators/CustomValidator.phpとして作成する。

作成するクラスは、Illuminate\Validation\Validatorを継承する。

app/Validators/CustomValidator.php

namespace App\Validators;

use Illuminate\Validation\Validator;

class CustomValidator extends Validator
{
    /**
     * alpah
     *
     * @param string $attribute
     * @param string $value
     * @return true
     */
    public function validateAlpha($attribute, $value)
    {
        return preg_match("/^[a-z]+$/i", $value);
    }

    /**
     * alpah_num
     *
     * @param string $attribute
     * @param string $value
     * @return true
     */
    public function validateAlphaNum($attribute, $value)
    {
        return preg_match("/^[a-z0-9]+$/i", $value);
    }

    /**
     * alpah_dash
     *
     * @param string $attribute
     * @param string $value
     * @return true
     */
    public function validateAlphaDash($attribute, $value)
    {
        return preg_match("/^[a-z0-9_-]+$/i", $value);
    }
}

STEP2. ルールをプロバイダに登録

app/Providers/AppServiceProvider.php

use App\Validators\CustomValidator; // 追加

// ... 省略 ...

    public function boot()
    {
        // 追加
        Validator::resolver(function ($translator, $data, $rules, $messages, $attributes) {
            return new CustomValidator($translator, $data, $rules, $messages, $attributes);
        });
    }

これで完了。

すでにalpha,alpha_num,alpha_dashが、それぞれ日本語などのマルチバイト文字でエラーを出すようになっている。

※ 標準ルールには半角英数系以外にも地雷があるようなので注意!
参考: 使っちゃダメ!Laravelの地雷バリデーションと標準ルールを禁止(オーバーライド)する方法 - Qiita

5. エラーメッセージのカスタマイズ

個別に変更したり、全体を変更したりできる。

5-1. デフォルトのメッセージを個別にカスタマイズする

エラーメッセージをコントローラーやFormRequestクラスで個別に変更したい場合は、下のような方法が使える。

Validatorファサードを使っている場合
$messages = [
    'required' => 'The :attribute field is required.',
];

$validator = Validator::make($input, $rules, $messages);

参考:Validatorファサード

FormRequestを使っている場合
// 追加
public function messages()
{
    return [
        'title.required' => 'A title is required',
        'body.required'  => 'A message is required',
    ];
}

参考:FormRequest

5-2. バリデーションメッセージ全体を日本語化する

GithubにLaravel-Langという世界各国の言語に対応するためのパッケージがあり、日本語のファイルも用意されている(ここ)ので、これを利用する。

STEP1. 日本語用ファイルをダウンロード

日本語のバリデーション用ファイルをダウンロードして、/resources/lang/jaフォルダを作成して設置する。

上で独自に作成したkatakanaルールに対応したい場合は、下を追加することで対応できる。

/resources/lang/ja/validation.php
'katakana' => ':attributeは、カタカナでなければなりません。', // 追加

STEP2. 設定を日本語に変更する

/config/app.phpを下のように編集。

/config/app.php
  /*
   |--------------------------------------------------------------------------
   | Application Locale Configuration
   |--------------------------------------------------------------------------
   |
   | The application locale determines the default locale that will be used
   | by the translation service provider. You are free to set this value
   | to any of the locales which will be supported by the application.
   |
   */

   'locale' => 'ja', // ここを変更

これで完了。

エラーメッセージが日本語で表示される。

エラーメッセージ(JSON)
{
    "message": "リクエストデータが不正です。",
    "errors": {
        "password": [
            "passwordには、アルファベッドのみ使用できます。"
        ]
    }
}

"passwordには、アルファベッドのみ使用できます。"のpasswordなどを日本語化したい場合には、/resources/lang/ja/validation.php内のattributes'日本語化したいキー名'=>'日本語訳'を追加していく。

/resources/lang/ja/validation.php

// ユーザー登録用の'attributes'設定例
'attributes' => [
    'email' => 'メールアドレス',
    'password' => 'パスワード',
    'password_confirmation' => 'パスワード(確認用)',
    'name' => 'お名前'
],

これで、下のようなエラーメッセージを表示することができる。

エラーメッセージ(JSON)
{
    "message": "リクエストデータが不正です。",
    "errors": {
        "password": [
            "パスワードには、アルファベッドのみ使用できます。"
        ]
    }
}

おわりに

  • バリデーション周りでだいたい困らない
  • 体系的にまとまっている

を目指したら長くなりました。

32
27
3

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
32
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?