Validation Tips (cd ..)
- Inline Validation
- Validate Nested Arrays Easily
- Stop On First Failure
- The "bail" Validation Rule
- Extract Validated Input
- Conditionally Adding Rules
- Normalize Validated Data
- Validate Dates Elegantly
- Exclude Validated Input
- Filter Only Real Emails
- Better Error Messages for Arrays
- The Required If Accepted Rule
- Validate Image Dimensions
- The "prohibitedIf" Rule
- The "sometimes" Validation Rule
- The "distinct" Validation Rule
- Confirm User Password
Laravel Tip 💡: Inline Validation (⬆️)
While Laravel offers a plethora of validation rules, there are times when you need custom ones. These are usually written in a custom class. But, did you know you can perform inline validation also? 🚀
<?php
request()->validate([
'email' => [
'required',
'email',
function ($attribute, $value, $fail) {
if (substr($value, -12) !== '@example.com') {
$fail($attribute, 'The email must belong to example.com domain');
}
},
],
]);
Laravel Tip 💡: Validate Nested Arrays Easily (⬆️)
Sometimes, when validating nested arrays, you may have custom rules that require access to the value of the item being validated. Laravel 9 and onwards comes with 'forEach,' enabling you to do just that 🚀
<?php
use App\Rules\HasPermission;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
$validator = Validator::make($request->all(), [
'companies.*.id' => Rule::forEach(function (string|null $value, string $attribute) {
return [
Rule::exists(Company::class, 'id'),
new HasPermission('manage-company', $value),
];
}),
]);
Laravel Tip 💡: Stop On First Failure (⬆️)
Sometimes, when validating a request, you may want to stop at the first failure. Laravel allows you to do this by setting the "stopOnFirstFailure" property to true on the form request class 🚀
<?php
/**
* Indicates if the validator should stop on the first rule failure.
*
* @var bool
*/
protected $stopOnFirstFailure = true;
Laravel Tip 💡: The "bail" Validation Rule (⬆️)
Sometimes, when validating a field, you may want to stop at the first validation failure. Laravel ships with a rule called "bail" to do exactly that 🚀
<?php
// When validating the title, if the required rule fails,
// Laravel won't test against the other rules.
$request->validate([
'title' => 'bail|required|unique:posts|max:255',
'body' => 'required',
]);
Laravel Tip 💡: Extract Validated Input (⬆️)
When working with validated input, we often need to extract only a few items from the request. Instead of manually unsetting or filtering, use Laravel's "safe()" method to do this elegantly 🚀
<?php
$validated = $request->safe()->only(['name', 'email']);
$validated = $request->safe()->except(['name', 'email']);
$validated = $request->safe()->all();
Laravel Tip 💡: Conditionally Adding Rules (⬆️)
When working with dynamic forms, you might want to validate certain inputs only if another input is checked. Laravel ships with the "exclude_if" validation rule, which does exactly that 🚀
<?php
use Illuminate\Support\Facades\Validator;
// 'appointment_date' and 'doctor_name' will not be validated if
// 'has_appointment' is false
$validator = Validator::make($data, [
'has_appointment' => 'required|boolean',
'appointment_date' => 'exclude_if:has_appointment,false|required|date',
'doctor_name' => 'exclude_if:has_appointment,false|required|string',
]);
Laravel Tip 💡: Normalize Validated Data (⬆️)
Have you ever needed to normalize the validated data before using it? Laravel Form Requests come with a "passedValidation" hook which allows you to tweak the validated data 🚀
<?php
/**
* Handle a passed validation attempt.
*/
protected function passedValidation(): void
{
$this->replace([
'name' => ucwords(strtolower($this->name)),
]);
}
Laravel Tip 💡: Validate Dates Elegantly (⬆️)
Did you know that when validating dates with Laravel, you can pass strings like "today" or "tomorrow" instead of actual dates? This makes the validation rules much more readable 🚀
<?php
// You can use any string supported by strtotime() date validation
$rules = [
'start_date' => 'required|date|after:tomorrow',
'end_date' => 'required|date|after_or_equal:start_date',
'past_date' => 'required|date|before:yesterday',
'deadline' => 'required|date|before_or_equal:today',
];
Laravel Tip 💡: Exclude Validated Input (⬆️)
Sometimes, you may want to exclude an input from the validated array. Instead of manually unsetting it, you can use the "exclude" rule, which does exactly that 🚀
<?php
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'captcha' => 'required|exclude',
]);
dd($validated); // only 'title' and 'body' are set on $validated
}
Laravel Tip 💡: Filter Only Real Emails (⬆️)
Tired of high bounce rates from invalid emails? Laravel comes with the "dns" validation rule to ensure you're getting real emails. It won't magically fix the issue, but it definitely improves deliverability 🚀
<?php
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'email' => 'required|email:dns'
]);
}
// In your tests, make sure to replace fake()->email() with fake()->freeEmail()
// to avoid flaky tests. freeEmail() will always generate an email with
// valid DNS records.
Laravel Tip 💡: Better Error Messages for Arrays (⬆️)
When validating arrays, it's a better UX to tell the user which item has failed rather than throwing a generic message. For this, you can use the ":index" and ":position" placeholders 🚀
<?php
use Illuminate\Support\Facades\Validator;
$validator = Validator::make($request->all(), [
'photos.*.description' => 'required',
], [
'photos.*.description.required' => 'Please describe photo #:position.',
]);
// This will result in "Please describe photo #1".
// You can use :index to start from 0, and if you have deeply nested arrays,
// you can use :second-index, :second-position, :third-index, :third-position
Laravel Tip 💡: The Required If Accepted Rule (⬆️)
When validating forms, sometimes you want to conditionally require a field if another one was filled. Laravel ships with "required_if_accepted" to do exactly that 🚀
<?php
// subscribe_to_newsletter must be true for the email field to be required
$validator = Validator::make($data, [
'subscribe_to_newsletter' => 'boolean',
'email' => 'required_if_accepted:subscribe_to_newsletter|email',
]);
Laravel Tip 💡: Validate Image Dimensions (⬆️)
Ever needed to validate the dimensions of an image, like an avatar? Laravel comes with built-in validation rules for this. You can use the "dimensions" rule to build your validation logic 🚀
<?php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
Validator::make($data, [
'avatar' => [
'required',
Rule::dimensions()->maxWidth(1000)->maxHeight(500)->ratio(3 / 2),
],
]);
Laravel Tip 💡: The "prohibitedIf" Rule (⬆️)
Sometimes, you may want to "prohibit" a field from having data based on a condition, such as the presence of another field. Laravel ships with the "prohibitedIf" rule to do exactly that 🚀
<?php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
// You can pass a bool
Validator::make($request->all(), [
'role_id' => Rule::prohibitedIf($request->user()->is_admin),
]);
// Or a closure if the logic is more complex
Validator::make($request->all(), [
'role_id' => Rule::prohibitedIf(fn () => $request->user()->is_admin),
]);
Laravel Tip 💡: The "sometimes" Validation Rule (⬆️)
Have you ever needed to validate a field only if it's present, but skip it when it’s not? Laravel ships with the "sometimes" validation rule to do exactly that 🚀
<?php
$validator = Validator::make($data, [
// The email will only be validated if it is present in the $data array
'email' => ['sometimes', 'email'],
]);
Laravel Tip 💡: The "distinct" Validation Rule (⬆️)
Have you ever needed to check if an array contains duplicate values? While you can do this manually, it can get slightly messy. Instead, you can use the "distinct" validation rule to do exactly this 🚀
<?php
use Illuminate\Support\Facades\Validator;
$posts = [
['title' => 'First Post'],
['title' => 'Second Post'],
['title' => 'First Post'], // Duplicate title here
['title' => 'Third Post'],
];
// Instead of manually checking if the data contains duplicates
$filteredCount = collect($posts)->pluck('title')->unique();
$filteredCount->count() !== collect($posts)->count(); // true
// You can simply use built in validation rules 🔥
Validator::make($posts, ['*.title' => 'distinct:strict'])->fails(); // true
Laravel Tip 💡: Confirm User Password (⬆️)
Certain actions like deleting an account might require users to confirm their password. While you could implement this logic manually, Laravel ships with a built in validation rule, current_password, to do exactly that 🚀
<?php
public function destroy(Request $request): RedirectResponse
{
$request->validate([
'password' => ['required', 'current_password:web'],
]);
// ...
return to_route('home');
}