web-dev-qa-db-fra.com

Ajout d'erreurs de validation personnalisées à Laravel forme

J'ai un formulaire de base configuré pour permettre à un utilisateur de modifier son adresse e-mail et j'effectue la validation suivante avant de modifier l'e-mail:

// 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();
}

Cela fonctionne bien, mais après cette validation de base, j'aimerais vérifier si l'utilisateur a fourni un mot de passe correct. Pour ce faire, je procède comme suit avec la bibliothèque d'authentification de base de Laravel:

// 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");
}

Plutôt que de gérer la logique pour indiquer à l'utilisateur que son mot de passe est incorrect, je préfère simplement ajouter une erreur de formulaire à l'entrée password pour qu'il s'affiche comme une validation de formulaire normale. Quelque chose comme ça:

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();
}

Ainsi, l'erreur de mot de passe incorrect s'affichera à côté de mon mot de passe et ressemblera à une validation de formulaire appropriée.

Comment puis-je faire ceci?

11
John Dorean

Voir la réponse de Darren Craig.

Une façon de le mettre en œuvre cependant.

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

Il y a un problème avec la réponse acceptée (et le validateur de Laravel en général, à mon avis): le processus de validation lui-même et la détection du statut de validation sont fusionnés en une seule méthode. 

Si vous rendez aveuglément tous les messages de validation du sac, ce n'est pas grave. Mais si vous avez une logique supplémentaire qui détecte si le validateur a échoué ou non et effectue des actions supplémentaires (telles que l'envoi de messages texte internationaux pour les champs de formulaire validés actuels), vous avez un problème. 

Manifestation:

    // 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???

Également, 

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

donne les mêmes résultats. Pourquoi? Parce que si vous regardez dans le code du validateur de Laravel, vous trouverez ce qui suit:

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

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

Comme vous pouvez le constater, la méthode fails() efface le sac en perdant tous les messages que vous avez ajoutés, laissant ainsi le validateur supposer qu'il n'y a pas d'erreur. 

Il n'y a aucun moyen d'ajouter des erreurs au validateur existant et de le faire échouer. Vous pouvez uniquement créer un nouveau validateur avec des erreurs personnalisées comme ceci:

    $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

Si vous n'aimez pas l'idée d'abuser de la règle de validation required pour les erreurs ajoutées personnalisées, vous pouvez toujours étendre Laravel Validator avec des règles personnalisées. J'ai ajouté une règle générique failkey et l'ai rendue obligatoire de la manière suivante:

    // 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;
}

Et je peux l'utiliser comme ça:

    $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

Bien sûr, c'est toujours une façon détournée de contourner le problème. 

Idéalement, je modifierais la conception du validateur afin de séparer clairement sa phase de validation de la détection de statut (méthodes distinctes pour validate() et passes() ou mieux isValid()) et ajouter des méthodes pratiques pour faire échouer manuellement un champ spécifique avec une règle spécifique. Bien que cela puisse aussi être considéré comme un hacky, nous n'avons toujours pas d'autre choix si nous voulons utiliser le validateur Laravel non seulement avec les propres règles de validation de Laravel, mais également avec nos règles de logique métier personnalisées.

8
JustAMartin

Syntaxe alternative:

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

De plus, il pourrait être utile d’ajouter la fonction Redirect::back() suivante:

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

Selon 

L'alpha

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

2
Pathros

