Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel 5.4 - How to use multiple error messages for the same custom validation rule

In order to reuse code, I created my own validator rule in a file named ValidatorServiceProvider :

class ValidatorServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Validator::extend('checkEmailPresenceAndValidity', function ($attribute, $value, $parameters, $validator) {
            $user = User::where('email', $value)->first();
            
            // Email has not been found
            if (! $user) {
                return false;
            }
            
            // Email has not been validated
            if (! $user->valid_email) {
                return false;
            }
            
            return true;
        });
    }

    public function register()
    {
        //
    }
}

And I use this rule like this :

public function rules()
{
    return [
        'email' => 'bail|required|checkEmailPresenceAndValidity'
    ];
}

But, I want to set different error messages for each case, something like this :

if (! $user) {
    $WHATEVER_INST->error_message = 'email not found';
    return false;
}
        
if (! $user->valid_email) {
    $WHATEVER_INST->error_message = 'invalid email';
    return false;
}

But I don't figure out how to achieve this without doing 2 different rules ...
Of course it could work with multiple rules but it will also perform multiple SQL queries, and I really want to avoid that.
Also, keep in mind that in real case I could have more than 2 validations like theses in a single rule.

Does anyone have an idea ?

=====
EDIT 1 :

Actually, I think that I want something that works in a similar way to the between or size rules.
They represent one single rule, but provide multiple error messages :

'size'                 => [
    'numeric' => 'The :attribute must be :size.',
    'file'    => 'The :attribute must be :size kilobytes.',
    'string'  => 'The :attribute must be :size characters.',
    'array'   => 'The :attribute must contain :size items.',
],

Laravel checks if the value represents a numeric, a file, a string or an array ; and gets the right error message to use.
How do we achieve this kind of thing with custom rule ?

like image 262
Marc Avatar asked Jun 22 '17 09:06

Marc


1 Answers

Unfortunately Laravel doesn't currently provide a concrete way to add and call your validation rule directly from your attribute params array. But that does not exclude a potential and friendly solution based on Trait and Request usage.

Please find below my solution for example.

First thing is to wait for the form to be processed to handle the form request ourselves with an abstract class. What you need to do is to get the current Validator instance and prevent it from doing further validations if there's any relevant error. Otherwise, you'll store the validator instance and call your custom user validation rule function that you'll create later :

<?php

namespace App\Custom\Validation;

use \Illuminate\Foundation\Http\FormRequest;

abstract class MyCustomFormRequest extends FormRequest
{
    /** @var \Illuminate\Support\Facades\Validator */
    protected $v = null;

    protected function getValidatorInstance()
    {
        return parent::getValidatorInstance()->after(function ($validator) {
            if ($validator->errors()->all()) {
                // Stop doing further validations
                return;
            }
            $this->v = $validator;
            $this->next();
        });
    }

    /**
     * Add custom post-validation rules
     */
    protected function next()
    {
        
    }
}

The next step is to create your Trait which will provide the way to validate your inputs thanks to the current validator instance and handle the correct error message you want to display :

<?php

namespace App\Custom\Validation;

trait CustomUserValidations
{
    protected function validateUserEmailValidity($emailField)
    {
        $email = $this->input($emailField);

        $user = \App\User::where('email', $email)->first();
        
        if (! $user) {
            return $this->v->errors()->add($emailField, 'Email not found');
        }
        if (! $user->valid_email) {
            return $this->v->errors()->add($emailField, 'Email not valid');
        }

        // MORE VALIDATION POSSIBLE HERE
        // YOU CAN ADD AS MORE AS YOU WANT
        // ...
    }
}

Finally, do not forget to extend your MyCustomFormRequest. For example, after your php artisan make:request CreateUserRequest, here's the easy way to do so :

<?php

namespace App\Http\Requests;

use App\Custom\Validation\MyCustomFormRequest;
use App\Custom\Validation\CustomUserValidations;

class CreateUserRequest extends MyCustomFormRequest
{
    use CustomUserValidations;

    /**
     * Add custom post-validation rules
     */
    public function next()
    {
        $this->validateUserEmailValidity('email');
    }

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'email' => 'bail|required|email|max:255|unique:users',
            'password' => 'bail|required',
            'name' => 'bail|required|max:255',
            'first_name' => 'bail|required|max:255',
        ];
    }
}

I hope that you'll find your way in what I suggest.

like image 78
Sense Avatar answered Sep 16 '22 15:09

Sense