当前位置: 首页 > 知识库问答 >
问题:

Laravel验证:仅允许已知属性/属性,否则验证失败

林鹏鹍
2023-03-14

我们正在构建一个需要精确性的apiendpoint。我们希望对POST/PUT到服务器的参数实施严格的验证。

如果api用户发送了一个不受支持的key=value对(例如,我们允许参数[first\u name,last\u name],并且用户包含一个不受支持的参数[country]),我们希望验证失败。

已尝试构建名为允许的\u属性(用作允许的\u属性:attr1、attr2、)的自定义验证器,但要使其在$validationRules数组中可用,必须将其应用于嵌套/子属性列表的父级(…因为我们的自定义验证器无法访问正在验证的属性)。

Validator::extend('allowed_attributes', 'App\Validators\AllowedAttributesValidator@validate');

这就给其他验证器带来了问题,我们必须预测父/子结构及其周围的代码,包括额外的验证后错误键和错误消息字符串的清理。

tl;dr:非常脏,不是一个干净的实现。

$validationRules = [
  'parent' => 'allowed_attributes:first_name,last_name',
  'parent.first_name' => 'required|string|max:40',
  'parent.last_name' => 'required|string|max:40'
];

$isValid = Validator::make(['parent' => $request], $validationRules);

var_dump("Validation results: " . ($isValid ? "passed" : "failed"));

关于如何在laravel中更干净地实现这一点,而不需要使用父/子关系来访问所有$request属性列表(在自定义验证器中),有什么想法/建议吗?

共有2个答案

周锐
2023-03-14

它应该适用于具有此自定义验证器的简单键/值对:

Validator::extendImplicit('allowed_attributes', function ($attribute, $value, $parameters, $validator) {
    // If the attribute to validate request top level
    if (strpos($attribute, '.') === false) {
        return in_array($attribute, $parameters);
    }

    // If the attribute under validation is an array
    if (is_array($value)) {
        return empty(array_diff_key($value, array_flip($parameters)));
    }

    // If the attribute under validation is an object
    foreach ($parameters as $parameter) {
        if (substr_compare($attribute, $parameter, -strlen($parameter)) === 0) {
            return true;
        }
    }

    return false;
});

验证器逻辑非常简单:

  • 如果$attribute不包含 ,我们正在处理一个顶级参数,我们只需检查它是否存在于传递给规则的允许的属性列表中

这里的关键是将其应用于验证规则,如下所示(请注意第一条验证规则):

$validationRules = [
  'parent.*' => 'allowed_attributes:first_name,last_name',
  'parent.first_name' => 'required|string|max:40',
  'parent.last_name' => 'required|string|max:40'
];

父对象* 规则将对“父”对象的每个键应用自定义验证器。

只是不要在对象中包装您的请求,而是使用与上述相同的概念,并使用*应用allowed_attributes规则:

$validationRules = [
  '*' => 'allowed_attributes:first_name,last_name',
  'first_name' => 'required|string|max:40',
  'last_name' => 'required|string|max:40'
];

这将把规则应用于所有当前的顶级输入请求字段。

注意:请记住,在规则数组中放入规则时,laravel验证会受到规则顺序的影响。例如,移动父对象* 规则将在父级上触发该规则。名字和父项。姓氏;相反,将其保留为第一条规则不会触发对first\u namelast\u name的验证。

这意味着您最终可以从allowed_attributes规则的参数列表中删除具有进一步验证逻辑的属性。

例如,如果您希望只需要first_name和last_name并禁止对象中的任何其他字段,您可以使用以下规则:

$validationRules = [
  // This will be triggered for all the request fields except first_name and last_name
  'parent.*' => 'allowed_attributes', 
  'parent.first_name' => 'required|string|max:40',
  'parent.last_name' => 'required|string|max:40'
];

但是,以下内容不会像预期的那样工作:

$validationRules = [
  'parent.first_name' => 'required|string|max:40',
  'parent.last_name' => 'required|string|max:40',
  // This, instead would be triggered on all fields, also on first_name and last_name
  // If you put this rule as last, you MUST specify the allowed fields.
  'parent.*' => 'allowed_attributes', 
];

据我所知,根据Laravel的验证逻辑,如果你要验证一个对象数组,这个自定义验证器可以工作,但是你会得到的错误消息在数组项上是通用的,而不是在数组项的键上允许。

例如,您在请求中允许一个products字段,每个字段都有一个id:

$validationRules = [
  'products.*' => 'allowed_attributes:id',
];

如果您验证这样的请求:

{
    "products": [{
        "id": 3
    }, {
        "id": 17,
        "price": 3.49
    }]
}

