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:
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.
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.@selected()
Laravel helper
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:
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!