Laravel 5.4 - 如何为同一个自定义验证规则使用多个错误消息
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/44696091/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Laravel 5.4 - How to use multiple error messages for the same custom validation rule
提问by Marc
In order to reuse code, I created my own validator rule in a file named ValidatorServiceProvider:
为了重用代码,我在名为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()
{
//
}
}
And I use this rule like this :
我像这样使用这个规则:
public function rules()
{
return [
'email' => 'bail|required|checkEmailPresenceAndValidity'
];
}
But, I want to set different error messages for each case, something like this :
但是,我想为每种情况设置不同的错误消息,如下所示:
if (! $user) {
$WHATEVER_INST->error_message = 'email not found';
return false;
}
if (! $user->valid_email) {
$WHATEVER_INST->error_message = 'invalid email';
return false;
}
But I don't figure out how to achieve this without doing 2 differents rules ...
Of course it could work with multiple rules but it will also perform multiple SQL queries, and I really want to avoid that.
Also, keep in mind that in real case I could have more than 2 validations like theses in a single rule.
但是我不知道如何在不执行 2 个不同规则的情况下实现这一点......
当然它可以使用多个规则,但它也会执行多个 SQL 查询,我真的想避免这种情况。
另外,请记住,在实际情况下,我可以在单个规则中进行 2 个以上的验证,例如论文。
Does anyone have an idea ?
有没有人有想法?
=====
EDIT 1 :
=====
编辑 1 :
Actually, I think that I want something that works in a similar way than the beetween
or size
rules.
They represent one single rule, but provide multiple error messages :
实际上,我认为我想要一些与beetween
orsize
规则类似的东西。
它们代表一个规则,但提供多个错误消息:
'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 checks if the value represents a numeric, a file, a string or an array ; and gets the right error message to use.
How do we achieve this kind of thing with custom rule ?
Laravel 检查该值是否代表数字、文件、字符串或数组;并获取要使用的正确错误消息。
我们如何使用自定义规则来实现这种事情?
采纳答案by Sense
Unfortunately Laravel doesn't currently provide a concrete way to add and call your validation rule directly from your attribute params array. But that's does not exclude a potential and friendly solution based on Trait
and Request
usage.
不幸的是,Laravel 目前没有提供一种具体的方法来直接从你的属性 params 数组中添加和调用你的验证规则。但这并不排除基于Trait
和Request
使用的潜在和友好的解决方案。
Please find below my solution for example.
例如,请在下面找到我的解决方案。
First thing is to wait for the form to be processed to handle the form request ourselve with an abstract class. What you need to do is to get the current Validator
instance and prevent it from doing further validations if there's any relevant error. Otherwise, you'll store the validator instance and call your custom user validation rule function that you'll create later :
首先是等待表单被处理以使用抽象类来处理我们自己的表单请求。您需要做的是获取当前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()
{
}
}
The next step is to create your Trait
which will provide the way to validate your inputs thanks to the current validator instance and handle the correct error message you want to display :
下一步是创建您的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
// ...
}
}
Finally, do not forget to extend your MyCustomFormRequest
. For example, after your php artisan make:request CreateUserRequest
, here's the easy way to do so :
最后,不要忘记扩展您的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',
];
}
}
I hope that you'll find your way in what I suggest.
我希望你能按照我的建议找到自己的方式。
回答by Conor Mancone
Poor handling of custom validation rules is why I ditched laravel (well, it was one of many reasons, but it was the straw that broke the camel's back, so to speak). But anyway, I have a three part answer for you: a reason why you don't want to do this in this specific case, a quick general overview of the mess you have to deal with, and then the answer to your question in case you still want to do it.
自定义验证规则处理不当是我放弃 laravel 的原因(嗯,这是众多原因之一,但可以说是压垮骆驼的最后一根稻草)。但无论如何,我有一个三部分的答案给你:一个你不想在这个特定情况下这样做的原因,一个你必须处理的混乱的快速概述,然后是你的问题的答案,以防万一你还是想做。
Important security concern
重要的安全问题
Best security practices for managing logins dictate that you should always return one generic error message for login problems. The quintessential counter-example would be if you returned "That email is not registered with our system" for an email-not-found and "Wrong password" for a correct email with the wrong password. In the case where you give separate validation messages, you give potential attackers additional information about how to more effectively direct their attacks. As a result, all login-related issues should return a generic validation message, regardless of the underlying cause, something to the effect of "Invalid email/password combination". The same is true for password recovery forms, which often say something like, "Password recovery instructions have been sent to that email, if it is present in our system". Otherwise you give attackers (and others) a way to know what email addresses are registered with your system, and that can expose additional attack vectors. So in this particularcase, one validation message is what you want.
管理登录的最佳安全实践规定,您应该始终针对登录问题返回一条通用错误消息。典型的反例是,如果您在未找到电子邮件时返回“该电子邮件未在我们的系统中注册”,而对于密码错误的正确电子邮件则返回“密码错误”。在您提供单独的验证消息的情况下,您向潜在攻击者提供了有关如何更有效地引导他们的攻击的附加信息。因此,无论根本原因如何,所有与登录相关的问题都应返回一条通用验证消息,类似于“无效的电子邮件/密码组合”。密码恢复表格也是如此,它通常会说“密码恢复说明已发送到该电子邮件,在特殊情况下,您需要一条验证消息。
The trouble with laravel
Laravel 的问题
The issue you are running into is that laravel validators simply return true or false to denote whether or not the rule is met. Error messages are handled separately. You specifically cannot specify the validator error message from inside your validator logic. I know. It's ridiculous, and poorly planned. All you can do is return true or false. You don't have access to anything else to help you, so your pseudo code isn't going to do it.
您遇到的问题是 Laravel 验证器仅返回 true 或 false 以表示是否满足规则。错误消息是单独处理的。您特别不能从验证器逻辑内部指定验证器错误消息。我知道。这很荒谬,而且计划不周。你所能做的就是返回真或假。您无权访问其他任何东西来帮助您,因此您的伪代码不会这样做。
The (ugly) answer
(丑陋的)答案
The simplest way to create your own validation messages is to create your own validator. That looks something like this (inside your controller):
创建您自己的验证消息的最简单方法是创建您自己的验证器。看起来像这样(在您的控制器中):
$validator = Validator::make($input, $rules, $messages);
You would still have to create your validator on boot (your Valiator::Extend
call. Then you can specify the $rules
normally by passing them in to your custom validator. Finally, you can specify your messages. Something like this, overall (inside your controller):
你仍然需要在启动时创建你的验证器(你的Valiator::Extend
调用。然后你可以$rules
通过将它们传递给你的自定义验证器来正常指定。最后,你可以指定你的消息。总体上是这样的(在你的控制器内):
public function login( Request $request )
{
$rules = [
'email' => 'bail|required|checkEmailPresenceAndValidity'
]
$messages = [
'checkEmailPresenceAndValidity' => 'Invalid email.',
];
$validator = Validator::make($request->all(), $rules, $messages);
}
(I don't remember if you have to specify each rule in your $messages
array. I don't think so). Of course, even this isn't very awesome, because what you pass for $messages is simply an array of strings (and that is all it is allowed to be). As a result, you still can't have this error message easily change according to user input. This all happens before your validator runs too. Your goal is to have the validation message change depending on the validation results, however laravel forces you to build the messages first. As a result, to really do what you want to do, you have to adjust the actual flow of the system, which isn't very awesome.
(我不记得是否必须在$messages
数组中指定每个规则。我不这么认为)。当然,即使这也不是很好,因为您传递给 $messages 的只是一个字符串数组(这就是允许的全部内容)。因此,您仍然无法根据用户输入轻松更改此错误消息。这一切都发生在您的验证器运行之前。您的目标是根据验证结果更改验证消息,但是 laravel 强制您首先构建消息。结果,要真正做你想做的,你必须调整系统的实际流程,这不是很厉害。
A solution would be to have a method in your controller that calculates whether or not your custom validation rule is met. It would do this before you make your validator so that you can send an appropriate message to the validator you build. Then, when you create the validation rule, you can also bind it to the results of your validation calculator, so long as you move your rule definition inside of your controller. You just have to make sure and not accidentally call things out of order. You also have to keep in mind that this requires moving your validation logic outside of the validators, which is fairly hacky. Unfortunately, I'm 95% sure there really isn't any other way to do this.
一个解决方案是在您的控制器中有一个方法来计算您的自定义验证规则是否得到满足。它会在您制作验证器之前执行此操作,以便您可以向您构建的验证器发送适当的消息。然后,当您创建验证规则时,您还可以将其绑定到验证计算器的结果,只要您将规则定义移动到控制器中即可。您只需要确保而不是意外地将事情弄乱。您还必须记住,这需要将您的验证逻辑移到验证器之外,这相当棘手。不幸的是,我 95% 确定确实没有其他方法可以做到这一点。
I've got some example code below. It definitely has some draw backs: your rule is no longer global (it is defined in the controller), the validation logic moves out of the validator (which violates the principle of least astonishment), and you will have to come up with an in-object caching scheme (which isn't hard) to make sure you don't execute your query twice, since the validation logic is called twice. To reiterate, it is definitely hacky, but I'm fairly certain that this is the only way to do what you want to do with laravel. There might be better ways to organize this, but this should at least give you an idea of what you need to make happen.
我在下面有一些示例代码。它肯定有一些缺点:你的规则不再是全局的(它是在控制器中定义的),验证逻辑移出验证器(这违反了最小惊讶原则),你将不得不想出一个输入-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
}
}
Also, it is worth a quick note that this implementation relies on $this
support for closures, which (I believe) was added in PHP 5.4. If you are on an old version of PHP you'll have to provide $this
to the closure with use
.
此外,值得注意的是,此实现依赖于$this
对闭包的支持,(我相信)是在 PHP 5.4 中添加的。如果您使用的是旧版本的 PHP,则必须$this
使用use
.
Edit to rant
编辑咆哮
What it really boils down to is that the laravel validation system is designed to be very granular. Each validation rule is specifically only supposed to validate one thing. As a result, the validation message for a given validator should never have to be changed, hence why $messages
(when you build your own validator) only accepts plain strings.
归根结底,laravel 验证系统的设计非常精细。每个验证规则专门只应该验证一件事。因此,永远不必更改给定验证器的验证消息,因此为什么$messages
(当您构建自己的验证器时)只接受纯字符串。
In general granularity is a good thing in application design, and something that proper implementation of SOLID principles strive for. However, this particular implementation drives me crazy. My general programming philosophy is that a good implementation should make the most common uses-cases very easy, and then get out of your way for the less-common use-cases. In this cases the architecture of laravel makes the most common use-cases easy but the less common use-cases almost impossible. I'm not okay with that trade off. My general impression of Laravel was that it works great as long as you need to do things the laravel way, but if you have to step out of the box for any reason it is going to actively make your life more difficult. In your case the best answer is to probably just step right back inside that box, i.e. make two validators even if it means making a redundant query. The actual impact on your application performance likely will not matter at all, but the hit you will take to your long-term maintainability to get laravel to behave the way you want it will be quite large.
一般来说,粒度在应用程序设计中是一件好事,也是正确实现 SOLID 原则所追求的。然而,这个特殊的实现让我发疯。我的一般编程理念是,一个好的实现应该使最常见的用例变得非常简单,然后为不太常见的用例让路。在这种情况下,laravel 的架构使最常见的用例变得简单,但不太常见的用例几乎不可能。我不同意这种权衡。我对 Laravel 的总体印象是,只要你需要以 Laravel 的方式做事,它就可以很好地工作,但是如果你出于任何原因不得不跳出框框,它就会积极地让你的生活变得更加困难。在你的情况下,最好的答案可能是直接回到那个盒子里,即 即使这意味着进行冗余查询,也要创建两个验证器。对您的应用程序性能的实际影响可能根本无关紧要,但是您对长期可维护性的影响,以使 laravel 以您希望的方式运行,这将是相当大的。
回答by Rimas Kudelis
Alternatively to the other proposals, I think you could also call Validator::replacer('yourRule', function())
in addition to Validator::extend('yourRule', function(...))
and keep track of what causes validation failures in the service provider class you're extending the validator from. This way, you are be able to completely replace the default error message with another one.
或者其他的建议,我想你也可以调用Validator::replacer('yourRule', function())
除了Validator::extend('yourRule', function(...))
和跟踪什么你从扩展验证服务提供商类导致验证失败。这样,您就可以将默认错误消息完全替换为另一个错误消息。
According to docs, replacer()
is meant for making placeholder replacements in the error message before it is being returned, so while this is not strictly that case, it is close enough. Of course, it's kind of an ugly(ish) workaround, but it will probably work (at least it seems to work for me, at a first glance).
根据docs,replacer()
用于在返回错误消息之前在错误消息中进行占位符替换,因此虽然严格来说这不是那种情况,但已经足够接近了。当然,这是一种丑陋的(ish)解决方法,但它可能会起作用(至少乍一看似乎对我有用)。
One thing to keep in mind though is that you'll probablyhave to keep track of these failure causes in an array if you want to avoid automatically returning same message for all fields that failed your custom validation rule.
但要记住的一件事是,如果您想避免为所有未通过自定义验证规则的字段自动返回相同的消息,您可能必须在数组中跟踪这些失败原因。
回答by cre8
Where have you found the error messages for the size validation?
您在哪里找到尺寸验证的错误消息?
I looked up the validation rules in the
Illuminate\Validation\ConcernsValidatesAttributes
trait and all functions return a bool value (also the size validation).
我在Illuminate\Validation\ConcernsValidatesAttributes
trait 中查找了验证规则,
所有函数都返回一个 bool 值(也是大小验证)。
protected function validateSize($attribute, $value, $parameters)
{
$this->requireParameterCount(1, $parameters, 'size');
return $this->getSize($attribute, $value) == $parameters[0];
}
What you have found belongs to this part:
你找到的属于这部分:
$keys = ["{$attribute}.{$lowerRule}", $lowerRule];
In this case it's only for formatting the the output by setting a lowerRule
value, that laravel handles in special cases, like the size validation:
在这种情况下,它仅用于通过设置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);
}
So as long as validation rules have to return a bool value there is no way to return more than one error message. Otherwise you have to rewrite some party of the validation rules.
因此,只要验证规则必须返回 bool 值,就无法返回多个错误消息。否则,您必须重写验证规则的某一方。
An approach for your problem with the validation you could use the exists validation:
您可以使用存在验证的验证问题的一种方法:
public function rules()
{
return [
'email' => ['bail', 'required', Rule::exists('users')->where(function($query) {
return $query->where('valid_email', 1);
})]
];
}
So you would need 2exists validation rules. I would suggest to use the existing one from laravel to check if the email is set and a custom one to check if the account is validated.
所以你需要2 个存在的验证规则。我建议使用 laravel 中的现有电子邮件来检查电子邮件是否已设置,并使用自定义电子邮件来检查帐户是否经过验证。