Today will conclude a three-part series on registering a custom class-based rule with a simple string-based rule name. If you haven't read part one with the solution or part two describing how rules are resolved, I recommend you do that first.
The fundamental reason this whole discussion got started was because the older Rule
interface has been deprecated in favor of the newer ValidationRule
interface.
But here is where it gets a little bizarre: Even if you're dutifully using the newer interface, Laravel is translating it back into the older interface before calling it.
Check out this chunk of code in the ValidationRuleParser
class:
if ($rule instanceof InvokableRule || $rule instanceof ValidationRule) {
$rule = InvokableValidationRule::make($rule);
}
Notice how our newer ValidationRule
gets converted into an InvokableValidationRule
?
That class implements the older Rule
interface, which is why we can call the passes()
method on it from our service provider extension.
Also notice inside InvokableValidationRule::passes()
, it builds out that $fail
callback for you:
$this->invokable->{$method}($attribute, $value, function ($attribute, $message = null) {
$this->failed = true;
return $this->pendingPotentiallyTranslatedString($attribute, $message);
});
I considered just using that same code, but it felt too brittle to copy/paste that $fail
definition into my app service provider.
Honestly though, it also feels a little weird that Laravel is converting the newer interface back into the older one anyway. I imagine at some future point when the older interface is completely removed, this will all get refactored, and my solution back in part one will need to adjust as well.
But for now, this is the solution that works. And it gave us an excuse to dig deeper into Laravel source code and get familiar with how validation rules work.
Here to help,
Joel
P.S. Join the Mastering Laravel Community and you too can ask questions which may send me off on a deep dive into the Laravel source code.