Using Unique Validation Rule in Form Request in Laravel

Sometimes you want to make a field unique in a database table so that duplicity should be avoided. The very common case is email address. Laravel provides useful tools to achieve this, including the Unique validation rule. However to make it work as desired you need some tricky configuration which we are going to discuss in this post.

Let’s start with database table definition. Here in this example we will create and update entries in a domains table which has user_id against the user who saves it and a few other fields.

Database Migrations

Here is our Database Migration. In our example we are going to have field name as unique.

Schema::create('domains', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('user_id');
    $table->string('name')->unique();
    $table->boolean('deleted', 1)->default(0);
    $table->timestamps();
});

And run it

php artisan migrate

This will generate an index in the table which will prevent two rows in the table having the same name. This works just fine, we want to handle this exception in a more user friendly manner when someone is trying to save their domain. This is where validation rules come into play.

Form Request Validation

So we have a form that the user has to fill in to create the domain. Before it could be saved to database we have to validate user input either in the controller for store and update methods, or we can use form request validation method. I prefer to use form request validation method as it given us option to keep our controller cleaner and have less code in it.  Also we can keep all validation logic at one place, that is in app/Http/Requests folder.

You can use the artisan command to generate a form request class, in our case we will call it DomainRequest

php artisan make:request DomainRequest

And then we inject the newly created DomainRequest class into your store and update methods in the DomainController

namespace App\Http\Controllers;
use App\Http\Requests\DomainRequest;
use App\Models\Domain;
class DomainController extends Controller
{
    public function store(DomainRequest $request)
    {
    }
    public function update(DomainRequest $request)
    {
    }
}

Now we can add the unique validation rule to the DomainRequest class. We want to make the domain name a required attribute, want to validate it against FQDN regular expression, and then make sure that the name is unique in the domains table. The rule has a format of unique:table,column.

public function rules()
{
    return [
        'name' => [
            "required",
            "regex:/^(?!\-)(?:(?:[a-zA-Z\d][a-zA-Z\d\-]{0,61})?[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}$/",
            "unique:domains"
        ]
    ];
}

If we try and create two domains with the same title then it will return a validation error.

But if we try and update the domain?

When we try and update the domain it will again check that the name is required and that whether there is a domain with the same name!

It checks against the domains table and finds the record we saved previously, which has the same name as the domain we are trying to update, so it returns a validation error.

To get around this we need to update our validation rule to let it know that it needs to check that the name is unique in the table, except against the domain record we have saved previously and which we are trying to update now.

In fact there are two another parameters we can add to the unique validation rule which is except to say which model to ignore. We could write this a couple of ways but I prefer the second as I find it a bit more readable when I come back to it later.

public function rules()
{
    return [
        'name' => [
            "required",
            "regex:/^(?!\-)(?:(?:[a-zA-Z\d][a-zA-Z\d\-]{0,61})?[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}$/",
            "required|unique:domains,name,{$this->domain?->id}"
        ],
    ];
}

Or

use Illuminate\Validation\Rule;

....

public function rules()
{
    return [
        'name' => [
            "required",
            "regex:/^(?!\-)(?:(?:[a-zA-Z\d][a-zA-Z\d\-]{0,61})?[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}$/",
            Rule::unique('domains', 'name')->ignore($this->domain)
        ],
    ];
}

The “name” in the rules in examples above tells the validator to check for uniqueness of the name field value agains entire domains table except its own model!

And finally the store and update methods in the controller looks like:

public function store( DomainRequest $request )
{
    $newDomain = Domain::create( $request->validated() );

    if ( !$newDomain )
    {
        return response(['errors' => 'Domain creation failed'], 422);
    }

    return response([ 'domain' => $newDomain ]);
}
public function update( DomainRequest $request )
{
    $domain->update( $request->validated() );
    return response([ 'domain' => $domain ]);
}

I hope this helps someone.

(Original post can be found here)

Leave a Reply