web-dev-qa-db-ja.com

Laravel 5.4-同じカスタム検証ルールに対して複数のエラーメッセージを使用する方法

コードを再利用するために、私はValidatorServiceProviderという名前のファイルに独自のバリデータールールを作成しました。

class ValidatorServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Validator::extend('checkEmailPresenceAndValidity', function ($attribute, $value, $parameters, $validator) {
            $user = User::where('email', $value)->first();

            // Email has not been found
            if (! $user) {
                return false;
            }

            // Email has not been validated
            if (! $user->valid_email) {
                return false;
            }

            return true;
        });
    }

    public function register()
    {
        //
    }
}

そして、私はこのルールを次のように使用します:

public function rules()
{
    return [
        'email' => 'bail|required|checkEmailPresenceAndValidity'
    ];
}

しかし、私はそれぞれのケースごとに異なるエラーメッセージを設定したいと思います、このようなもの:

if (! $user) {
    $WHATEVER_INST->error_message = 'email not found';
    return false;
}

if (! $user->valid_email) {
    $WHATEVER_INST->error_message = 'invalid email';
    return false;
}

しかし、私は2つの異なるルールをせずにこれを達成する方法を理解していません...
もちろん、それは複数のルールで動作する可能性がありますが、複数のSQLクエリも実行するので、私はそれを本当に避けたいです。
また、実際のケースでは、1つのルールでこれらのような検証を2つ以上行うことができることに注意してください。

誰かがアイデアを持っていますか?

=====
編集1:

実際、beetweensizeルールと同じように機能するものが欲しいと思います。
これらは1つのルールを表しますが、複数のエラーメッセージを提供します。

'size'                 => [
    'numeric' => 'The :attribute must be :size.',
    'file'    => 'The :attribute must be :size kilobytes.',
    'string'  => 'The :attribute must be :size characters.',
    'array'   => 'The :attribute must contain :size items.',
],

Laravelは、値が数値、ファイル、文字列、または配列を表すかどうかをチェックします。使用する適切なエラーメッセージを取得します。
カスタムルールを使用してこの種のことを実現するにはどうすればよいですか?

11
Marc

残念ながらLaravelは現在、属性params配列から直接検証ルールを追加して呼び出すための具体的な方法を提供していません。しかし、これはTraitRequestの使用に基づく潜在的でわかりやすいソリューションを除外するものではありません。

たとえば、以下の私の解決策を見つけてください。

まず最初に、フォームが処理されてフォームリクエストを抽象クラスで処理するのを待ちます。あなたがする必要があるのは、現在のValidatorインスタンスを取得し、関連するエラーがある場合にそれ以上の検証を行わないようにすることです。それ以外の場合は、バリデーターインスタンスを格納し、後で作成するカスタムユーザー検証ルール関数を呼び出します。

<?php

namespace App\Custom\Validation;

use \Illuminate\Foundation\Http\FormRequest;

abstract class MyCustomFormRequest extends FormRequest
{
    /** @var \Illuminate\Support\Facades\Validator */
    protected $v = null;

    protected function getValidatorInstance()
    {
        return parent::getValidatorInstance()->after(function ($validator) {
            if ($validator->errors()->all()) {
                // Stop doing further validations
                return;
            }
            $this->v = $validator;
            $this->next();
        });
    }

    /**
     * Add custom post-validation rules
     */
    protected function next()
    {

    }
}

次のステップは、Traitを作成することです。これは、現在のバリデーターインスタンスのおかげで入力を検証し、表示する正しいエラーメッセージを処理する方法を提供します。

<?php

namespace App\Custom\Validation;

trait CustomUserValidations
{
    protected function validateUserEmailValidity($emailField)
    {
        $email = $this->input($emailField);

        $user = \App\User::where('email', $email)->first();

        if (! $user) {
            return $this->v->errors()->add($emailField, 'Email not found');
        }
        if (! $user->valid_email) {
            return $this->v->errors()->add($emailField, 'Email not valid');
        }

        // MORE VALIDATION POSSIBLE HERE
        // YOU CAN ADD AS MORE AS YOU WANT
        // ...
    }
}

最後に、MyCustomFormRequestを拡張することを忘れないでください。たとえば、php artisan make:request CreateUserRequest、これを行う簡単な方法を次に示します。

<?php

namespace App\Http\Requests;

use App\Custom\Validation\MyCustomFormRequest;
use App\Custom\Validation\CustomUserValidations;

class CreateUserRequest extends MyCustomFormRequest
{
    use CustomUserValidations;

    /**
     * Add custom post-validation rules
     */
    public function next()
    {
        $this->validateUserEmailValidity('email');
    }