J'ai résolu un problème similaire avec la validation et la validation personnalisée. Dans mon cas, je dois vérifier que le fichier téléchargé avec le formulaire est une image valide ainsi que les données de publication. Par conséquent, je dois exécuter un test de validation du fichier et les tests de validation des données de publication. J'étais un problème lorsque j'ai essayé de renvoyer mes données de validation personnalisées. Seules les erreurs de validation de Laravel étaient présentes. Selon @JustAMartin post, j'ai codé une solution qui montre toutes les erreurs. 

    //Creem una instància del validador. Açò ens permet manipular-lo
    $validator = Validator::make($request->all(), [
        'nomCompanyia' => 'required',
        'urlCompanyia' => 'url'
    ]);

    $imageError = false;
    $imgOriginal = null;
    $imgMitjana = null;
    $imgXicoteta = null;
    $fallaValidacio = !$validator->passes(); //-> Retorna true si cap error, false en cas contrari.

    if($request->hasFile('logoCompanyia') && !$fallaValidacio)
    {
        $imatge = $request->file('logoCompanyia');

        if($imatge->isValid() && $this->verificaExtensionsImatges($imatge->getClientOriginalExtension(), $imatge->guessExtension()))
        {
            $sPath = $imatge->store('images/companyies/', 'public');
            $fullPathOriginal = public_path() . "/storage/" . $sPath;
            $fpInfo = pathinfo($fullPathOriginal);
            $imgOriginal = sprintf("%s.%s", $fpInfo['filename'], $fpInfo['extension']);

            //Crear les miniatures
            $mitjana = Image::make($fullPathOriginal)->widen(300, function ($constraint) {
                $constraint->upsize();
            });

            $imgMitjana = sprintf("%s_300.%s", $fpInfo['filename'], $fpInfo['extension']);
            $mitjana->save($fpInfo['dirname'] . '/' . $imgMitjana);

            $xicoteta = Image::make($fullPathOriginal)->widen(100, function ($constraint) {
                $constraint->upsize();
            });

            $imgXicoteta = sprintf("%s_100.%s", $fpInfo['filename'], $fpInfo['extension']);
            $xicoteta->save($fpInfo['dirname'] . '/' . $imgXicoteta);
        }
        else
        {
            $imageError = true;
            $validator->getMessageBag()->add('logoCompanyia', "Sembla que el fitxer d'imatge no és vàlid o està corrupte. Només s'accepten els formats: .jpg, .jpeg, .png, .gif");
        }
    }
    else
    {
        $imageError = true;
        $validator->getMessageBag()->add('logoCompanyia', "Sembla que el fitxer d'imatge no és vàlid o ha sigut rebutjat per el servidor si és massa gran.");
    }

    if($fallaValidacio || $imageError)
    {
        $data['mode'] = "nou";
        $data['urlFormulari'] = "administracio/companyies/afegir";
        $data['nomCompanyia'] = $request->nomCompanyia;
        $data['idCompanyia'] = 0;
        $data['urlCompanyia'] = $request->urlCompanyia;
        $data['logoCompanyia'] = $request->logoCompanyia;
        $data['errors'] = (object) $validator->errors();

        return view($this->formulariTemplate, $data);
    }

    $companyia = new Companyies();
    $companyia->nom = $request->nomCompanyia;
    $companyia->url = $request->urlCompanyia;
    $companyia->logo_original = $imgOriginal;
    $companyia->logo_300 = $imgMitjana;
    $companyia->logo_100 = $imgXicoteta;

    $companyia->save();

Comme vous pouvez le constater, je ne fais qu'un appel à $ validator-> pass () et je stocke le résultat dans une variable. Lorsque j'appelle cette méthode, tous les tests Laravel sont effectués. Si elles sont transmises ou non, le résultat est stocké dans la variable, vous pouvez donc tester votre variable ultérieurement. Cela permet de faire les tests sur le fichier pour déterminer finalement si toutes les données sont correctes ou non. 

S'il y a des erreurs, je redirige avec l'assistant view (), en ajoutant toutes les données: entrée et erreurs. S'il n'y a pas d'erreur, le comportement normal de la méthode est poursuivi. 

0
Guillermo Espert

Si vous utilisez des appels ajax, n'oubliez pas de lancer une ValidationException.

if ($subscribed) {
    $validator->errors()->add('email', __('Your email is already subscribed.'));
    throw new ValidationException($validator);
}
0
Toni Almeida

Je comprends pourquoi vous le souhaitez, mais du point de vue de la sécurité, il est généralement déconseillé de renvoyer un message indiquant si le nom d'utilisateur et/ou le mot de passe est incorrect. Cela permettrait à un pirate informatique de savoir s’il a obtenu le nom d’utilisateur ou le mot de passe correct.

Il est préférable de renvoyer un message générique du type "Vos informations d'identification sont incorrectes", que vous ne voudriez de toute façon pas afficher à côté de vos champs.

0
Darren Craig