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

Select Dropdowns: "No Duplicates" Example

In a previous lesson, we looked at checkboxes, and now it's time to check the Select dropdowns. They are very similar to checkboxes:

Here are our conditions:

  • Each player has to have a place
  • Player places can't be repeated

And, of course, if the validation fails - we need to pre-select the selected players.


Validation Rules

Our validation rules are pretty straightforward:

app/Http/Requests/StoreGameWinnerRequest.php

// ...
 
public function rules(): array
{
$gamePlayersCount = $this->route('game')->users->count();
 
return [
'players' => ['required', 'array', 'min:' . $gamePlayersCount],
'players.*' => ['required', 'integer', 'distinct', 'min:1', 'max:' . $gamePlayersCount],
];
}
 
// ...

Here's what we have:

  • players must be present as an array and have a minimum count of our players count of the game.
  • players.* must be selected as an integer and, must be distinct (no duplicates), must be between 1 and the count of our players.

As with checkboxes, we need to have required on the players field. Otherwise, the players.* rule will not be checked.

As a bonus, here we will specify cleaner error messages, as the default ones are not that clear for the end user:

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

app/Http/Requests/StoreGameWinnerRequest.php

// ...
 
public function messages()
{
return [
'players.*.required' => 'Please select a winner for each place.',
'players.*.distinct' => 'Please select a different winner for each place.',
];
}

This allows us to specify custom messages for each rule. In our case, we care about required and distinct rules. The * wildcard will apply the message to all items in the players array, so we don't have to specify each.

Submitting a form with no winners selected will now show a friendly error message:

And in case of duplicate values, we'll see this:


The Form

Next is the actual form. We'll use the same approach as with checkboxes, but this time we'll use a select dropdown:

resources/views/games/winners.blade.php

{{-- ... --}}
 
<form method="POST" action="{{ route('game.winners.update', $game->id) }}">
@csrf
 
@foreach($game->users as $user)
<div class="mb-4 grid grid-cols-2 gap-4">
<div class="w-1/2">{{ $user->name }}</div>
<div class="w-1/2">
<select name="players[{{$user->id}}]" id="players[{{$user->id}}]"
class="border-2 border-gray-300 p-2 w-full">
<option value="">Select Place</option>
@foreach($places as $place => $name)
<option value="{{ $place }}" @selected(old('players.' . $user->id, $user->pivot->place) == $place)>{{ $name }}</option>
@endforeach
</select>
@error('players.'.$user->id)
<div class="text-red-500 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
</div>
@endforeach
 
<div class="mb-4">
<button type="submit"
class="bg-blue-500 text-white px-4 py-2 rounded font-medium">Update Player Places
</button>
</div>
</form>
 
{{-- ... --}}

And here are the important things to see:

  • We use players[{{$user->id}}] as the name of the select. This will create an array of players, where the key is the user ID and the value is the place.
  • We're using the @selected() Laravel helper to check if the current place is selected.
  • We're using the old() helper to pre-select the selected values before the form was submitted.
  • Since we have a pivot table, we use a pivot value to set the default value. This is done with old('players.' . $user->id, $user->pivot->place).

The Controller

Once this form is submitted, we should see that the validation worked and our data is correct:


If you want to play around with this example, you can find the code on GitHub at GameWinnerController, StoreGameWinnerRequest and resources/views/games/winners.blade.php.