-
Notifications
You must be signed in to change notification settings - Fork 11.7k
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 theitemsvalidation ruleitems.*--> matches all rules starting withitems.items.*.title--> matches allitems.{[0-9]}.titlerules
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
itemsvalidation rules are considered but notitems.*.titleanditems.*.description.