In this post I’m going to show you how you can validate foreign keys in your request form objects by calling:
I’m digging it. It’s just a simple extension on the
exists rule, but I think it is killer.
Find Or Fail?
When I started with Laravel, I always found it quick and easy to utilise the Eloquent query builder’s
findOrFail method to ensure that any foreign keys I was submitted from a user were valid. Most of the time these foreign keys are coming from a HTML select drop-down in some web form. Of course we want to fail to make sure we don’t attempt to insert, into the database, a foreign key that doesn’t exist (from some tricksy little hobbits’zs that decide they want to wreak havoc in our apps), but was this the best approach?
Then I got thinking, really the
findOrFail method is just for resources in the URL. That being said - I really don’t care what type of error I send someone being malicious.
Let’s say we have a user in the database with an id of
2. That user has a foreign key of
If this user is being updated by calling
PATCH: /users/2, but the request contains a
role_id value that does not exists, it doesn’t really make sense that the response would be a 404 error because that user does exist. They should, of course, receive a validation error. But
findOrFail did serve its general purpose for me in the early days.
Keeping all your validation in a form object makes things much easier to maintain. If I’m using a form object and still utilising
findOrFail in the controller for my
role_id foreign key, obviously I haven’t validated my data. I should trust that those keys exist, and can just lean on the
find method instead.
Luckily, Laravel offers a handy validation rule
exists to make this easy. As an initial example, lets say we are creating a password reset controller. The request coming in will contain an email address, and we need to check that the email address exists in the database for some user. We might do something like:
This will hit the
users table and search through the
Exists with Foreign Key, the Stringy Way
Translating this to a foreign key rule is pretty straightforward. We just need to specify the table and column.
My current project has a
Subscription class. The user has a
subscription_id foreign key field. Let’s see how we could validate a submitted
subscription_id value using the exists rule:
Here we are saying, ensure that the request value of
subscription_id exists in the
subscriptions table by looking for it in the
Perfect! Exactly what we were after. But wait - there’s more!
Wouldn’t it be annoying if we change our table name or primary key column name later on? Then we’d have to update these strings manually. But we could always just access those values from an Eloquent instance instead:
Hmmm. Although it works, I can’t handle this. It’s just to messy 😷
Exists with Foreign Key, the Fluent Way
Let’s utilise Laravel’s built in fluent validation rules. These are nice helpers around some of the more complex validation rules to make it easier to do more complex computation within the rules.
Okay, now we are getting somewhere, but I think we can do better, never settle for second best…that’s why I use Laravel 🌶
Extending The Validator
Let’s solidify our concept of foreign key validation to make this really clear by extending the Laravel validator, you should do this in a service provider.
We’re going to allow you to pass in an Eloquent instance or a class string. It’s going to be awesome…
Using The Foreign Key Validation Rule
We can now access our new rule in our form validation. To kick things off, let’s do it the stringy way - but hang around - cause it’s about to get sexy up in here.
Sure - so that will work fine, but again - we can do better! There are a couple of ways we can do this. The first is to extend the
Illuminate\Validation\Rule class and add the method there, or add a macro to it, as it utilises the
Macroable trait, but I’m going to use this as a chance to flaunt the Laravel Validation Rule package I made.
Once you have that installed, just extend the package like so:
You should do this just after you extend the validator in your service provider.
Now we have added all the magic sauce, lets see it in action. When we want to validate that a foreign key exists, we can simply use:
or assuming we already had an instance in the
- You will want to make sure to add a custom error message. Check out the docs to see how to do that.