Laravelのルールオブジェクトを使ってカスタムバリデーションを実装する方法

ゆーたろー

こんにちは、ゆーたろーです。

大阪のHRTechベンチャーのエンジニアです。
TypeScript/Vue.js(Nuxt.js)/Laravelを使っています。

・プログラミングスクール講師
・月1で勉強会運営
・Twitter(フォロワー4700人以上)で情報発信

など色々やっている1児のパパです。

この記事ではLaravelでバリデーションをカスタマイズして強力にする方法を解説します。

Laravelはフレームワークなので便利なバリデーション機能を色々用意してくれていますが、デフォルトで使えるものではセキュリティ的に不十分な場合があります。

パスワード等、第三者に知られてはいけない情報は登録時のバリデーションを強化することが好ましいです。

というわけで早速Laravelでのカスタムバリデーションを作成する方法を解説していきますね!

今回はこのような強いバリデーションを設定します。

半角英字(小文字)、半角英字(大文字)、半角数字を少なくとも1文字以上含む8文字以上

初めにそもそもバリデーションって何?というところから解説しますので、具体的な方法について知りたい方は『カスタムバリデーションの実装方法』に飛んでください。

目次

バージョン

Laravel6.x

そもそもバリデーションって何?

そもそもバリデーションって何?

バリデーションはとてもざっくり言うと入力値のチェックです。

Web業界用語辞典にはこのように書かれています。

バリデーション(ばりでーしょん)とは、フォームに入力された値に対する未入力チェック、文字列長チェック、型チェックなどの検証の仕組みのこと。

http://webyougo.wiki.fc2.com/wiki/%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3

入力値チェックと一言で言っても色々なチェックの仕方があります。

例えば

  • メールアドレスなら@が入っているか?
  • パスワードなら半角英字と全角英字がそれぞれ1文字以上含まれているか?
  • 名前なら30文字以内になっているか?

このような入力値が妥当かどうかのルールを設けてチェックすることです。

アプリケーションの開発では主に

  • ユーザー登録
  • ログイン
  • コンテンツの新規登録

などで用いられます。

このバリデーション(=入力値のチェック)をLaravelでカスタマイズする方法をこれから解説します。

Laravelで使用可能なバリデーションルール

Laravelには使用可能なバリデーションルールがたくさん用意されているので、大体のルールは適用できると思います。

こちらの公式ドキュメントの日本語訳を見れば一覧で確認できます。

カスタムバリデーションの実装方法

公式ドキュメントの日本語訳ReadDoubleではカスタムバリデーションの実装方法は2つ紹介されています。

  • ルールオブジェクトの使用
  • Validatorファサードのextendメソッドの使用

この記事では前者のルールオブジェクトの使用について説明します。

今回は一例としてユーザー登録時のパスワードのバリデーションを強化してみます。

Laravelのデフォルトのユーザー登録時のパスワードバリデーション

デフォルトではとても簡素(=弱い)です。

<?php
// 略

class RegisterController extends Controller
{
    // 略
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
    }
    // 略
}

'password'のバリデーション内容はこちらです。

バリデーションルール内容
required必須(入力されているか)
string文字列であるか
min:88文字以上であるか
confirmed確認用のパスワードと同じであるか
Laravelのデフォルトのユーザー登録時のパスワードバリデーション

このレベルのバリデーションだと11111111aaaaaaaaなど予測しやすいパスワードの登録が可能なので、アプリケーションの脆弱性となります。

これはいかん!ということで

半角英字(小文字)、半角英字(大文字)、半角数字を少なくとも1文字以上含む8文字以上

というバリデーションにします。

ルールオブジェクトの作成

まずはルールオブジェクトを作成します。

artisanファイルが存在するディレクトリ(=プロジェクト直下)にいることを確認して、ターミナルで以下のコマンドを実行します。

$ php artisan make:rule CustomPasswordValidation

※ルールの名前(CustomPasswordValidation)任意です。

ルールオブジェクトの概要

