验证数组 - 获取当前迭代
Validating array - get current iteration
我正在尝试使用 Laravel 的 FormRequest
验证 POST 请求。
客户正在提交一个订单,其中包含一系列商品。我们要求用户仅在 asking_price > 500
和 quantity > 10
.
时才表明该项目是否需要 special_delivery
以下是我的预期规则:
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
}
我试图按照这些思路做一些事情:
Rule::requiredIf($this->input('item.*.asking_price') > 500 && $this->input('item.*.quantity' > 10));
这个问题是我无法找到一种方法来访问当前 items
迭代索引以指示要验证的项目。
我还尝试了以下自定义验证:
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
存在,它只会 运行。这破坏了整个目的!
任何帮助将不胜感激!
谢谢!
如果你愿意,我可能已经想出了一个解决你问题的方法,一个索引感知 sometimes
。
不幸的是,由于无法向验证器添加宏,您要么必须覆盖验证工厂(这就是我的建议)并使用您自己的自定义验证 class,要么创建一个基于关闭的辅助函数方法,将 Validator 实例作为附加参数传递,并使用它代替 $this
.
先酱:indexAwareSometimes
验证函数
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]);
}
}
}
}
很多灵感显然来自 sometimes
method,并且没有太大变化。我们基本上遍历数组($parent
数组,在你的例子中是 items
),其中包含我们所有其他数组(items.*
)和实际数据以验证并添加 $rules
(required
) 到 $attribute
(special_delivery
) 在当前索引中,如果 $callback
计算为真。
回调闭包需要两个参数,第一个是您的父验证实例的形式 $data
,由 Validator::getData()
检索,第二个 $index
外部 foreach
是当时它调用了回调。
在你的例子中,函数的用法看起来有点像这样:
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
class
扩展 Laravel 的原生 Validator class 并不像听起来那么难。我们正在创建自定义 ValidationServiceProvider 并继承 Laravel 的 Illuminate\Validation\ValidationServiceProvider
作为父级。只有 registerValidationFactory
方法需要用它的副本替换,我们在其中指定工厂应使用的自定义验证器解析器:
<?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
方法:
<?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
就可以了。
它甚至适用于 Barry vd. Heuvel's laravel-ide-helper
package。
return [
'providers' => [
//Illuminate\Validation\ValidationServiceProvider::class,
App\Providers\ValidationServiceProvider::class,
]
]
回到上面的例子,你只需要改变你的表单请求的getValidatorInstance()
方法:
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
);
}
我正在尝试使用 Laravel 的 FormRequest
验证 POST 请求。
客户正在提交一个订单,其中包含一系列商品。我们要求用户仅在 asking_price > 500
和 quantity > 10
.
special_delivery
以下是我的预期规则:
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
}
我试图按照这些思路做一些事情:
Rule::requiredIf($this->input('item.*.asking_price') > 500 && $this->input('item.*.quantity' > 10));
这个问题是我无法找到一种方法来访问当前 items
迭代索引以指示要验证的项目。
我还尝试了以下自定义验证:
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
存在,它只会 运行。这破坏了整个目的!
任何帮助将不胜感激! 谢谢!
如果你愿意,我可能已经想出了一个解决你问题的方法,一个索引感知 sometimes
。
不幸的是,由于无法向验证器添加宏,您要么必须覆盖验证工厂(这就是我的建议)并使用您自己的自定义验证 class,要么创建一个基于关闭的辅助函数方法,将 Validator 实例作为附加参数传递,并使用它代替 $this
.
先酱:indexAwareSometimes
验证函数
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]);
}
}
}
}
很多灵感显然来自 sometimes
method,并且没有太大变化。我们基本上遍历数组($parent
数组,在你的例子中是 items
),其中包含我们所有其他数组(items.*
)和实际数据以验证并添加 $rules
(required
) 到 $attribute
(special_delivery
) 在当前索引中,如果 $callback
计算为真。
回调闭包需要两个参数,第一个是您的父验证实例的形式 $data
,由 Validator::getData()
检索,第二个 $index
外部 foreach
是当时它调用了回调。
在你的例子中,函数的用法看起来有点像这样:
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
class
扩展 Laravel 的原生 Validator class 并不像听起来那么难。我们正在创建自定义 ValidationServiceProvider 并继承 Laravel 的 Illuminate\Validation\ValidationServiceProvider
作为父级。只有 registerValidationFactory
方法需要用它的副本替换,我们在其中指定工厂应使用的自定义验证器解析器:
<?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
方法:
<?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
就可以了。
它甚至适用于 Barry vd. Heuvel's laravel-ide-helper
package。
return [
'providers' => [
//Illuminate\Validation\ValidationServiceProvider::class,
App\Providers\ValidationServiceProvider::class,
]
]
回到上面的例子,你只需要改变你的表单请求的getValidatorInstance()
方法:
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
);
}