Courses

Laravel Array Validation: All You Need To Know

Checkboxes: One-Dimensional Array

In this lesson, we'll start with the most common scenario for arrays in forms - checkboxes. We'll create a form that allows us to select multiple players to join a game:

This has to work like this:

  • The game name is required
  • At least three players must be selected
  • The players must exist in the database

But also need to ensure that if the form is invalid, the selected players are still selected. That can become tricky, and we'll explain why.


Validation Rules

Let's begin with an overview of our validation rules and why we need them:

app/Http/Requests/StoreGameRequest.php

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

Here's what we have:

  • name is required, must be a string, and can't be longer than 200 characters. This is pretty straightforward.
  • players is required, as we need it to be filled. It must be an array and must have at least three items. It is important to have this rule; otherwise, if the user doesn't select any players, the players array will be empty, and the validation will pass. We don't want that.
  • players.* is a wildcard rule that applies to all items in the players array. It is required, must be an integer and must exist in the users table. This is the rule that will make sure that the selected players exist in the database.

Or in other words, if we don't have required on our players, it will not check the players.* rule.

But what is this * in our rule? Well, it's a wildcard that expects there to be an index in the array. So if we have an array like this:

$players = [
1 => 1,
2 => 2,
3 => 'abc',
];

It would return an error for the players.3 rule, as it is not an integer. This wildcard is very useful, allowing us to validate arrays of unknown length. And can even be used to validate nested arrays (we'll cover that in a future lesson).


The Form

Validation without proper preparation of the form is nothing, so let's implement our form:

resources/views/games/create.blade.php

{{-- ... --}}
 
<form method="POST" action="{{ route('games.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>
<div class="mb-4 grid grid-cols-2 gap-4">
@foreach($users as $id => $name)
<div class="w-1/2">
<label class="text-xl text-gray-600">
<input type="checkbox" class="border-2 border-gray-300 p-2" name="players[]"
id="players"
value="{{ $id }}" @checked(in_array($id, old('players', [])))>
{{ $name }}
</label>
</div>
@endforeach
</div>
<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 Game
</button>
</div>
</form>
 
{{-- ... --}}

Our primary focus should be on the checkbox list:

@foreach($users as $id => $name)
<div class="w-1/2">
<label class="text-xl text-gray-600">
<input type="checkbox" class="border-2 border-gray-300 p-2" name="players[]"
id="players"
value="{{ $id }}" @checked(in_array($id, old('players', [])))>
{{ $name }}
</label>
</div>
@endforeach

Here we can spot a few things:

  • We're using the players[] name for our checkboxes. This is important, as it will create an array of values when the form is submitted.
  • We're using the @checked() Laravel helper to mark previously selected players as checked.
  • Combining in_array() with old() allows us to check if the player was selected in the previous request. If it were, we'd mark it as checked.
  • Having old('players', []) prevents us from having an error if the players array doesn't exist in the old input. This can happen if the form is submitted without selecting any players or was never submitted before.

The next step is to add an edit form that looks a bit different:

resources/views/games/edit.blade.php

{{-- ... --}}
 
<form method="POST" action="{{ route('games.update', $game->id) }}">
@csrf
@method('PUT')
<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', $game->name) }}">
@error('name')
<div class="text-red-500 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
<div class="mb-4 grid grid-cols-2 gap-4">
@foreach($users as $id => $name)
<div class="w-1/2">
<label class="text-xl text-gray-600">
<input type="checkbox" class="border-2 border-gray-300 p-2" name="players[]"
id="players"
value="{{ $id }}" @checked(in_array($id, old('players', $game->users->pluck('id')->toArray())))>
{{ $name }}
</label>
</div>
@endforeach
</div>
<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">Update Game
</button>
</div>
</form>
 
{{-- ... --}}

Where again, our focus goes to the checkbox list:

@foreach($users as $id => $name)
<div class="w-1/2">
<label class="text-xl text-gray-600">
<input type="checkbox" class="border-2 border-gray-300 p-2" name="players[]"
id="players"
value="{{ $id }}" @checked(in_array($id, old('players', $game->users->pluck('id')->toArray())))>
{{ $name }}
</label>
</div>
@endforeach

It looks almost exactly like the create form, yet instead of passing [] to our old('players') as a default value - we pass the array of players from the $game object. This will allow us to mark previously selected players as checked without having to do any additional checks.


The Controller

If everything was done correctly, we should be able to submit the form and create a new game with players:

This gave us a game name and four players selected in an array. It's as simple as that!


In the next lesson, we will look at validating nested arrays. Still, if you want to play around with this example, you can find the code on GitHub at GameController, StoreGameRequest, and views in resources/views/games folder.

No comments or questions yet...