Tangible Bytes

A Web Developer’s Blog

Laravel Object Validation Closure

I’m using Laravel Inertia and the resulting form submission returns JSON objects - which I wanted to validate.

In my case I have a quiz - which has questions which in turn have answers and those answers have user-submitted responses.

If the CMS editor tries to delete answers which have responses this will result in an SQL referential integrity error - so I want a validation rule for each answer which says

If the answer object has property “deleted” and the database has responses for this answer - fail.

Request file

QuizUpdateRequest.php looks like

<?php

namespace App\Http\Requests;

use Closure;
use DB;
use Illuminate\Foundation\Http\FormRequest;

class QuizUpdateRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [

            'questions.*.answers.*' => function (string $attribute, mixed $value, Closure $fail) {

                if ($value['deleted'] ?? false) {
                    $responses = DB::table('quiz_responses')->where('quiz_answer_id', $value['id'])->exists();
                    if ($responses) {
                        $fail('Cannot delete : The answer has responses.');
                    }
                }
            },
        ];
    }
}

Post Object

I’m using inertiaJS and posting an object like below (edited for clarity)

Note one answer has deleted : true which marks it for deletion.

{
  "questions": [
    {
      "id": 90160,
      "question": "fghfgfcg",
      "answers": [
        {
          "id": 90634,
          "answer": "fghfghfgh",
        },
        {
          "id": 90636,
          "answer": "asdfasdf",
          "deleted": true
        }
      ]
    }
  ]
}

Solution

All the examples I saw in the Laravel docs are for validation rules on individual fields

But when I used questions.*.answers.* the $value object captures each answer with $value['deleted'] and $value['id'] both available in the closure.

This makes it simple to take one property - the deleted flag to see that the validation is needed. Then use another property - the id - to look for responses and fail if they exist.