user2722667
user2722667

Reputation: 8661

Laravel validate array of objects

I wonder if it's possible to validate an array of objects in Laravel? I have built a form looking like an Excel page using Vue, so the user can edit many rows, which gets uploaded later.

The data that I wish to validate when making a POST request to my controller looks like this:

[
    // 0
    {
        title: "my title",
        post: "my post text",
    },
    // 1
    {
        title: "my title",
        post: "my post text",
    },
    // 2
    {
        title: "my title",
        post: "my post text",
    },
];

So how can I, for example, add a required rule to each input?

Upvotes: 27

Views: 46000

Answers (4)

Alexander Ivashchenko
Alexander Ivashchenko

Reputation: 868

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

Upvotes: 2

Saul Montilla
Saul Montilla

Reputation: 804

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)
  ]);
}

Upvotes: 2

Michael Aaron Wilson
Michael Aaron Wilson

Reputation: 1040

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.

Upvotes: 17

Mauricio Rodrigues
Mauricio Rodrigues

Reputation: 807

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

Upvotes: 45

Related Questions