ルールオブジェクトについて公式ドキュメント通りにざっくり説明します。

先ほどのコマンドで生成されたファイルを見ます。

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class CustomPasswordValidation implements Rule
{
    /**
     * バリデーションの成功を判定
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        return strtoupper($value) === $value;
    }

    /**
     * バリデーションエラーメッセージの取得
     *
     * @return string
     */
    public function message()
    {
        return 'The :attribute must be uppercase.';
    }
}

ルールオブジェクトは2つのメソッドを含みます。passesとmessageです。passesメソッドは属性の値と名前を受け取り、その属性値が有効であればtrue、無効であればfalseを返します。messageメソッドは、バリデーション失敗時に使用する、バリデーションエラーメッセージを返します。

https://readouble.com/laravel/6.x/ja/validation.html#available-validation-rules#using-rule-objects

ルールオブジェクトの概要をまとめるとこのような感じです。

メソッド名書く処理返り値
passesバリデーションルールtrue(成功) or false(失敗)
messageエラーメッセージpassesメソッドの返り値がfalseの時に使うエラーメッセージ
ルールオブジェクトの概要

このルールオブジェクトを使ってカスタムバリデーションを作成します。

カスタムバリデーションの実装

実際にパスワードに半角英字(小文字)、半角英字(大文字)、半角数字を1文字以上含む8文字以上のバリデーションを定義していきます。

完成形

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class PasswordValidation implements Rule
{
    //最小文字数
    private const MIN_CHARACTER = 8;
    //最大文字数
    private const MAX_CHARACTER = null;
    //1文字以上の半角英字(大文字)
    private const INCLUDE_LESS_THAN_ONE_UPPER_LETTER = true;
    //1文字以上の半角英字(小文字)
    private const INCLUDE_LESS_THAN_ONE_LOWER_LETTER = true;
    //1文字以上の半角数字
    private const INCLUDE_LESS_THAN_ONE_NUMBER = true;


    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        $regexOfValidation = "[a-zA-z\d]";
        // 半角英字(小文字)1文字以上
        if (self::INCLUDE_LESS_THAN_ONE_LOWER_LETTER) {
            $regexOfValidation = "(?=.*?[a-z])" . $regexOfValidation;
        }
        // 半角英字(大文字)1文字以上
        if (self::INCLUDE_LESS_THAN_ONE_UPPER_LETTER) {
            $regexOfValidation = "(?=.*?[A-Z])" . $regexOfValidation;
        }
        // 半角数字1文字以上
        if (self::INCLUDE_LESS_THAN_ONE_NUMBER) {
            $regexOfValidation = "(?=.*?\d)" . $regexOfValidation;
        }
        // 最大、最小文字数
        if (self::MAX_CHARACTER || self::MIN_CHARACTER) {
            $regexOfValidation = $regexOfValidation . "{" . self::MIN_CHARACTER . "," . self::MAX_CHARACTER . "}";
        }
        $regexOfValidation = "/\A{$regexOfValidation}+\z/";

        return preg_match($regexOfValidation, $value);
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        $validationMessage = 'パスワードは';
        $validationOneLetterMessage = [];
        if (self::INCLUDE_LESS_THAN_ONE_LOWER_LETTER) {
            $validationOneLetterMessage[] = '半角英字(小文字)';
        }
        if (self::INCLUDE_LESS_THAN_ONE_UPPER_LETTER) {
            $validationOneLetterMessage[] = '半角英字(大文字)';
        }
        if (self::INCLUDE_LESS_THAN_ONE_NUMBER) {
            $validationOneLetterMessage[] = '半角数字';
        }
        if ($validationOneLetterMessage) {
            $validationMessage .= implode('、', $validationOneLetterMessage) . 'を1文字以上含む';
        }
        if (self::MIN_CHARACTER) {
            $validationMessage .= self::MIN_CHARACTER . "文字以上";
        }
        if (self::MAX_CHARACTER) {
            $validationMessage .= self::MAX_CHARACTER . "文字以下";
        }
        $validationMessage .= 'で入力してください';

        return $validationMessage;
    }
}

