Adding custom validation errors to Laravel form

60,473

Solution 1

See Darren Craig's answer.

One way to implement it though.

// inside if(Auth::validate)
if(User::where('email', $email)->first())
{
    $validator->getMessageBag()->add('password', 'Password wrong');
}
else
{
    $validator->getMessageBag()->add('email', 'Email not found');
}

Solution 2

There is one problem with the accepted answer (and Laravel's Validator in general, in my opinion) - the validation process itself and validation status detection is merged into one method.

If you blindly render all validation messages from the bag, it's no big deal. But if you have some additional logic that detects if the validator has failed or not and does additional actions (such as feeding international text messages for current validated form fields), then you have a problem.

Demonstration:

    // let's create an empty validator, assuming that we have no any errors yet
    $v = Validator::make([], []);

    // add an error
    $v->errors()->add('some_field', 'some_translated_error_key');
    $fails = $v->fails(); // false!!! why???
    $failedMessages = $v->failed(); // 0 failed messages!!! why???

Also,

    $v->getMessageBag()->add('some_field', 'some_translated_error_key');

yields the same results. Why? Because if you look into Laravel's Validator code, you will find the following:

public function fails()
{
    return ! $this->passes();
}

public function passes()
{
    $this->messages = new MessageBag;

As you can see, fails() method essentially clears away the bag losing all the messages you have appended, and thus making the validator assume that there are no errors.

There is no way to append errors to existing validator and make it fail. You can only create a new validator with custom errors like this:

    $v = Validator::make(['some_field' => null],
            ['some_field' => 'Required:some_translated_error_key']);
    $fails = $v->fails(); // true
    $failedMessages = $v->failed(); // has error for `required` rule

If you don't like the idea of abusing required validation rule for custom appended errors, you can always extend Laravel Validator with custom rules. I added a generic failkey rule and made it mandatory this way:

    // in custom Validator constructor: our enforced failure validator
    array_push($this->implicitRules, "Failkey");

    ...


/**
 * Allows to fail every passed field with custom key left as a message
 * which should later be picked up by controller
 * and resolved with correct message namespaces in validate or failValidation methods
 *
 * @param $attribute
 * @param $value
 * @param $parameters
 *
 * @return bool
 */
public function validateFailkey($attribute, $value, $parameters)
{
    return false; // always fails
}

protected function replaceFailkey($message, $attribute, $rule, $parameters)
{
    $errMsgKey = $parameters[0];

    // $parameters[0] is the message key of the failure
    if(array_key_exists($errMsgKey, $this->customMessages)){
        $msg = $this->customMessages[$parameters[0]];
    }       
    // fallback to default, if exists
    elseif(array_key_exists($errMsgKey, $this->fallbackMessages)){
        return $this->fallbackMessages[$parameters[0]];
    }
    else {
        $msg = $this->translator->trans("validation.{$errMsgKey}");
    }

    // do the replacement again, if possible
    $msg = str_replace(':attribute', "`" . $this->getAttribute($attribute) 
            . "`", $msg);

    return $msg;
}

And I can use it like this:

    $v = Validator::make(['some_field' => null],
            ['some_field' => 'failkey:some_translated_error_key']);
    $fails = $v->fails(); // true
    $failedMessages = $v->failed(); // has error for `Failkey` rule

Of course, that's still a hacky way to work around the issue.

Ideally, I would redesign the Validator to clearly separate its validation phase from status detection (separate methods for validate() and passes() or better isValid()) and also add convenience methods to manually fail specific field with specific rule. Although that also might be considered hacky, but still we have no other choice if we want to use Laravel validator not only with Laravel's own validation rules, but also our custom business logic rules.

Solution 3

Moreover, it could be helpful to add the following Redirect::back() function:

$validator->getMessageBag()->add('password', 'Password wrong');    
return Redirect::back()->withErrors($validator)->withInput();

According to

The Alpha

(http://heera.it/laravel-manually-invalidate-validation#.VVt7Wfl_NBc)

Solution 4

Alternate syntax:

$validator->errors()
          ->add('photos', 'At least one photo is required for a new listing.');

Solution 5

user Matt K said in a comment that laravel has since implemented validation hooks, which does exactly what we want:

$validator = Validator::make(...);

$validator->after(function ($validator) {
    if ($this->somethingElseIsInvalid()) {
        $validator->errors()->add('field', 'Something is wrong with this field!');
    }
});

if ($validator->fails()) {
    // this actually runs! even if the original validator succeeded!
}
Share:
60,473
John Dorean
Author by

John Dorean

Updated on July 24, 2022

Comments

  • John Dorean
    John Dorean almost 2 years

    I have a basic form set up to allow a user to change their email address, and I'm doing the following validation on it before I change the email:

    // Set up the form validation
    $validator = Validator::make(
        Input::all(),
        array(
            'email' => 'email|unique:users',
            'password' => 'required'
        )
    );
    
    // If validation fails, redirect to the settings page and send the errors
    if ($validator->fails())
    {
        return Redirect::route('settings')->withErrors($validator)->withInput();
    }
    

    This works fine, however after this basic validation I'd like to check if the user supplied a correct password. To do so, I'm doing the following with Laravel's basic authentication library:

    // Find the user and validate their password
    $user = Auth::user();
    
    if (!Auth::validate(array('username' => $user->username, 'password' => Input::get('password'))))
    {
        die("failed to authenticate");
    }
    

    Rather than handling the logic to tell the user their password is incorrect myself, I'd rather just add a form error to the password input so it shows up just like regular form validation. Something like so:

    if (!Auth::validate(array('username' => $user->username, 'password' => Input::get('password'))))
    {
        $validator->addError('password', 'That password is incorrect.');
        return Redirect::route('settings')->withErrors($validator)->withInput();
    }
    

    That way, the incorrect password error will show next to my password input and look like proper form validation.

    How can I do this?