    /**
     * 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 [
            'email' => 'bail|required|email|max:255|unique:users',
            'password' => 'bail|required',
            'name' => 'bail|required|max:255',
            'first_name' => 'bail|required|max:255',
        ];
    }
}

私の提案にあなたの道が見つかることを願っています。

7
Sense

私がlaravelを捨てたのは、カスタム検証ルールの扱いが悪いためです(まあ、それは多くの理由の1つでしたが、いわばラクダの背中を壊したのはストローでした)。しかし、とにかく、私はあなたに3つの部分で答えます:この特定のケースでこれを実行したくない理由、対処しなければならない混乱の概要、そしてケースの質問への回答あなたはまだそれをやりたいのです。

重要なセキュリティ上の懸念

ログインを管理するための最良のセキュリティプラクティスでは、ログインの問題に対して常に1つの一般的なエラーメッセージを返す必要があります。典型的な反例は、メールが見つからない場合に「そのメールはシステムに登録されていません」を返し、間違ったパスワードを含む正しいメールの場合は「間違ったパスワード」を返した場合です。個別の検証メッセージを提供する場合は、潜在的な攻撃者に、攻撃をより効果的に誘導する方法に関する追加情報を提供します。その結果、ログインに関連するすべての問題は、根本的な原因に関係なく、「無効な電子メール/パスワードの組み合わせ」の影響に関係する一般的な検証メッセージを返すはずです。同じことが、パスワード回復フォームにも当てはまり、「システムにパスワードが存在する場合、その電子メールにパスワード回復の指示が送信されました」のようなことがよくあります。それ以外の場合、システムに登録されている電子メールアドレスを知る方法を攻撃者(およびその他の人)に与えるため、追加の攻撃ベクトルが公開される可能性があります。したがって、この特定のケースでは、1つの検証メッセージが必要です。

laravelのトラブル

あなたが遭遇している問題は、laravelバリデーターが単純にtrueまたはfalseを返し、ルールが満たされているかどうかを示すことです。エラーメッセージは個別に処理されます。バリデータロジック内からバリデータエラーメッセージを具体的に指定することはできません。知っている。それはばかげており、計画も不十分です。できることは、trueまたはfalseを返すことだけです。あなたはあなたを助けるために他のものにアクセスすることができないので、あなたの疑似コードはそれをするつもりはありません。

(醜い)答え

独自の検証メッセージを作成する最も簡単な方法は、 独自のバリデータを作成 することです。これは次のようになります(コントローラー内):

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

起動時にバリデーター(Valiator::Extend呼び出し)を作成する必要があります。カスタムバリデーターに渡すことで、通常$rulesを指定できます。最後に、メッセージを指定できます。このようなもの、全体(コントローラー内):

public function login( Request $request )
{
    $rules = [
        'email' => 'bail|required|checkEmailPresenceAndValidity'
    ]

    $messages = [
        'checkEmailPresenceAndValidity' => 'Invalid email.',
    ];

    $validator = Validator::make($request->all(), $rules, $messages);
}

$messages配列に各ルールを指定する必要があるかどうか覚えていません。そうは思いません)。もちろん、これはそれほど素晴らしいものではありません。$ messagesに渡すのは、単に文字列の配列だからです(それが許可されているすべてのことです)。その結果、ユーザー入力に応じてこのエラーメッセージを簡単に変更することはできません。これはすべて、バリデーターが実行される前にも発生します。あなたの目標は、検証結果に応じて検証メッセージを変更することですが、laravelは最初にメッセージを作成することを強制します。その結果、本当にやりたいことを行うには、システムの実際のフローを調整する必要がありますが、これはそれほど素晴らしいことではありません。

解決策は、カスタム検証ルールが満たされているかどうかを計算するメソッドをコントローラーに含めることです。バリデーターを作成する前にこれを行うので、作成したバリデーターに適切なメッセージを送信できます。次に、検証ルールを作成するときに、ルール定義をコントローラー内に移動する限り、それを検証計算機の結果にバインドすることもできます。あなたが確認する必要があるのは、誤って故障したものを誤って呼び出さないことです。また、検証ロジックを検証ツールの外に移動する必要があることにも注意してください。これはかなりハッキーです。残念ながら、私は95%確実にこれを行う他の方法がないと確信しています。

以下のサンプルコードがあります。確かに欠点があります:ルールはグローバルではなくなり(コントローラーで定義されます)、検証ロジックはバリデーターの外に移動します(これは最小の驚きの原則に違反します)。 -検証ロジックが2回呼び出されるため、クエリを2回実行しないようにするためのオブジェクトキャッシュスキーム(難しいことではありません)。繰り返しますが、これは間違いなくハックですが、これがlaravelでやりたいことを行う唯一の方法であると私はかなり確信しています。これを整理するより良い方法があるかもしれませんが、これは少なくともあなたが何をする必要があるかについての考えをあなたに与えるはずです。

<?php
namespace App\Http\Controllers;

use User;
use Validator;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class LoginController extends Controller
{
    public function __construct() {
        Validator::extend('checkEmailPresenceAndValidity', function ($attribute, $value, $parameters, $validator) {

            return $this->checkLogin( $value ) === true ? true : false;

        });
    }

    public function checkLogin( $email ) {
        $user = User::where('email', $email)->first();

        // Email has not been found
        if (! $user) {
            return 'not found';
        }

        // Email has not been validated
        if (! $user->valid_email) {
            return 'invalid';
        }

        return true;
    }

    public function login( Request $request ) {

        $rules = [
            'email' => 'bail|required|checkEmailPresenceAndValidity'
        ]

        $hasError = $this->checkLogin( $request->email );
        if ( $hasError === 'not found' )
            $message = "That email wasn't found";
        elseif ( $hasError === 'invalid' )
            $message = "That is an invalid email";
        else
            $message = "Something was wrong with your request";


        $messages = [
            'checkEmailPresenceAndValidity' => $message,
        ];

        $validator = Validator::make($request->all(), $rules, $messages);

        if ($validator->fails()) {
            // do something and redirect/exit
        }

        // process successful form here
    }
}

また、この実装はクロージャーの$thisサポートに依存していることにも注意してください。これはPHP 5.4で追加されたと思います。古いバージョンのPHPを使用している場合は、useを使用してクロージャーに$thisを提供する必要があります。

rant to edit

結局のところ、laravel検証システムは非常に細かく設計されているということです。各検証ルールは、具体的には1つのことだけを検証することを前提としています。その結果、特定のバリデーターの検証メッセージを変更する必要はありません。そのため、$messages(独自のバリデーターを作成する場合)がプレーンな文字列しか受け入れないのはなぜですか。

一般に、細分性はアプリケーションの設計において良いことであり、SOLID原則の適切な実装が求められるものです。しかし、この特定の実装は私を夢中にさせます。私の一般的なプログラミングの哲学は、優れた実装は最も一般的なユースケースを非常に簡単にし、あまり一般的でないユースケースから邪魔にならないようにすることです。この場合、laravelのアーキテクチャにより、最も一般的なユースケースは簡単になりますが、あまり一般的でないユースケースはほとんど不可能になります。私はそのトレードオフで大丈夫ではありません。 Laravelについての私の一般的な印象は、laravelの方法を実行する必要がある限りはうまく機能するということでしたが、何らかの理由で箱から出なければならない場合は、積極的にあなたの人生をより困難にするため。あなたの場合、最善の答えは、おそらくそのボックスのすぐ後ろに戻ることです。つまり、冗長なクエリを作成することを意味する場合でも、2つのバリデーターを作成します。アプリケーションのパフォーマンスへの実際の影響はおそらく問題にはなりませんが、長期的な保守性がlaravelを期待どおりに動作させるために必要な影響は非常に大きくなります。

5
Conor Mancone

他の提案の代わりに、Validator::replacer('yourRule', function())に加えてValidator::extend('yourRule', function(...))を呼び出して、バリデーターを拡張するサービスプロバイダークラスで検証エラーの原因を追跡することもできます。このようにして、デフォルトのエラーメッセージを別のエラーメッセージに完全に置き換えることができます。

docs によると、replacer()はエラーメッセージが返される前にプレースホルダーを置換するためのものであるため、厳密にはそうではありませんが、十分に近いです。もちろん、これは見苦しい(やっかいな)回避策のようなものですが、おそらくうまくいくでしょう(少なくとも私には一見するとうまくいくようです)。

ただし、おそらく自動的に返されないようにしたい場合は、配列でこれらの失敗の原因を追跡する必要があることを覚えておいてくださいカスタム検証ルールに失敗したすべてのフィールドのメッセージ。

1
Rimas Kudelis

サイズ検証のエラーメッセージはどこにありますか?

検証ルールをIlluminate\Validation\ConcernsValidatesAttributesトレイトで検索すると、すべての関数がブール値(サイズ検証も)を返します。

protected function validateSize($attribute, $value, $parameters)
{
    $this->requireParameterCount(1, $parameters, 'size');

    return $this->getSize($attribute, $value) == $parameters[0];
}

あなたが見つけたものはこの部分に属します:

$keys = ["{$attribute}.{$lowerRule}", $lowerRule];

この場合、lowerRule値を設定して出力をフォーマットするためだけのもので、laravelはサイズ検証などの特別な場合に処理します。

    // If the rule being validated is a "size" rule, we will need to gather the
    // specific error message for the type of attribute being validated such
    // as a number, file or string which all have different message types.
    elseif (in_array($rule, $this->sizeRules)) {
        return $this->getSizeMessage($attribute, $rule);
    }

したがって、検証ルールがブール値を返す必要がある限り、複数のエラーメッセージを返す方法はありません。それ以外の場合は、検証ルールの一部を書き直す必要があります。

存在する検証を使用できる検証の問題に対するアプローチ:

public function rules()
{
    return [
        'email' => ['bail', 'required',  Rule::exists('users')->where(function($query) {
        return $query->where('valid_email', 1);
    })]
    ];
}   

したがって、必要なのは2検証ルールが存在することです。 laravelからの既存のものを使用してメールが設定されているかどうかを確認し、カスタムのものを使用してアカウントが検証されているかどうかを確認することをお勧めします。

0
cre8