# 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:

```bash
php artisan make:rule Equals
```

Adjust the scaffolding to:

```php
<?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:

```php
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:

```php
Route::post(
    "/subscriptions/{subscription}/appointments", 
    [SubscriptionAppointmentsController::class, 'store']
);
```

and the controller to handle this is as follows:

```php
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:

```php
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:

```php
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](https://stackoverflow.com/a/45763076/4171578) 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](https://laravel.com/docs/10.x/validation#using-rule-objects).

Run the following artisan command to generate the scaffolding:

```bash
php artisan make:rule Equals
```

and then adjust it as follows:

```php
<?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:

```php
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
