Laravel 5.5 validation ruleception (rule inside rule)
Laravel’s 5.5 Validation Rules are awesome. They let you add custom validations into your code in an organized way.
I think they were made as a simple atomic validation rule, that only deals with one validation at a time. That’s why they only take one attribute — value; they are supposed to replace the string validations.
For more complicated and heavy attribute linked validations, they recommend creating a custom Request and add validation there…
But I still needed to make a simple validation: validate the user’s phone if it had changed.
I could fetch the user and append to the validation string: 'unique:users'
to the ‘phone’ attribute only if the request()->input('phone’)
is defined and not equal to auth()->user()->phone
defined, but it’d get messy really soon.
Laravel’s validation Rule
With php artisan make:rule PhoneUnique
we can now start to build this rule. We’ll have to take the user in the constructor, and then, on passes()
, call the ‘unique:users’ rule only if the user’s phone changed.
use Illuminate\Support\Facades\Validator;
use Illuminate\Contracts\Validation\Rule;
class PhoneUnique implements Rule
{
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function passes($attribute, $value)
{
// If it hasn't changed, then it passes
if (!$value || $this->player->phone === $value) {
return true;
}
$validator = Validator::make([
'phone' => $value
], [
'phone' => 'unique:users'
]);
return $validator->passes();
}
public function message()
{
return 'Player\'s phone must be unique.';
}
}
So now it’s simpler. 🙂
$user = auth()->user();
request()->validate([
'phone' => ['required', new PhoneUnique($user)]
]);
Make it prettier
As I know that I’ll be using a lot of this in my project (the ValidPhone rule can be digits + check for country code + check for length + not duplicated), I figured it’d be nice to have it reusable. That’s why I made BaseRule that has that functionality:
Now extending this base class on your rules, you’ll be able to nest validations with other rules.
Example:
use Illuminate\Contracts\Validation\Rule;
class PlayerPhoneUnique extends BaseRule implements Rule
{
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function passes($attribute, $value)
{
if (!$value || $this->player->phone === $value) {
return true;
}
// Needs the attribute parameter cause that's the field used by unique
return $this->validate($value, 'unique:players', $attribute);
}
public function message()
{
return 'Player\'s phone must be unique.';
}
}
Now, as validate stores the validator, you can add multiple rules, and then on message()
you can access it and check for it’s errors.
More examples
- Multiple rules on same validator
public function passes($attribute, $value)
{
return $this->validate($value, [
'required', 'digits:10', new PhoneUnique($this->user)
], $attribute);
}
- Multiple validators
public function passes($attr, $value)
{
if (! $this->validate($value, ['required', 'digits:10'], $attr)
) {
return false;
}
return $this->validate($value,
new PhoneUnique($this->user), $attr);
}
Custom message using validation errors
The last invalid should be the one returned on getValidator()
public function message()
{
$errors = $this->getValidator()->errors();
if ($errors->any()) {
return $errors->all();
}
return 'Some other error';
}
Hope it was useful.