-
Notifications
You must be signed in to change notification settings - Fork 11.6k
Description
Laravel Version
12.0
PHP Version
8.2
Database Driver & Version
Postgres 17 / macOS 15.6 on Laravel Sail
Description
Laravel provides a way for "live validation" with the Precognition package. This allows to run validation rules on the server side before the actual request is sent. The package provides a functionality to trigger only validation for specified fields using the only
property:
import { useForm } from 'laravel-precognition-react-inertia';
const form = useForm('post', '...', {
name: '',
description: '',
});
form.validate({only: ['name']})
The example code will only report errors that effect the name
field. Under the hood the custom header precognition-validate-only
is added to the recognitive request. This header is evaluated in the trait CanBePrecognitive
:
framework/src/Illuminate/Http/Concerns/CanBePrecognitive.php
Lines 15 to 23 in 29684f5
public function filterPrecognitiveRules($rules) | |
{ | |
if (! $this->headers->has('Precognition-Validate-Only')) { | |
return $rules; | |
} | |
return (new Collection($rules)) | |
->only(explode(',', $this->header('Precognition-Validate-Only'))) | |
->all(); |
Only the validation rules specified in the precognition-validate-only
header are then filtered there.
The Problem
The filter logic inside the CanBePrecognitive
works fine for simple use cases but dose not consider array validations. Given an example where some validation on an array of objects is specified for a request:
$rules = [
'items' => ['required', 'array', 'min:1'],
'items.*.title' => ['required', 'string', 'max:256'],
'items.*.description' => ['required', 'string', 'min:1000'],
];
In that case the parameter $rules
inside the CanBePrecognitive
will look like this where for each array item of the items
property in the request a new entry items.x.{title|description}
is added.
"items" => array:3 [
0 => "required"
1 => "array"
2 => "min:3"
],
"items.0.title" => array:3 [
0 => "required"
1 => "string"
2 => "max:256"
],
"items.0.description" => array:3 [
0 => "required"
1 => "string"
2 => "max:1000"
]
The current filter logic dose not catch the items.x.{title|description}
rules.
Solution Proposal
A possible solution would be to allow wildcards in the precognition-validate-only
header similar to the behaviour when specifying the validation rules.
items
--> exactly matches theitems
validation ruleitems.*
--> matches all rules starting withitems.
items.*.title
--> matches allitems.{[0-9]}.title
rules
PS. If this bug report & solution is evaluated as valid I am happy to contribute an implementation of the solution.
Workaround
My current workaround is to predict the possible validation rules based on the user input and pass all rules to the only
property when triggering the precognition request
function validate() {
// will return an array like ['items.0.title', 'items.0.description', 'items.1.title', 'items.1.description', ...]
const rules = form.data.items.reduce((acc, item, index) => [...acc, `items.${index}.title`, `items.${index}.description`], [] as string[]);
form.validate({only: rules})
}
Steps To Reproduce
- Create a new Controller which has some validation and register any route with it.
// TestController.php
final readonly class TestController
{
public function test(Request $request) {
Validator::make($request->all(), [
'items' => ['required', 'array', 'min:1'],
'items.*.title' => ['required', 'string', 'max:256'],
'items.*.description' => ['required', 'string', 'min:1000'],
])->validate();
}
}
// web.php
Route::post('/test', [TestController::class, 'test'])->middleware([HandlePrecognitiveRequests::class])
- Consume the endpoint using Precognition, React and Inertia
export function SomePage() {
const form = useForm('post', `/test`, {
items: [{ title: '', description: '' }],
});
const submit: FormEventHandler = (e) => {
e.preventDefault();
form.validate({
only: ['items],
onSuccess: () => {
console.log('success');
},
onValidationError: (e) => console.log(e),
});
};
return (
<form onSubmit={submit}>
<!-- Some form logic removed for simplicity -->
<button type="submit">Submit</button>
</form>
)
}
- Submit the form and see how only the
items
validation rules are considered but notitems.*.title
anditems.*.description
.