Laravel validate array of objects

Laravel validations works fine if the elements of the array are associative arrays, but if they are objects it will fail, and sometimes you want to keep your data as objects for further manipulations.

I created the following custom rule

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Validator;

/**
 * Valida in arreglo de objetos
 * 
 * Si bien Laravel puede validar arrays, solo funciona
 * si sus elementos son arreglos asociativos, cuando son 
 * objetos la regla no se aplica
 */
class ArrayOfObjects implements Rule
{
    /**
     * Reglas de validacion
     */
    private array $rules;

    /**
     * Mensaje de error
     */
    private string $error;

    /**
     * Create a new rule instance.
     *
     * @return void
     */
    public function __construct(array $rules)
    {
        $this->rules = $rules;
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        foreach ($this->rules as $property => $property_rules)
        {
            $rules = [$property => $property_rules];

            if (isset($value->{$property}))
            {
                $data = [$property => $value->{$property}];

                $validator = Validator::make($data, $rules);

                if ($validator->fails())
                {
                    $this->error = $validator->errors()->first($property);

                    // Detener el loop si hay un error
                    return false;
                }
            }
            else
            {
                if (in_array('required', $property_rules))
                {
                    $this->error = __('validation.required', ['attribute' => $property]);

                    return false;
                }
            }
        }

        return true;        
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return $this->error;
    }
}

Its ussage in a controller for example

public function store (Request $request)
{
  $object_rules = [
    'attr_1' => ['required', 'string'],
    'attr_2' => ['required', 'numeric']
  ];

  $request->validate([
    'array.*' => new ArrayOfObjects($object_rules)
  ]);
}

The approved answer works if your posting an array, however to take it a step further, I am needing to save multiple arrays. While that approach would work if I make two separate endpoints, what if I want to save everything inside one DB::transaction?

Viola:

// POST:
{
    "array1": [
        { "key1": "string", "key2": 1 },
        { "key1": "string", "key2": 0 }
    ],
    "array2": [
        { "key3": "string", "key4": 1 },
        { "key3": "string", "key4": 0 }
    ]
}
// SERVER:
$this->validate($request, [
    'array1' => 'present|array',
    'array2' => 'present|array',
    'array1.*.key1' => 'required|string',
    'array1.*.key2' => 'required|integer',
    'array2.*.key3' => 'required|string',
    'array2.*.key4' => 'required|integer'
]);

DB::transaction(function() use($request) {
    foreach($request['array1'] as $x){
        // ...do stuff here
    };
});

Note: 'present|array' accepts empty arrays whereas 'required|array' would reject them.


You can use array validation of Laravel.

eg.

$validator = Validator::make($request->all(), [
    'row.*.title' => 'required',
    'row.*.post' => 'required',
]);

You can find more about Laravel array validation here

Tags:

Laravel