您将在产品2上看到一个错误,但您无法判断是哪个字段导致了问题!

劳通
2023-03-14

我更喜欢发布一个新的答案,因为这个方法与之前的方法不同,而且更干净。因此,我宁愿将这两种方法分开,而不是在同一个答案中混为一谈。

自上次回答以来,我深入研究了验证名称空间的源代码,发现最简单的方法是扩展Validator类,以重新实现passs()函数,同时检查所需内容。

此实现还可以正确地处理单个数组/对象字段的特定错误消息,而不产生任何影响,并且应该与通常的错误消息转换完全兼容。

你应该首先在你的app文件夹中创建一个Validator类(我把它放在app/Validation/Validator.php)并实现如下passs方法:

<?php

namespace App\Validation;

use Illuminate\Support\Arr;
use Illuminate\Validation\Validator as BaseValidator;

class Validator extends BaseValidator
{
    /**
     * Determine if the data passes the validation rules.
     *
     * @return bool
     */
    public function passes()
    {
        // Perform the usual rules validation, but at this step ignore the
        // return value as we still have to validate the allowance of the fields
        // The error messages count will be recalculated later and returned.
        parent::passes();

        // Compute the difference between the request data as a dot notation
        // array and the attributes which have a rule in the current validator instance
        $extraAttributes = array_diff_key(
            Arr::dot($this->data),
            $this->rules
        );

        // We'll spin through each key that hasn't been stripped in the
        // previous filtering. Most likely the fields will be top level
        // forbidden values or array/object values, as they get mapped with
        // indexes other than asterisks (the key will differ from the rule
        // and won't match at earlier stage).
        // We have to do a deeper check if a rule with that array/object
        // structure has been specified.
        foreach ($extraAttributes as $attribute => $value) {
            if (empty($this->getExplicitKeys($attribute))) {
                $this->addFailure($attribute, 'forbidden_attribute', ['value' => $value]);
            }
        }

        return $this->messages->isEmpty();
    }
}

这将基本上扩展默认的Validator类,以添加对Pass方法的其他检查。检查通过转换为点符号(以支持数组/对象验证)的输入属性和至少分配了一个规则的属性之间的键计算数组差异。

然后,您错过的最后一步是在服务提供程序的引导方法中绑定新的Validator类。要做到这一点,您可以重写绑定到IoC容器中的类的解析程序:

// Do not forget the class import at the top of the file!
use App\Validation\Validator;

// ...

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->app->make('validator')
            ->resolver(function ($translator, $data, $rules, $messages, $attributes) {
                return new Validator($translator, $data, $rules, $messages, $attributes);
            });
    }

// ...

您不必做任何特定的事情来使用此功能。只需像往常一样调用valester方法:

$this->validate(request(), [
    'first_name' => 'required|string|max:40',
    'last_name' => 'required|string|max:40'
]);

要自定义错误消息,您只需在lang文件中添加一个翻译键,该键等于forbidden_attribute(您可以在addFailure方法调用的自定义Validator类中自定义错误键名称)。

示例:resources/lang/en/validation。php

<?php

return [
    // ...

    'forbidden_attribute' => 'The :attribute key is not allowed in the request body.',

    // ...
];

注:此实现仅在Laravel 5.3中进行了测试

 类似资料:
  • 我正在尝试使用“语言”中的验证属性 我有这个: 要显示错误,请执行以下操作: 以及控制器中的验证: $messages数组: 有人能告诉我我做错了什么吗。我希望:attribute name在attributes数组(语言)中被替换为“nice name”。 谢谢 编辑: 我注意到问题是我从来没有为我的Laravel项目设置默认语言。当我设置语言为'NL'上面的代码工作。但是,当我设置我的语言时,

  • 晚上好,我正在尝试在下面的场景中使用Hibernate验证器:

  • 字段的属性似乎不起作用。 在HTML5中有没有其他属性可以帮助我设置字段值的最小长度?

  • 假设我有以下课程: 是否可以通过“MyProduct”类验证“code”属性?比如:

  • 问题内容: 我有一个laravel 模型,该模型在和上具有唯一的验证规则。在我的存储库中,当我更新模型时,我将重新验证字段,以使所需的规则验证没有问题: 测试失败 有没有办法优雅地解决这个问题? 问题答案: 将当前正在更新的实例的追加到验证器。 传递实例的来忽略唯一的验证器。 在验证器中,使用参数来检测您是否正在 更新 或 创建 资源。 如果进行更新,则强制唯一规则忽略给定的id: 如果创建,请照

  • As we learned earlier in the book, the validate method on a Model is called before set and save, and is passed the model attributes updated with the values from these methods. By default, where we def