When you use one of the built-in validation rules that Laravel provides, you can use a string to refer to it in your code: 'required'
, 'email'
, and so on.
Last week, a question came up in the Mastering Laravel community about how to register a custom validation rule to work in the same way. I've never had a need to do it, but this dev was doing it for a package they built, and wanted the more native Laravel experience for the rule this package provides.
To provide a concrete example, let's say I have a custom PhoneNumber
rule class and I want to access it as 'phone_number'
in my validation rules.
If you're using the older (and now deprecated) Rule
interface which has a passes()
method on it, you can wire it up like this:
// app/Providers/AppServiceProvider.php
public function boot(): void
{
$this->app['validator']->extend('phone_number', PhoneNumber::class . '@passes');
}
That's fairly straightforward, and the dev who asked the question already knew this.
His question was more nuanced: How do you do this with the newer ValidationRule
interface that uses the validate()
method instead?
That is a bit trickier because this newer interface requires a $fail
callback to be passed in.
How should you define that?
And how would you wire that up in the app service provider?
Before I show the solution, I want to say up front that there are a couple of things about it that I don't particularly like. And to keep this tip short, I won't get into them today, but stick with me. We will explore this topic in more depth throughout the week.
With that disclaimer out of the way, and after looking at how Laravel internally wires up these custom rules, here is the solution I came up with:
// app/Providers/AppServiceProvider.php
public function boot(): void
{
$this->app['validator']->extend('phone_number', function($attribute, $value, $parameters, $validator) {
return InvokableValidationRule::make(new PhoneNumber())
->setValidator($validator)
->passes($attribute, $value);
});
}
This works! But wow, this is definitely more verbose than the previous solution.
On top of that, why am I still calling the passes()
method from the older, deprecated interface?
And where is the $fail
callback I said we needed?
Stay tuned throughout the week for a deeper dive into this topic, and all will be explained.
Here to help,
Joel
P.S. In my opinion, validation is one of the most under-appreciated aspects of Laravel. Check out Mastering Laravel Validation Rules to learn more.