Back to Course |
Laravel Array Validation: All You Need To Know

Multi-Dimensional Array Example

Another example for us to look at is multi-dimensional arrays. This is mainly meant to validate the whole array and its sub-items. For example, if we have a team of players, we want to validate both the players and their positions:

Here's what we are aiming for:

  • The team name is required
  • At least three players are required, but no more than 10
  • Player id is required and has to be unique in the team
  • Player position is required and has to be unique in the team

Validation Rules

Here's what our validation rules will look like:

app/Http/Requests/StoreTeamRequest.php

// ...
 
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:200'],
'players' => ['required', 'array', 'min:3', 'max:10'],
'players.*.id' => ['required', 'exists:users,id', 'integer', 'distinct'],
'players.*.position' => ['required', 'string', 'max:200', 'distinct'],
];
}
 
// ...

As you can see, it looks pretty similar to the previous examples, but this time we have players.*.id and players.*.position rules. This allows us to specify the conditions to validate our array input.

Worth mentioning that we can also customize the error messages for each rule:

To fix this, we'll use the messages method:

// ...
 
public function messages()
{
return [
'players.*.id.required' => 'The player field is required.',
'players.*.id.exists' => 'The selected player is invalid.',
'players.*.id.distinct' => 'Player cannot be selected twice',
'players.*.position.required' => 'Player position is required',
'players.*.position.distinct' => 'Player positions must be unique',
];
}
 
// ...

Here, we have to specify the path to the rule, in our case, players.*.id, which indicates that any index on the players' array should get this message if the id validation fails.


The Form

For our form - we'll keep it simple for now and just have a fixed number of players (spoiler alert: we'll make Livewire and VueJS versions of this form in the following lessons):

resources/views/teams/create.blade.php

{{-- ... --}}
 
<form method="POST" action="{{ route('teams.store') }}">
@csrf
<div class="mb-4">
<label class="text-xl text-gray-600" for="name">Name <span
class="text-red-500">*</span></label>
<input type="text" class="border-2 border-gray-300 p-2 w-full" name="name" id="name"
value="{{ old('name') }}">
@error('name')
<div class="text-red-500 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
 
@for($i = 1; $i <= 3; $i++)
<div class="mb-4 grid grid-cols-2">
<div class="w-1/2">
<label class="text-xl text-gray-600" for="players[{{$i}}][id]">Player<span
class="text-red-500">*</span></label>
<select name="players[{{$i}}][id]" id="players[{{$i}}][id]"
class="border-2 border-gray-300 p-2 w-full">
<option value="">Select Player</option>
@foreach($users as $id => $name)
<option value="{{ $id }}"
@selected(old('players.'.$i.'.id') == $id)
>{{ $name }}</option>
@endforeach
</select>
@error('players.'.$i.'.id')
<div class="text-red-500 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
<div class="w-1/2">
<label class="text-xl text-gray-600" for="players[{{$i}}][position]">Position<span
class="text-red-500">*</span></label>
<select name="players[{{$i}}][position]" id="players[{{$i}}][position]"
class="border-2 border-gray-300 p-2 w-full">
<option value="">Select Position</option>
@foreach($positions as $id => $name)
<option value="{{ $id }}"
@selected(old('players.'.$i.'.position') == $id)
>{{ $name }}</option>
@endforeach
</select>
@error('players.'.$i.'.position')
<div class="text-red-500 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
</div>
@endfor
 
<div class="mb-4">
@error('players')
<div class="text-red-500 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
 
<div class="mb-4">
<button type="submit"
class="bg-blue-500 text-white px-4 py-2 rounded font-medium">Create Team
</button>
</div>
</form>
 
{{-- ... --}}

Here, our primary focus is on this part:

@for($i = 1; $i <= 3; $i++)
<div class="mb-4 grid grid-cols-2">
<div class="w-1/2">
<label class="text-xl text-gray-600" for="players[{{$i}}][id]">Player<span
class="text-red-500">*</span></label>
<select name="players[{{$i}}][id]" id="players[{{$i}}][id]"
class="border-2 border-gray-300 p-2 w-full">
<option value="">Select Player</option>
@foreach($users as $id => $name)
<option value="{{ $id }}"
@selected(old('players.'.$i.'.id') == $id)
>{{ $name }}</option>
@endforeach
</select>
@error('players.'.$i.'.id')
<div class="text-red-500 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
<div class="w-1/2">
<label class="text-xl text-gray-600" for="players[{{$i}}][position]">Position<span
class="text-red-500">*</span></label>
<select name="players[{{$i}}][position]" id="players[{{$i}}][position]"
class="border-2 border-gray-300 p-2 w-full">
<option value="">Select Position</option>
@foreach($positions as $id => $name)
<option value="{{ $id }}"
@selected(old('players.'.$i.'.position') == $id)
>{{ $name }}</option>
@endforeach
</select>
@error('players.'.$i.'.position')
<div class="text-red-500 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
</div>
@endfor

Here, we're using a for loop to generate three players. Each player has two fields: id and position:

  • players[{{$i}}][id] - this will generate players[1][id], players[2][id], players[3][id], etc.
  • players[{{$i}}][position] - this will generate players[1][position], players[2][position], players[3][position], etc.
  • Combination of both these fields will give us a multi-dimensional array that we can validate.
  • To display previous values, we're using @selected() Laravel helper
  • To get the previous field value from old(), we use old('players.'.$i.'.id') and old('players.'.$i.'.position'), which will give us old('players.1.id'), old('players.2.id'), old('players.3.id'), etc...

When this is done, we have a form that looks like this:


The Controller

In our Controller, we will receive a nicely formed array of players like this:

We can loop through each player and do whatever we want with it.


Code for this lesson can be found on GitHub but be aware, that static example was just a temporary solution, so you might need to look in the commit history!