8000 Inconsistent validation behavior with empty arrays and nested rules · Issue #55657 · laravel/framework · GitHub
[go: up one dir, main page]

Skip to content

Inconsistent validation behavior with empty arrays and nested rules #55657

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
comhon-project opened this issue May 6, 2025 · 9 comments
Open

Comments

@comhon-project
Copy link
comhon-project commented May 6, 2025

Laravel Version

11.44.2

PHP Version

8.3

Database Driver & Version

No response

Description

Hello everyone,

I've noticed an unexpected behavior when validating empty arrays with nested rules.

Here’s an example without any nested rules, which works as expected:

$validated = Validator::validate(['foo' => []], [
    'foo' => 'present|array',
]);
// $validated contains the "foo" key:  ["foo" => []]

However, when I add a nested rule:

$validated = Validator::validate(['foo' => []], [
    'foo' => 'present|array',
    'foo.bar' => 'integer'
]);
// $validated is empty: []

Expected:
the validated values should be the same and contain the "foo" key, since "foo.bar" is not required.

Steps To Reproduce

$validated = Validator::validate(['foo' => []], [
    'foo' => 'present|array',
]);
// $validated contains the "foo" key:  ["foo" => []]

$validated = Validator::validate(['foo' => []], [
    'foo' => 'present|array',
    'foo.bar' => 'integer'
]);
// $validated is empty: []
@macropay-solutions
Copy link
macropay-solutions commented May 6, 2025

Can you please try:

$validated = Validator::validate(['foo' => []], [
    'foo' => 'present|array',
    'foo.bar' => 'sometimes|integer'
]);

https://laravel.com/docs/12.x/validation#validating-arrays

@Kristuxa84
Copy link
Kristuxa84 commented May 6, 2025 via email

@comhon-project
Copy link
Author

Can you please try:

$validated = Validator::validate(['foo' => []], [
'foo' => 'present|array',
'foo.bar' => 'sometimes|integer'
]);

https://laravel.com/docs/12.x/validation#validating-arrays

It doesn't work with the sometimes rule either. And (if I'm not mistaken), the documentation link doesn't address my issue.

@macropay-solutions
Copy link
macropay-solutions commented May 6, 2025

@comhon-project the issue is not reproducing in version 8, but it reproduces also in version 9.

\Illuminate\Validation\Factory::$excludeUnvalidatedArrayKeys

// v 9
    /**
     * Indicates that unvalidated array keys should be excluded, even if the parent array was validated.
     *
     * @var bool
     */
    protected $excludeUnvalidatedArrayKeys = true;

//=> array:0 []

// v 8
    /**
     * Indicates that unvalidated array keys should be excluded, even if the parent array was validated.
     *
     * @var bool
     */
    protected $excludeUnvalidatedArrayKeys;

//=> array:1 [
//  "foo" => []
//]

If we edit the V9 to be like V8 in vendor, then the behavior is the same as in 8.

You can call this \Illuminate\Validation\Factory::includeUnvalidatedArrayKeys to fix the behaviour before \Illuminate\Validation\Factory::make is called

    /**
     * Indicate that unvalidated array keys should be included in validated data when the parent array is validated.
     *
     * @return void
     */
    public function includeUnvalidatedArrayKeys()
    {
        $this->excludeUnvalidatedArrayKeys = false;
    }

Example of custom validator:
Register validation service provider \MacropaySolutions\LaravelCrudWizard\Providers\ValidationServiceProvider instead of Illuminate\Validation\ValidationServiceProvider::class if you are using illuminate/validation < 11.44.1 to solve CVE-2025-27515 security issue
https://github.com/macropay-solutions/laravel-crud-wizard-free/blob/73d126e5a5154100bc8156a11d73a6b40b309ac0/src/Providers/ValidationServiceProvider.php#L37

        $this->app->singleton('validator', function ($app) {
            $validator = new Factory($app['translator'], $app);
            $validator->includeUnvalidatedArrayKeys(); // add this here
            $validator->resolver(

This is just an example. Maybe others could point out other simpler ways.

Update:

Acc to this: https://stackoverflow.com/a/76450468
You just call in a service provider

// In a service provider
 \Illuminate\Support\Facades\Validator::includeUnvalidatedArrayKeys();

@comhon-project
Copy link
Author
comhon-project commented May 6, 2025
< 8000 p dir="auto">thanks for that work around but i don't want to include the array key when it is invalid.

in my exemple :

$validated = Validator::validate(['foo' => []], [
    'foo' => 'present|array',
    'foo.bar' => 'integer'
]);

the nested value bar is not required so ['foo' => []] should be considered as valid i guess.

@macropay-solutions
Copy link

@comhon-project this is the code that removes the foo element
\Illuminate\Validation\Validator::validated

            if ($this->excludeUnvalidatedArrayKeys &&
                in_array('array', $rules) &&
                ! empty(preg_grep('/^'.preg_quote($key, '/').'\.+/', array_keys($this->getRules())))) {
                continue;
            }

@comhon-project
Copy link
Author
comhon-project commented May 6, 2025

maybe there should be an additional condition ? something like :
(the code has been updated since the fix since fix #52658)

            if (
                $this->excludeUnvalidatedArrayKeys &&
                (in_array('array', $rules) || in_array('list', $rules)) &&
                $value !== null &&
                $value !== [] && // <= the new condition
                ! empty(preg_grep('/^' . preg_quote($key, '/') . '\.+/', array_keys($this->getRules())))
            ) {
                continue;
            }

this way an empty array, that just don't have "maching" values, would be added to validated values.

@macropay-solutions
Copy link
macropay-solutions commented May 7, 2025

@comhon-project
So:

if excludeUnvalidatedArrayKeys
AND the key (foo) has a rule of array or list
AND foo value is not null //*
AND foo value not empty array // * *
AND 8000 foo is present in other keys of the rules as foo....
then ignore.

*$value !== null should be also double checked if there is a nullable in foo rules before returning it because this will return [foo => null] | or it will not get into this case if it doesn't have the nullable anyway?
**$value !== [] should be also double checked if there isn't a required in the other foo.... rules before returning it | or it will not get into this case if it has the required anyway?

What do you think?

Update
Your proposed solution does not fix this case

    $validated = Validator::validate(['foo' => ['x' => 0]], [
        'foo' => 'present|array',
        'foo.bar' => 'integer',
    ]);

returns []; // but this is another issue, just like this one that you rose, even without your extra condition.

@comhon-project
Copy link
Author

*$value !== null should be also double checked if there is a nullable in foo rules before returning it because this will return [foo => null] | or it will not get into this case if it doesn't have the nullable anyway?
**$value !== [] should be also double checked if there isn't a required in the other foo.... rules before returning it | or it will not get into this case if it has the required anyway?

If I'm not wrong, these cases will not passe the validation and an exception will be thrown earlier, just few lines before :

throw_if($this->messages->isNotEmpty(), $this->exception, $this);

so no need to double check in the if statement.

Update
Your proposed solution does not fix this case

Bummer 😅
need more inversigation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants
0