長いですね…(笑)

解説していきます!

クラス定数の定義

クラス定数を定義します。

クラス定数についてはこちらの記事でも触れています。

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class PasswordValidation implements Rule
{
    // クラス定数を定義
    // 最小文字数
    private const MIN_CHARACTER = 8;
    // 最大文字数
    private const MAX_CHARACTER = null;
    // 1文字以上の半角英字(大文字)
    private const INCLUDE_LESS_THAN_ONE_UPPER_LETTER = true;
    // 1文字以上の半角英字(小文字)
    private const INCLUDE_LESS_THAN_ONE_LOWER_LETTER = true;
    // 1文字以上の半角数字
    private const INCLUDE_LESS_THAN_ONE_NUMBER = true;

    // 略
}

設定したクラス定数は以下の通りです。

クラス変数バリデーションルール今回の設定
MIN_CHARACTER最小文字数8文字以上
MAX_CHARACTER最大文字数指定なし(null)
INCLUDE_LESS_THAN_ONE_UPPER_LETTER1文字以上の半角英字(大文字)チェックする(true)
INCLUDE_LESS_THAN_ONE_LOWER_LETTER1文字以上の半角英字(小文字)チェックする(true)
INCLUDE_LESS_THAN_ONE_NUMBER1文字以上の半角数字チェックする(true)
設定したクラス定数

このように1つ1つの項目に対してわざわざクラス定数を定義している理由は、今後もしバリデーションのルール(強度)を変えたくなった時に簡単に変えることでできるようにするためです。

例えば

  • 最大文字数を指定したい → MAX_CHARACTERにnullではなく値を設定する
  • 1文字以上の半角数字を入れなくてもユーザー登録可能にしたい → INCLUDE_LESS_THAN_ONE_NUMBERfalseにする

などのようにクラス定数を修正するだけでバリデーションルールをすぐに変えることができます。

バリデーションルールの作成(passesメソッド)

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class PasswordValidation implements Rule
{
    // 略

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        $regexOfValidation = "[a-zA-z\d]";
        // 半角英字(小文字)1文字以上
        if (self::INCLUDE_LESS_THAN_ONE_LOWER_LETTER) {
            $regexOfValidation = "(?=.*?[a-z])" . $regexOfValidation;
        }
        // 半角英字(大文字)1文字以上
        if (self::INCLUDE_LESS_THAN_ONE_UPPER_LETTER) {
            $regexOfValidation = "(?=.*?[A-Z])" . $regexOfValidation;
        }
        // 半角数字1文字以上
        if (self::INCLUDE_LESS_THAN_ONE_NUMBER) {
            $regexOfValidation = "(?=.*?\d)" . $regexOfValidation;
        }
        // 最大、最小文字数
        if (self::MAX_CHARACTER || self::MIN_CHARACTER) {
            $regexOfValidation = $regexOfValidation . "{" . self::MIN_CHARACTER . "," . self::MAX_CHARACTER . "}";
        }
        $regexOfValidation = "/\A{$regexOfValidation}+\z/";

        return preg_match($regexOfValidation, $value);
    }
    
    // 略
}

各バリデーションルールごとに変数$regexOfValidationに正規表現を追加しています。

最後のこちらのコードで正規表現にマッチするかどうか(バリデーションOK:true、バリデーションNG=false)を返しています。

return preg_match($regexOfValidation, $value);

今回のバリデーションの正規表現で難しいのはこの3つだと思います。

  • 半角英字(小文字)1文字以上:(?=.*?[a-z])
  • 半角英字(大文字)1文字以上:(?=.*?[A-Z])
  • 半角数字1文字以上:(?=.*?\d)

上記正規表現のそれぞれのパーツに関してまとめると下表のようになります。

