It should work for simple key/value pairs with this custom validator:
Validator::extendImplicit('allowed_attributes', function ($attribute, $value, $parameters, $validator) {
if (strpos($attribute, '.') === false) {
return in_array($attribute, $parameters);
}
if (is_array($value)) {
return empty(array_diff_key($value, array_flip($parameters)));
}
foreach ($parameters as $parameter) {
if (substr_compare($attribute, $parameter, -strlen($parameter)) === 0) {
return true;
}
}
return false;
});
The validator logic is pretty simple:
- If
$attribute
doesn't contains a .
, we're dealing with a top level parameter, and we just have to check if it is present in the allowed_attributes
list that we pass to the rule.
- If
$attribute
's value is an array, we diff the input keys with the allowed_attributes
list, and check if any attribute key has left. If so, our request had an extra key we didn't expect, so we return false
.
- Otherwise
$attribute
's value is an object we have to check if each parameter we're expecting (again, the allowed_attributes
list) is the last segment of the current attribute (as laravel gives us the full dot notated attribute in $attribute
).
The key here is to apply it to validation rules should like this (note the first validation rule):
$validationRules = [
'parent.*' => 'allowed_attributes:first_name,last_name',
'parent.first_name' => 'required|string|max:40',
'parent.last_name' => 'required|string|max:40'
];
The parent.*
rule will apply the custom validator to each key of the 'parent' object.
To answer your question
Just don't wrap your request in an object, but use the same concept as above and apply the allowed_attributes
rule with a *
:
$validationRules = [
'*' => 'allowed_attributes:first_name,last_name',
'first_name' => 'required|string|max:40',
'last_name' => 'required|string|max:40'
];
This will apply the rule to all the present top level input request fields.
NOTE: Keep in mind that laravel validation is influenced by order of the rules as they are putted in rules array.
For example, moving the parent.*
rule on bottom will trigger that rule on parent.first_name
and parent.last_name
; as opposed, keeping it as the first rule will not trigger the validation for the first_name
and last_name
.
This means that you could eventually remove the attributes that has further validation logic from the allowed_attributes
rule's parameter list.
For example, if you would like to require only the first_name and last_name and prohibit any other field in the parent
object, you might use these rules:
$validationRules = [
'parent.*' => 'allowed_attributes',
'parent.first_name' => 'required|string|max:40',
'parent.last_name' => 'required|string|max:40'
];
But, the following WON'T work as expected:
$validationRules = [
'parent.first_name' => 'required|string|max:40',
'parent.last_name' => 'required|string|max:40',
'parent.*' => 'allowed_attributes',
];
Array Minor Issues
As far as I know, per Laravel's validation logic, if you were up to validate an array of objects, this custom validator would work, but the error message you would get would be generic on the array item, not on the key of that array item that wasn't allowed.
For example, you allow a products field in your request, each with an id:
$validationRules = [
'products.*' => 'allowed_attributes:id',
];
If you validate a request like this:
{
"products": [{
"id": 3
}, {
"id": 17,
"price": 3.49
}]
}
You will get an error on product 2, but you won't be able to tell which field is causing the problem!