验证数组-获取当前迭代 [英] Validating array - get current iteration
问题描述
我正在尝试使用Laravel的 FormRequest
验证POST请求.
I'm trying to validate a POST request using Laravel's FormRequest
.
客户正在提交包含一系列商品的订单.我们要求用户仅在 asking_price>要求时指出商品是否需要
和 special_delivery
.500 quantity>10
.
The customer is submitting an order, which has an array of items. We are requiring the user to indicate whether the item needs special_delivery
only if the asking_price > 500
and the quantity > 10
.
以下是我的预期规则:
public function rules() {
'customer_id' => 'required|integer|exists:customers,id',
'items' => 'required|array',
'items.*.name' => 'required|string',
'items.*.asking_price' => 'required|numeric',
'items.*.quantity' => 'required|numeric',
'items.*.special_delivery' // required if price > 500 && quantity > 10
}
我试图按照以下方式做一些事情:
I've attempted to do something along these lines:
Rule::requiredIf($this->input('item.*.asking_price') > 500 && $this->input('item.*.quantity' > 10));
这个问题是我找不到一种方法来访问当前的 items
迭代索引来指示要针对哪个项目进行验证.
The problem with this is that I can't find a way to access the current items
iteration index to indicate which item to validate against.
我还尝试了以下自定义验证:
I also tried the following custom validation:
function ($attribute, $value, $fail) {
preg_match('/\d+/', $attribute, $m);
$askingPrice = $this->input('items')[$m[0]]['asking_price'];
$quantity= $this->input('items')[$m[0]]['quantity'];
if ($askingPrice > 500 && $quantity > 10) {
$fail("$attribute is required");
}
}
尽管此功能使我可以访问当前的 $ attribute
,但问题是它仅在 special_delivery
存在的情况下才能运行.这就破坏了整个目的!
Although this function gives me access to the current $attribute
,the problem is that it will only run if special_delivery
exists. Which defeats the entire purpose!
任何帮助将不胜感激!谢谢!
Any help will be much appreciated! Thank you!
推荐答案
如果您愿意的话,我可能想出了一个解决问题的方法,有时是索引感知的有时
.
I might've come up with a solution to your problem, a index aware sometimes
if you so will.
由于不幸的是不可能将宏添加到Validator中,因此您要么必须重写验证工厂(这是我的建议),然后使用您自己的自定义验证类,要么根据该方法创建一个辅助函数,然后传递Validator实例作为附加参数,并使用它代替 $ this
.
Since it's unfortunately not possible to add macros to the Validator, you would either have to override the validation factory (that's what I suggest) and use your own custom validation class or make a helper function based off the method, pass the Validator instance as an additional parameter and use this instead of $this
.
function indexAwareSometimes(
\Illuminate\Contracts\Validation\Validator $validator,
string $parent,
$attribute,
$rules,
\Closure $callback
) {
foreach (Arr::get($validator->getData(), $parent) as $index => $item) {
if ($callback($validator->getData(), $index)) {
foreach ((array) $attribute as $key) {
$path = $parent.'.'.$index.'.'.$key;
$validator->addRules([$path => $rules]);
}
}
}
}
许多灵感显然来自 有时
方法并没有太大变化.我们基本上是遍历包含所有其他数组( items.*
)的数组(在您的情况下为 $ parent
数组).使用实际数据进行验证,并将当前的 $ rules
(必需
)添加到 $ attribute
( special_delivery
)中如果 $ callback
的计算结果为true,则为索引.
A lot of inspiration obviously came from the sometimes
method and not much has changed. We're basically iterating through the array (the $parent
array, in your case items
) containing all our other arrays (items.*
) with actual data to validate and adding the $rules
(required
) to $attribute
(special_delivery
) in the current index if $callback
evaluates to true.
回调闭包需要两个参数,第一个是父验证实例的形式 $ data
,由 Validator :: getData()
检索,第二个是$ index
外部的 foreach
是调用回调时的时间.
The callback closure requires two parameters, first being the form $data
of your parent validation instance, retrieved by Validator::getData()
, second the $index
the outer foreach
was at the time it called the callback.
在您的情况下,该函数的用法看起来像这样:
In your case the usage of the function would look a little like this:
use Illuminate\Support\Arr;
class YourFormRequest extends FormRequest
{
public function rules()
{
return [
'customer_id' => 'required|integer|exists:customers,id',
'items' => 'required|array',
'items.*.name' => 'required|string',
'items.*.asking_price' => 'required|numeric',
'items.*.quantity' => 'required|numeric',
];
}
public function getValidatorInstance()
{
$validator = parent::getValidatorInstance();
indexAwareSometimes(
$validator,
'items',
'special_delivery',
'required',
fn ($data, $index) => Arr::get($data, 'items.'.$index.'.asking_price') > 500 &&
Arr::get($data, 'items.'.$index.'.quantity') > 10
);
}
}
扩展本机 Validator
类
扩展Laravel的本机 Validator类不是听起来很难.我们正在创建一个自定义的ValidationServiceProvider,并继承Laravel的 Illuminate \ Validation \ ValidationServiceProvider
作为父级.仅 registerValidationFactory
方法需要替换为它的副本,在该副本中,我们指定了应由工厂使用的自定义验证程序解析器:
Extending the native Validator
class
Extending Laravel's native Validator class isn't as hard as it sounds. We're creating a custom ValidationServiceProvider and inherit Laravel's Illuminate\Validation\ValidationServiceProvider
as a parent. Only the registerValidationFactory
method needs to be replaced by a copy of it where we specify our custom Validator resolver that should be used by the factory instead:
<?php
namespace App\Providers;
use App\Validation\CustomValidator;
use Illuminate\Contracts\Translation\Translator;
use Illuminate\Validation\Factory;
use Illuminate\Validation\ValidationServiceProvider as ParentValidationServiceProvider;
class ValidationServiceProvider extends ParentValidationServiceProvider
{
protected function registerValidationFactory(): void
{
$this->app->singleton('validator', function ($app) {
$validator = new Factory($app['translator'], $app);
$resolver = function (
Translator $translator,
array $data,
array $rules,
array $messages = [],
array $customAttributes = []
) {
return new CustomValidator($translator, $data, $rules, $messages, $customAttributes);
};
$validator->resolver($resolver);
if (isset($app['db'], $app['validation.presence'])) {
$validator->setPresenceVerifier($app['validation.presence']);
}
return $validator;
});
}
}
自定义验证器继承了Laravel的 Illuminate \ Validation \ Validator
并添加了 indexAwareSometimes
方法:
The custom validator inherits Laravel's Illuminate\Validation\Validator
and adds the indexAwareSometimes
method:
<?php
namespace App\Validation;
use Closure;
use Illuminate\Support\Arr;
use Illuminate\Validation\Validator;
class CustomValidator extends Validator
{
/**
* @param string $parent
* @param string|array $attribute
* @param string|array $rules
* @param Closure $callback
*/
public function indexAwareSometimes(string $parent, $attribute, $rules, Closure $callback)
{
foreach (Arr::get($this->data, $parent) as $index => $item) {
if ($callback($this->data, $index)) {
foreach ((array) $attribute as $key) {
$path = $parent.'.'.$index.'.'.$key;
$this->addRules([$path => $rules]);
}
}
}
}
}
然后,我们只需在 config/app.php
中用您自己的自定义服务提供商替换Laravel的 Illuminate \ Validation \ ValidationServiceProvider
,您就可以开始了.
Then we just need to replace Laravel's Illuminate\Validation\ValidationServiceProvider
with your own custom service provider in config/app.php
and you're good to go.
它甚至可以与 Barry vd一起使用.Heuvel的 laravel-ide-helper
包.
It even works with Barry vd. Heuvel's laravel-ide-helper
package.
return [
'providers' => [
//Illuminate\Validation\ValidationServiceProvider::class,
App\Providers\ValidationServiceProvider::class,
]
]
回到上面的示例,您只需要更改表单请求的 getValidatorInstance()
方法:
Going back to the example above, you only need to change the getValidatorInstance()
method of your form request:
public function getValidatorInstance()
{
$validator = parent::getValidatorInstance();
$validator->indexAwareSometimes(
'items',
'special_delivery',
'required',
fn ($data, $index) => Arr::get($data, 'items.'.$index.'.asking_price') > 500 &&
Arr::get($data, 'items.'.$index.'.quantity') > 10
);
}
这篇关于验证数组-获取当前迭代的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!