Laravel Validation: Validating Input as Equal to a specific value or PHP Variable
TL; DR;
As of version 10, Laravel does not have a built-in way to validate that a field is equal to a specific value. However, you can easily create a custom rule to accomplish this.
Create scaffolding for the rule with artisan:
php artisan make:rule Equals
Adjust the scaffolding to:
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class Equals implements ValidationRule
{
public function __construct(public $comparedValue)
{
}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if ($value != $this->comparedValue) {
$fail('The :attribute must be equal to ' . (string) $this->comparedValue . '.');
}
}
}
Use the Rule as follows:
Validator::make([
'subscription_status' => $subscription->status
], [
'subscription_status' => [
new Equals('active'),
],
])->validate();
Why you may need to validate that a field is equal to a specific field or PHP variable
I like to work with Laravel Validation outside of a typical context of validating input data submitted in a form. For example, in my app, Subscription
object determines attributes like price, preferred duration, time of the day, etc. of Appointment
s that belong to it. The endpoint for creating Appointment
s looks like like:
Route::post(
"/subscriptions/{subscription}/appointments",
[SubscriptionAppointmentsController::class, 'store']
);
and the controller to handle this is as follows:
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Project;
class SubscriptionAppointmentsController extends Controller
{
public function store(Request $request, Subscription $subscription)
{
Validator::make($request->all(), [
'dates' => [
'required',
'array',
],
]);
// loop through appointments, and create on each of provided dates
// ...
}
}
🤔 What if I want to make sure that Appointments are scheduled only within active Subscriptions?
Conventional approach: after
validation hook
If we stick to the conventional approach and allow ourselves to use built-in validation rules for fields submitted in the form we will have to do something like this:
public function store(Request $request, Subscription $subscription)
{
Validator::make($request->all(), [
'dates' => [
'required',
'array',
],
])->after(function ($validator) use ($subscription) {
if ($subscription->status != 'active') {
$validator->errors()->add(
'subscription_status',
'Appointments can only be scheduled within active subscriptions'
);
}
})->validate();
// loop through appointments, and create on each of provided dates
// ...
}
There's nothing wrong with this approach. It may be a preferred solution if the logic for determining if Subscription
is active is more complex. For example, I would reach out for this solution if I had to call some method on Subscription
, like: $subscription->isActive()
. But if we're doing a simple string comparison, there are less verbose solutions.
"Native" Laravel solution: in
rule
We can simplify things a little bit by moving the Subscription
status to validation data, and using the "in
" validation rule:
public function store(Request $request, Subscription $subscription)
{
Validator::make(array_merge($request->all(), [
'subscription_status' => $subscription->status`
]), [
'dates' => [
'required',
'array',
],
'subscription_status' => [
'in:active',
],
])->validate();
// loop through appointments, and create on each of provided dates
// ...
}
This is the fastest, and perfectly valid solution, and it is the current go-to answer on StackOverflow on that topic. It only has 2 minor (very cosmetic) problems associated with it:
we're getting a generic error message on failure:
The selected subscription status is invalid.
it "feels weird" to use the equivalent of
in_array()
, when we know the array has only one value
Solution for purists: Custom validation rule
If you want to go the extra mile, and have clearer error messages for your users, I suggest creating a custom validation rule.
Run the following artisan command to generate the scaffolding:
php artisan make:rule Equals
and then adjust it as follows:
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class Equals implements ValidationRule
{
public function __construct(public $comparedValue)
{
}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if ($value != $this->comparedValue) {
$fail('The :attribute must be equal to ' . (string) $this->comparedValue . '.');
}
}
}
With this rule, we're able to refactor our controller method to:
use App\Rules\Equals;
public function store(Request $request, Subscription $subscription)
{
Validator::make(array_merge($request->all(), [
'subscription_status' => $subscription->status
]), [
'dates' => [
'required',
'array',
],
'subscription_status' => [
new Equals('active'),
],
])->validate();
// loop through appointments, and create on each of provided dates
// ...
}
We can pass any value or variable to our Equals
rule, as long as it is castable to string.
Conclusion
While using Laravel Validation to check that one value is equal to specific value may not be a very common scenario, I run into it quite frequently. There are 3 most common solutions I usually reach out for in such cases:
use
after
validation hookuse
in:
validation rulecreate custom validation rule