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 Appointments that belong to it. The endpoint for creating Appointments 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 hook

  • use in: validation rule

  • create custom validation rule