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

时间:2017-06-22 09:57:08

标签: php laravel validation 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查询,我真的想避免这种情况 另外,请记住,在实际情况下,我可以在单个规则中进行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.',
],

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

5 个答案:

答案 0 :(得分: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()
    {

    }
}

下一步是创建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',
        ];
    }
}

我希望你能按照我的建议找到自己的方式。

答案 1 :(得分:5)

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

重要的安全问题

管理登录的最佳安全措施要求您应始终为登录问题返回一条通用错误消息。典型的反例如果你回来了#34;那封电子邮件没有在我们的系统中注册&#34;对于未找到的电子邮件和&#34;错误的密码&#34;使用错误的密码输入正确的电子邮件。在您提供单独的验证消息的情况下,您可以向潜在攻击者提供有关如何更有效地指导其攻击的其他信息。因此,所有与登录相关的问题都应返回一般验证消息,无论其根本原因是什么,都会导致&#34;无效的电子邮件/密码组合&#34;。对于密码恢复表单也是如此,如果密码恢复表单存在于我们的系统中,它通常会说“&#34;密码恢复指令已发送到该电子邮件”。否则,您可以让攻击者(和其他人)了解您的系统中注册了哪些电子邮件地址,这可能会暴露其他攻击媒介。因此,在特定的情况下,您需要一条验证消息。

laravel的问题

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

(丑陋)回答

创建自己的验证消息的最简单方法是create your own validator。看起来像这样(在你的控制器里面):

$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

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

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

我在下面有一些示例代码。它肯定有一些缺点:你的规则不再是全局的(它在控制器中定义),验证逻辑移出验证器(这违反了最不惊讶的原则),你将不得不想出一个 - 对象缓存方案(这并不难)确保您不会执行两次查询,因为验证逻辑被调用两次。重申一下,这肯定是hacky,但我相当肯定这是你想用laravel做的事情的唯一方法。可能有更好的方法来组织这个,但这至少应该让你知道你需要做些什么。

$this

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

编辑以咆哮

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

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

答案 2 :(得分:1)

除其他建议外,我认为除了Breakpoint will not currently be hit. No symbols have been loaded for this document之外,您还可以调用Validator::replacer('yourRule', function()),并跟踪导致验证程序扩展的服务提供程序类中导致验证失败的原因。这样,您就可以将默认错误消息完全替换为另一条错误消息。

根据docsValidator::extend('yourRule', function(...))旨在在返回错误消息之前在错误消息中进行占位符替换,因此,尽管并非严格如此,但它足够接近。当然,这是一个丑陋的解决方法,但它可能会起作用(至少乍一看似乎对我有用)。

要记住的一件事是,如果您想避免针对自定义失败的所有字段自动返回相同的消息,您将可能必须在数组中跟踪这些失败原因验证规则。

答案 3 :(得分:1)

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

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

制作自定义验证规则,如

php artisan make:rule EmailOrPhone

导航到 Rules 文件夹中创建的文件,即 Root->App->Rules->< strong>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;
    }
}

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

 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'],
        ]);

答案 4 :(得分:0)

您在哪里找到了尺寸验证的错误消息?

我查了一下验证规则 Illuminate\Validation\ConcernsValidatesAttributes trait和所有函数都返回bool值(也是大小验证)。

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);
    }

因此,只要验证规则必须返回bool值,就无法返回多个错误消息。否则你必须重写验证规则的某一方。

您可以使用存在验证验证问题的方法:

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

因此您需要 2 存在验证规则。我建议使用laravel中现有的一个来检查是否设置了电子邮件,并使用自定义电子邮件来检查帐户是否经过验证。