laravel / framework

The Laravel Framework.
https://laravel.com
MIT License
32.54k stars 11.02k forks source link

Validator changes array order when using Rule::in or Rule::exists rules on optional key #48440

Closed larskoole closed 1 year ago

larskoole commented 1 year ago

Laravel Version

10.23

PHP Version

8.2.10

Database Driver & Version

No response

Description

When validating arrays using the rules Rule::in() or Rule::exists() it returns the array in a different order than the original. This happens only if the key that is being validated is optional and uses one of the rules described above.

(there might be more rules this applies to)

A simple ksort() on my end would fix this but in my opinion it would be a better DX if the order doesn't change.

In our case we use the order of the array to store our own sort_order in the database but we are forced to keep adding ksort() everywhere.

Let me know if anyone needs more info.

Steps To Reproduce

Tinker(well)-friendly reproduction

$data = [
    'books' => [
        ['title' => 'Harry Potter', 'author' => 'J.K. Rowling'],
        ['title' => 'Lord of the Rings', 'author' => 'J.R.R. Tolkien'],
        ['id' => '3', 'title' => 'The Hobbit', 'author' => 'J.R.R. Tolkien'],
        ['title' => 'A Game of Thrones', 'author' => 'George R.R. Martin'],
    ]
];

$validated = Illuminate\Support\Facades\Validator::validate($data, [
    'books' => 'array',
    'books.*.id' => [
        Illuminate\Validation\Rule::in(['1', '2', '3', '4']),
    ],
    'books.*.title' => 'required|string',
]);

dd($validated);

This produces the following output (mind the array keys):

array:1 [▼ 
  "books" => array:4 [▼
    2 => array:2 [▼
      "id" => "3"
      "title" => "The Hobbit"
    ]
    0 => array:1 [▼
      "title" => "Harry Potter"
    ]
    1 => array:1 [▼
      "title" => "Lord of the Rings"
    ]
    3 => array:1 [▼
      "title" => "A Game of Thrones"
    ]
  ]
]

To prove it works when the optional key is present on all entries

$data = [
    'books' => [
        ['id' => '1', 'title' => 'Harry Potter', 'author' => 'J.K. Rowling'],
        ['id' => '2', 'title' => 'Lord of the Rings', 'author' => 'J.R.R. Tolkien'],
        ['id' => '3', 'title' => 'The Hobbit', 'author' => 'J.R.R. Tolkien'],
        ['id' => '4', 'title' => 'A Game of Thrones', 'author' => 'George R.R. Martin'],
    ]
];

$validated = Illuminate\Support\Facades\Validator::validate($data, [
    'books' => 'array',
    'books.*.id' => [
        Illuminate\Validation\Rule::in(['1', '2', '3', '4']),
    ],
    'books.*.title' => 'required|string',
]);

dd($validated);

Produces:

array:1 [▼
  "books" => array:4 [▼
    0 => array:2 [▼
      "id" => "1"
      "title" => "Harry Potter"
    ]
    1 => array:2 [▼
      "id" => "2"
      "title" => "Lord of the Rings"
    ]
    2 => array:2 [▼
      "id" => "3"
      "title" => "The Hobbit"
    ]
    3 => array:2 [▼
      "id" => "4"
      "title" => "A Game of Thrones"
    ]
  ]
]
github-actions[bot] commented 1 year ago

Thank you for reporting this issue!

As Laravel is an open source project, we rely on the community to help us diagnose and fix issues as it is not possible to research and fix every issue reported to us via GitHub.

If possible, please make a pull request fixing the issue you have described, along with corresponding tests. All pull requests are promptly reviewed by the Laravel team.

Thank you!

cosmastech commented 1 year ago

The issue is emanating from this line here https://github.com/laravel/framework/blob/10.x/src/Illuminate/Validation/Validator.php#L576

larskoole commented 1 year ago

So the two PRs of @sebapastore were both closed, I do very much appreciate the time and effort you put into the issue.

I still think the most logical approach would be to put back the validated items back the way they were given. This way it doesn't matter if $excludeUnvalidatedArrayKeys is set to true or false. We also never have to ksort() because the validator wouldn't have changed anything :)

That said, I don't know if it's as easy as I'm saying.

Delaney commented 1 year ago

Looking at the issue and @sebapastore's PRs, I'm not sure it can be solved without adding a new method. Yes, we don't have to use any sort function, but the validated method builds the results in order of the given rules and unless that is changed, there doesn't seem to be much else that can be done.

driesvints commented 1 year ago

It seems there's no viable solution for this one so closing this. Thanks everyone for your time. If you do want a solution for this, maybe a custom validator extension can be used?

MarcEspiard commented 11 months ago

Hey, just encountered this issue, we are relying on the input's order to save array entries in the db and preserving the input array order matters. If a fix can't be found, should this be at least mentionned in the docs somewhere?