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

Mar*_*tin 5 php validation laravel

我们正在构建一个需要精度的 API 端点。我们希望对 POST/PUT 到服务器的参数强制执行严格的验证。

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

已尝试构建一个名为allowed_attributes(用作allowed_attributes:attr1,attr2,...)的自定义验证器,但为了使其可以在$validationRules数组中使用,它必须应用于嵌套/子属性列表的父级(...因为否则我们的自定义验证器没有访问正在验证的属性)。

Validator::extend('allowed_attributes', 'App\Validators\AllowedAttributesValidator@validate');
Run Code Online (Sandbox Code Playgroud)

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

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"));
Run Code Online (Sandbox Code Playgroud)

关于如何在 Laravel 中更干净地完成此操作,而不需要使用父/子关系来访问所有 $request 属性的列表(在自定义验证器内),有什么想法/建议吗?

mde*_*exp 1

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

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;
});
Run Code Online (Sandbox Code Playgroud)

验证器逻辑非常简单:

  • 如果$attribute不包含 a .,我们正在处理一个顶级参数,我们只需检查它是否存在于allowed_attributes我们传递给规则的列表中。
  • 如果$attribute的值是一个数组,我们将输入键与allowed_attributes列表进行比较,并检查是否有剩余的属性键。如果是这样,我们的请求有一个我们没有预料到的额外密钥,所以我们返回false
  • 否则$attribute的值是一个对象,我们必须检查我们期望的每个参数(同样是列表allowed_attributes)是否是当前属性的最后一段(因为 laravel 为我们提供了完整的点符号属性$attribute)。

这里的关键是将其应用到验证规则应该像这样(注意第一个验证规则):

$validationRules = [
  'parent.*' => 'allowed_attributes:first_name,last_name',
  'parent.first_name' => 'required|string|max:40',
  'parent.last_name' => 'required|string|max:40'
];
Run Code Online (Sandbox Code Playgroud)

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

回答你的问题

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

$validationRules = [
  '*' => 'allowed_attributes:first_name,last_name',
  'first_name' => 'required|string|max:40',
  'last_name' => 'required|string|max:40'
];
Run Code Online (Sandbox Code Playgroud)

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


注意:请记住,laravel 验证会受到规则顺序的影响,因为它们被放入规则数组中。例如,移动底部的规则将触发和parent.*上的规则;相反,将其保留为第一条规则不会触发对和 的验证。parent.first_nameparent.last_namefirst_namelast_name

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

例如,如果您只想需要名字姓氏并禁止对象中的任何其他字段parent,则可以使用以下规则:

$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'
];
Run Code Online (Sandbox Code Playgroud)

但是,以下内容不会按预期工作:

$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', 
];
Run Code Online (Sandbox Code Playgroud)

数组小问题

据我所知,根据 Laravel 的验证逻辑,如果您要验证对象数组,则此自定义验证器可以工作,但您收到的错误消息将在数组项上通用,而不是在该数组的键上不允许的项目。

例如,您允许在请求中使用产品字段,每个字段都有一个 id:

$validationRules = [
  'products.*' => 'allowed_attributes:id',
];
Run Code Online (Sandbox Code Playgroud)

如果您验证这样的请求:

{
    "products": [{
        "id": 3
    }, {
        "id": 17,
        "price": 3.49
    }]
}
Run Code Online (Sandbox Code Playgroud)

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