Skip to content

CanBePrecognitive Trait dose not consider Validation Rules for Arrays #57437

@markusheinemann

Description

@markusheinemann

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:

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 the items validation rule
  • items.* --> matches all rules starting with items.
  • items.*.title --> matches all items.{[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

  1. 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])
  1. 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>
  )
}
  1. Submit the form and see how only the items validation rules are considered but not items.*.title and items.*.description.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions