Laravel 5.4 - 如何为同一个自定义验证规则使用多个错误消息

Mar*_*arc 10 php validation laravel 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()
    {
        //
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用这样的规则:

public function rules()
{
    return [
        'email' => 'bail|required|checkEmailPresenceAndValidity'
    ];
}
Run Code Online (Sandbox Code Playgroud)

但是,我想为每个案例设置不同的错误消息,如下所示:

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

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

但是我没有弄清楚如何在不做2条不同的规则的情况下实现这一点......
当然它可以使用多个规则,但它也会执行多个SQL查询,我真的想避免这种情况.
另外,请记住,在实际情况下,我可以在单个规则中使用2个以上的验证.

有没有人有想法?

=====
编辑1:

其实,我觉得我想要的东西,在比同样的方式工作beetweensize规则.
它们代表一条规则,但提供了多条错误消息:

'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.',
],
Run Code Online (Sandbox Code Playgroud)

Laravel检查值是表示数字,文件,字符串还是数组; 并获取正确的错误消息以供使用.
我们如何通过自定义规则实现这种目标?

Sen*_*nse 7

不幸的是,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()
    {

    }
}
Run Code Online (Sandbox Code Playgroud)

下一步是创建您的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
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,别忘了扩展你的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',
        ];
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望你能找到我的建议.


Mar*_*bae 7

如果您使用 Laravel 8 并且希望针对特定验证显示不同的错误消息,请按照以下步骤操作。

我将创建一个验证规则来检查字段是否是有效的电子邮件或有效的电话号码。它还会返回不同的错误消息。

制定自定义验证规则,例如

php artisan make:rule EmailOrPhone
Run Code Online (Sandbox Code Playgroud)

导航到规则文件夹中创建的文件,即->应用程序->规则-> EmailOrPhone.php

粘贴以下代码

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;

class EmailOrPhone implements Rule
{
    public $error_message;
    public function __construct()
    {
    }

    public function passes($attribute, $value)
    {

        $value = trim($value);
        if (is_numeric($value)){
            if (strlen($value) != 10){
                $this->error_message = "Phone number must contain 10 digits";
                return false;
            }else if (!Str::startsWith($value, '0')){
                $this->error_message = "Phone number must start with 0";
                return false;
            }else{
                return true;
            }
        }else{
            $validator = Validator::make(['email' => $value],[
                'email' => 'required|email'
            ]);

            if($validator->passes()){
               return true;
            }else{
                $this->error_message = "Please provide a valid email address";
                return false;
            }
        }
    }

    public function message()
    {
         return $this->error_message;
    }
}
Run Code Online (Sandbox Code Playgroud)

您现在可以在验证器中使用自定义验证,例如

 return Validator::make($data, [
            'firstname' => ['required', 'string', 'max:255'],
            'lastname' => ['required', 'string', 'max:255'],
            'email_phone' => ['required', 'string', 'max:255', new EmailOrPhone()],
            'password' => ['required', 'string',  'confirmed'],
        ]);
Run Code Online (Sandbox Code Playgroud)

  • 简单又优雅,我喜欢。其他!*像维京人一样把老鼠砸在地上*(然后立即后悔) (2认同)

Con*_*one 5

对自定义验证规则处理不当是为什么我抛弃laravel(嗯,这是众多原因中的一个,但它是打破骆驼背部的稻草,可以这么说).但无论如何,我有三个部分给你的答案:你不想在这个特定情况下做这个的原因,快速概述你必须处理的混乱,然后你的问题的答案,以防万一你仍然想要这样做.

重要的安全问题

管理登录的最佳安全实践规定,您应始终为登录问题返回一条通用错误消息.典型的反例是,如果您发送了"未通过我们的系统注册该电子邮件",未找到电子邮件,而使用错误密码的正确电子邮件则返回"密码错误".在您提供单独的验证消息的情况下,您可以向潜在攻击者提供有关如何更有效地指导其攻击的其他信息.因此,所有与登录相关的问题都应返回一般验证消息,无论其根本原因是什么,都会导致"无效的电子邮件/密码组合".密码恢复表单也是如此,通常会说"密码恢复说​​明已发送到该电子邮件,特殊情况下,一条验证消息就是您想要的.