正規表現内容
?=肯定先読み
.改行以外の任意の1文字
*?直前の文字が 0回以上 繰り返す
[a-z]a〜zの半角英数字
それぞれの正規表現の内容

この正規表現について詳しく知りたい方はこちらの記事を参考にしてみてください。

最終的な$regexOfValidationの中身はこのようになります。もはや暗号ですね…。

/\A(?=.*?\d)(?=.*?[A-Z])(?=.*?[a-z])[a-zA-z\d]{8,}+\z/

エラーメッセージの作成(messageメソッド)

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class PasswordValidation implements Rule
{
    // 略

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        $validationMessage = 'パスワードは';
        $validationOneLetterMessage = [];
        if (self::INCLUDE_LESS_THAN_ONE_LOWER_LETTER) {
            $validationOneLetterMessage[] = '半角英字(小文字)';
        }
        if (self::INCLUDE_LESS_THAN_ONE_UPPER_LETTER) {
            $validationOneLetterMessage[] = '半角英字(大文字)';
        }
        if (self::INCLUDE_LESS_THAN_ONE_NUMBER) {
            $validationOneLetterMessage[] = '半角数字';
        }
        if ($validationOneLetterMessage) {
            $validationMessage .= implode('、', $validationOneLetterMessage) . 'を1文字以上含む';
        }
        if (self::MIN_CHARACTER) {
            $validationMessage .= self::MIN_CHARACTER . "文字以上";
        }
        if (self::MAX_CHARACTER) {
            $validationMessage .= self::MAX_CHARACTER . "文字以下";
        }
        $validationMessage .= 'で入力してください';

        return $validationMessage;
    }
}

エラーメッセージの作り方はそこまで難しくないので詳しい解説は割愛しますが、

$validationMessage .= self::MIN_CHARACTER . "文字以上";

この.=その変数自身への文字列の連結を意味します。

// 普通に書く
$sampleText = sampleText . '追加する文字';

// 省略形
$sampleText .= '追加する文字';

// この書き方と同じ
$num = $num + 2
$num += 2;

最終的に返るバリデーションメッセージが入った変数$validationMessage)は

パスワードは、半角英字(小文字)、半角英字(大文字)、半角数字を1文字以上含む8文字以上で入力してください

です。

これでカスタムバリデーションの作成は完了です。

カスタムバリデーションの有効化

作成したカスタムバリデーションを有効化(適用)します。

これをしなければ意味がありません。

というわけでユーザー登録時のバリデーションに追加します。

<?php
// 略

class RegisterController extends Controller
{
    // 略
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', new CustomPasswordValidation, 'confirmed'],
        ]);
    }
    // 略
}

このように追加した入力項目にnew CustomPasswordValidationを追加すればOKです。

'password' => ['required', 'string', new CustomPasswordValidation, 'confirmed'],

最後に

Laravelでカスタムバリデーションを実装する方法を解説しました。

実装方法自体は結構シンプルですが、やはり難しいのは正規表現ですね…。

記事を書いている僕も正規表現はまだまだ上手く使いこなせてないので少しずつ使いこなせるように勉強しようと思いました。

バリデーションルールをカスタマイズして強力にする方法は汎用性が高いと思いますので、ぜひマスターしましょう。

“頭おかしい”プログラミングスクール『やんばるエキスパート』

内定者続出のエンジニア転職特化オンラインコミュニティ『転職クエスト』

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!

この記事を書いた人

上場グループのHRTechベンチャーで働くWebエンジニアです。
新卒で入社した大手重工メーカを4年で退職し、2020年4月からエンジニアとキャリアチェンジしました。

仕事ではTypeScript/Vue.js(Nuxt.js)/Laravelを主に使っています。

プログラミングスクールの講師やデザイン関連のお仕事もさせてもらっています。

神戸で「つながる勉強会」という勉強会を月1で運営しています。
https://tsunagaru-kobe.connpass.com/

お仕事のご依頼、ご相談はお問い合わせページもしくはTwitterのDMからお願いします。

コメント

コメントする

目次
閉じる