laravel的麻烦

您遇到的问题是laravel验证器只返回true或false以表示是否满足规则.错误消息是单独处理的.您无法从验证器逻辑中指定验证器错误消息.我知道.这太荒谬了,而且计划不周.你所能做的只是返回true或false.您无权访问任何其他内容来帮助您,因此您的伪代码不会这样做.

(丑陋的)答案

创建自己的验证消息的最简单方法是创建自己的验证器.看起来像这样(在你的控制器内):

$validator = Validator::make($input, $rules, $messages);
Run Code Online (Sandbox Code Playgroud)

您仍然需要在启动时创建验证器(您的Valiator::Extend电话.然后您可以$rules通过将它们传递到您的自定义验证器来正常指定.最后,您可以指定您的消息.像这样,整体(在您的控制器内):

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

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

    $validator = Validator::make($request->all(), $rules, $messages);
}
Run Code Online (Sandbox Code Playgroud)

(我不记得你是否必须在$messages数组中指定每个规则.我不这么认为).当然,即使这不是很棒,因为你为$ messages传递的只是一个字符串数组(这就是它允许的全部内容).因此,您仍然无法根据用户输入轻松更改此错误消息.这一切都发生在验证器运行之前.您的目标是根据验证结果更改验证消息,但是laravel会强制您首先构建消息.因此,要真正做你想做的事,你必须调整系统的实际流量,这不是很棒.

解决方案是在控制器中使用一种方法来计算是否满足自定义验证规则.它会在您创建验证程序之前执行此操作,以便您可以向构建的验证程序发送相应的消息.然后,当您创建验证规则时,您也可以将其绑定到验证计算器的结果,只要您在控制器内移动规则定义即可.你必须确保并且不小心不小心打电话.您还必须记住,这需要将验证逻辑移出验证器之外,这非常糟糕.不幸的是,我95%肯定没有任何其他方法可以做到这一点.

我在下面有一些示例代码.它肯定有一些缺点:你的规则不再是全局的(它在控制器中定义),验证逻辑移出验证器(这违反了最不惊讶的原则),你将不得不想出一个-object缓存方案(这并不难)确保您不执行两次查询,因为验证逻辑被调用两次.重申一下,它肯定是hacky,但我相当确定这是你想用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
    }
}
Run Code Online (Sandbox Code Playgroud)

此外,值得快速注意的是,此实现依赖于$this对闭包的支持,我相信这是在PHP 5.4中添加的.如果您使用的是旧版本的PHP,则必须提供$this给闭包use.

编辑咆哮

它真正归结为laravel验证系统的设计非常精细.每个验证规则专门用于验证一件事.因此,永远不必更改给定验证器的验证消息,因此$messages(当您构建自己的验证器时)只接受纯字符串.

一般而言,粒度在应用程序设计中是一件好事,并且正确实现了SOLID原则.然而,这个特殊的实现让我发疯.我的一般编程理念是,良好的实现应该使最常见的用例非常简单,然后为不太常见的用例做好准备.在这种情况下,laravel的体系结构使最常见的用例容易,但不太常见的用例几乎是不可能的.我不能满足于这种交易.我对Laravel的总体印象是,只要你需要以laravel的方式做事,它就会很有效,但如果你因任何原因不得不开箱即用,那么它将会让你的生活变得更加困难.在你的情况下,最好的答案是可能只是直接回到那个盒子里面,即 即使这意味着进行冗余查询,也要生成两个验证器.对您的应用程序性能的实际影响可能根本不重要,但是您将获得长期可维护性以获得laravel以您想要的方式运行的影响将非常大.