web-dev-qa-db-fra.com

Symfony2: Comment obtenir des erreurs de validation de formulaire après avoir lié la demande au formulaire

Voici mon code saveAction (où le formulaire passe les données à)

public function saveAction()
{
    $user = OBUser();

    $form = $this->createForm(new OBUserType(), $user);

    if ($this->request->getMethod() == 'POST')
    {
        $form->bindRequest($this->request);
        if ($form->isValid())
            return $this->redirect($this->generateUrl('success_page'));
        else
            return $this->redirect($this->generateUrl('registration_form'));
    } else
        return new Response();
}

Ma question est la suivante: comment puis-je obtenir les erreurs si $form->isValid() renvoie false?

108
putolaruan

Vous avez deux façons possibles de le faire:

  • ne redirige pas l'utilisateur en cas d'erreur et affiche {{ form_errors(form) }} dans le fichier de modèle
  • accéder au tableau d'erreur en tant que $form->getErrors()
116
nefo_x

Symfony 2.3/2.4:

Cette fonction récupère toutes les erreurs. Ceux du formulaire tels que "Le jeton CSRF n'est pas valide. Veuillez essayer de soumettre à nouveau le formulaire." ainsi que des erreurs supplémentaires sur les enfants de formulaire qui n'ont pas d'erreur bouillonnant.

private function getErrorMessages(\Symfony\Component\Form\Form $form) {
    $errors = array();

    foreach ($form->getErrors() as $key => $error) {
        if ($form->isRoot()) {
            $errors['#'][] = $error->getMessage();
        } else {
            $errors[] = $error->getMessage();
        }
    }

    foreach ($form->all() as $child) {
        if (!$child->isValid()) {
            $errors[$child->getName()] = $this->getErrorMessages($child);
        }
    }

    return $errors;
}

Pour obtenir toutes les erreurs sous forme de chaîne:

$string = var_export($this->getErrorMessages($form), true);

Symfony 2.5/3.0:

$string = (string) $form->getErrors(true, false);

Docs:
https://github.com/symfony/symfony/blob/master/UPGRADE-2.5.md#formhttps://github.com/symfony/symfony /blob/master/UPGRADE-3.0.md#form (en bas: The method Form::getErrorsAsString() was removed)

99
Flip

Voici la solution qui a fonctionné pour moi. Cette fonction est dans un contrôleur et retournera un tableau structuré de tous les messages d'erreur et du champ qui les a provoqués.

Symfony 2.0:

private function getErrorMessages(\Symfony\Component\Form\Form $form) {
    $errors = array();
    foreach ($form->getErrors() as $key => $error) {
        $template = $error->getMessageTemplate();
        $parameters = $error->getMessageParameters();

        foreach($parameters as $var => $value){
            $template = str_replace($var, $value, $template);
        }

        $errors[$key] = $template;
    }
    if ($form->hasChildren()) {
        foreach ($form->getChildren() as $child) {
            if (!$child->isValid()) {
                $errors[$child->getName()] = $this->getErrorMessages($child);
            }
        }
    }

    return $errors;
}

Symfony 2.1 et plus récent:

private function getErrorMessages(\Symfony\Component\Form\Form $form) {      
    $errors = array();

    if ($form->hasChildren()) {
        foreach ($form->getChildren() as $child) {
            if (!$child->isValid()) {
                $errors[$child->getName()] = $this->getErrorMessages($child);
            }
        }
    } else {
        foreach ($form->getErrors() as $key => $error) {
            $errors[] = $error->getMessage();
        }   
    }

    return $errors;
}
47
Icode4food

Utilisez le validateur pour obtenir les erreurs d'une entité spécifique

if( $form->isValid() )
{
    // ...
}
else
{
    // get a ConstraintViolationList
    $errors = $this->get('validator')->validate( $user );

    $result = '';

    // iterate on it
    foreach( $errors as $error )
    {
        // Do stuff with:
        //   $error->getPropertyPath() : the field that caused the error
        //   $error->getMessage() : the error message
    }
}

Référence de l'API:

Pour obtenir les messages appropriés (traduisibles), utilisant actuellement SF 2.6.3, voici ma dernière fonction (aucun de ce qui précède ne semble plus fonctionner):

 private function getErrorMessages(\Symfony\Component\Form\Form $form) {      
    $errors = array();
    foreach ($form->getErrors(true, false) as $error) {
        // My personnal need was to get translatable messages
        // $errors[] = $this->trans($error->current()->getMessage());
        $errors[] = $error->current()->getMessage();
    }

    return $errors;
}

comme la méthode Form :: getErrors () retourne maintenant une instance de FormErrorIterator , à moins que vous ne changiez le deuxième argument ($ flatten) en vrai . (Il retournera alors une instance FormError et vous devrez appeler la méthode getMessage () directement, sans la méthode current ():

 private function getErrorMessages(\Symfony\Component\Form\Form $form) {      
    $errors = array();
    foreach ($form->getErrors(true, true) as $error) {
        // My personnal need was to get translatable messages
        // $errors[] = $this->trans($error->getMessage());
        $errors[] = $error->getMessage();
    }

    return $errors;
}

)

Le plus important est en réalité de définir le premier argument sur true afin d’obtenir les erreurs. Laisser le deuxième argument ($ flatten) à sa valeur par défaut (true) renverra FormError , alors qu'il renverra FormErrorIterator instances définies sur false.

19
Cedo

La fonction pour symfony 2.1 et plus récente, sans aucune fonction déconseillée:

/**
 * @param \Symfony\Component\Form\Form $form
 *
 * @return array
 */
private function getErrorMessages(\Symfony\Component\Form\Form $form)
{
    $errors = array();

    if ($form->count() > 0) {
        foreach ($form->all() as $child) {
            /**
             * @var \Symfony\Component\Form\Form $child
             */
            if (!$child->isValid()) {
                $errors[$child->getName()] = $this->getErrorMessages($child);
            }
        }
    } else {
        /**
         * @var \Symfony\Component\Form\FormError $error
         */
        foreach ($form->getErrors() as $key => $error) {
            $errors[] = $error->getMessage();
        }
    }

    return $errors;
}
15
stwe

Pour mes messages flash j'étais heureux avec $form->getErrorsAsString()

Edit (de Benji_X80): Pour SF3, utilisez $form->getErrors(true, false);

15
Tjorriemorrie

Messages d'erreur de formulaire traduits (Symfony 2.1)

J'ai eu beaucoup de mal à trouver cette information, alors je pense qu'il vaut vraiment la peine d'ajouter une note sur la traduction des erreurs de formulaire.

@Icode4food answer retournera toutes les erreurs d'un formulaire. Cependant, le tableau renvoyé ne prend pas en compte pluralité de messages ou translation .

Vous pouvez modifier la boucle foreach de @Icode4food answer pour avoir une liste déroulante:

  • Obtenez toutes les erreurs d'une forme particulière
  • Renvoyer une erreur traduite
  • Prendre en compte la pluralisation si nécessaire

C'est ici:

foreach ($form->getErrors() as $key => $error) {

   //If the message requires pluralization
    if($error->getMessagePluralization() !== null) {
        $errors[] = $this->container->get('translator')->transChoice(
            $error->getMessage(), 
            $error->getMessagePluralization(), 
            $error->getMessageParameters(), 
            'validators'
            );
    } 
    //Otherwise, we do a classic translation
    else {
        $errors[] = $this->container->get('translator')->trans(
            $error->getMessage(), 
            array(), 
            'validators'
            );
    }
}

Cette réponse a été élaborée à partir de 3 messages différents:

4
Mick

Messages d'erreur de formulaire traduits (Symfony2.3)

Ma version de la résolution du problème:

/src/Acme/MyBundle/Resources/config/services.yml

services:
    form_errors:
        class: Acme\MyBundle\Form\FormErrors

/src/Acme/MyBundle/Form/FormErrors.php

<?php
namespace Acme\MyBundle\Form;

class FormErrors
{
    public function getArray(\Symfony\Component\Form\Form $form)
    {
        return $this->getErrors($form);
    }

    private function getErrors($form)
    {
        $errors = array();

        if ($form instanceof \Symfony\Component\Form\Form) {

            // соберем ошибки элемента
            foreach ($form->getErrors() as $error) {

                $errors[] = $error->getMessage();
            }

            // пробежимся под дочерним элементам
            foreach ($form->all() as $key => $child) {
                /** @var $child \Symfony\Component\Form\Form */
                if ($err = $this->getErrors($child)) {
                    $errors[$key] = $err;
                }
            }
        }

        return $errors;
    }
}

/src/Acme/MyBundle/Controller/DefaultController.php

$form = $this->createFormBuilder($entity)->getForm();
$form_errors = $this->get('form_errors')->getArray($form);
return new JsonResponse($form_errors);

Dans Symfony 2.5 vous pouvez obtenir toutes les erreurs de champs très facilement:

    $errors = array();
    foreach ($form as $fieldName => $formField) {
        foreach ($formField->getErrors(true) as $error) {
            $errors[$fieldName] = $error->getMessage();
        }
    }
3
Lebnik

Vous pouvez également utiliser le service de validation pour obtenir des violations de contrainte:

$errors = $this->get('validator')->validate($user);
3
antoinet

SYMFONY 3.X

Les autres méthodes SF 3.X indiquées ici ne fonctionnaient pas pour moi car je pouvais soumettre des données vides au formulaire (mais j'ai des contraintes NotNull/NotBlanck). Dans ce cas, la chaîne d'erreur ressemblerait à ceci:

string(282) "ERROR: This value should not be blank.
ERROR: This value should not be blank.
ERROR: This value should not be blank.
ERROR: This value should not be blank.
ERROR: This value should not be blank.
ERROR: This value should not be null.
name:
    ERROR: This value should not be blank.
"

Ce qui n'est pas très utile. Alors j'ai fait ceci:

public function buildErrorArray(FormInterface $form)
{
    $errors = [];

    foreach ($form->all() as $child) {
        $errors = array_merge(
            $errors,
            $this->buildErrorArray($child)
        );
    }

    foreach ($form->getErrors() as $error) {
        $errors[$error->getCause()->getPropertyPath()] = $error->getMessage();
    }

    return $errors;
}

Ce qui retournerait ça:

array(7) {
  ["data.name"]=>
  string(31) "This value should not be blank."
  ["data.street"]=>
  string(31) "This value should not be blank."
  ["data.zipCode"]=>
  string(31) "This value should not be blank."
  ["data.city"]=>
  string(31) "This value should not be blank."
  ["data.state"]=>
  string(31) "This value should not be blank."
  ["data.countryCode"]=>
  string(31) "This value should not be blank."
  ["data.organization"]=>
  string(30) "This value should not be null."
}
3
sbouba

Si vous utilisez des validateurs personnalisés, Symfony ne renvoie pas les erreurs générées par ces validateurs dans $form->getErrors(). $form->getErrorsAsString() renverra toutes les erreurs dont vous avez besoin, mais sa sortie est malheureusement formatée sous forme de chaîne, pas de tableau.

La méthode que vous utilisez pour obtenir toutes les erreurs (quelle que soit leur origine) dépend de la version de Symfony que vous utilisez.

La plupart des solutions suggérées impliquent la création d'une fonction récursive qui analyse tous les formulaires enfants et extrait les erreurs pertinentes dans un tableau. Symfony 2.3 n’a pas la fonction $form->hasChildren(), mais il a $form->all().

Voici une classe d’aide pour Symfony 2.3, que vous pouvez utiliser pour extraire toutes les erreurs de n’importe quel formulaire. (Il est basé sur le code d'un commentaire de yapro sur un ticket de bogue associé dans le compte github de Symfony.)

namespace MyApp\FormBundle\Helpers;

use Symfony\Component\Form\Form;

class FormErrorHelper
{
    /**
     * Work-around for bug where Symfony (2.3) does not return errors from custom validaters,
     * when you call $form->getErrors().
     * Based on code submitted in a comment here by yapro:
     * https://github.com/symfony/symfony/issues/7205
     *
     * @param Form $form
     * @return array Associative array of all errors
     */
    public function getFormErrors($form)
    {
        $errors = array();

        if ($form instanceof Form) {
            foreach ($form->getErrors() as $error) {
                $errors[] = $error->getMessage();
            }

            foreach ($form->all() as $key => $child) {
                /** @var $child Form */
                if ($err = $this->getFormErrors($child)) {
                    $errors[$key] = $err;
                }
            }
        }

        return $errors;
    }
}

Code d'appel:

namespace MyApp\ABCBundle\Controller;

use MyApp\FormBundle\Helpers;

class MyController extends Controller
{
    public function XYZAction()
    {
        // Create form.

        if (!$form->isValid()) {
            $formErrorHelper = new FormErrorHelper();
            $formErrors = $formErrorHelper->getFormErrors($form);

            // Set error array into twig template here.
        }
    }

}
2
Jay Sheth

Sur la base de la réponse de @Jay Seth, j'ai créé une version de la classe FormErrors spécialement pour Ajax Forms:

// src/AppBundle/Form/FormErrors.php
namespace AppBundle\Form;

class FormErrors
{

    /**
     * @param \Symfony\Component\Form\Form $form
     *
     * @return array $errors
     */
    public function getArray(\Symfony\Component\Form\Form $form)
    {
        return $this->getErrors($form, $form->getName());
    }

    /**
     * @param \Symfony\Component\Form\Form $baseForm
     * @param \Symfony\Component\Form\Form $baseFormName
     *
     * @return array $errors
     */
    private function getErrors($baseForm, $baseFormName) {
        $errors = array();
        if ($baseForm instanceof \Symfony\Component\Form\Form) {
            foreach($baseForm->getErrors() as $error) {
                $errors[] = array(
                    "mess"      => $error->getMessage(),
                    "key"       => $baseFormName
                );
            }

            foreach ($baseForm->all() as $key => $child) {
                if(($child instanceof \Symfony\Component\Form\Form)) {
                    $cErrors = $this->getErrors($child, $baseFormName . "_" . $child->getName());
                    $errors = array_merge($errors, $cErrors);
                }
            }
        }
        return $errors;
    }
}

Utilisation (par exemple dans votre action):

$errors = $this->get('form_errors')->getArray($form);

Version de Symfony: 2.8.4

Exemple de réponse JSON:

{
    "success": false,
    "errors": [{
        "mess": "error_message",
        "key": "RegistrationForm_user_firstname"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_user_lastname"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_user_email"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_user_zipCode"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_user_password_password"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_terms"
    }, {
        "mess": "error_message2",
        "key": "RegistrationForm_terms"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_marketing"
    }, {
        "mess": "error_message2",
        "key": "RegistrationForm_marketing"
    }]
}

L'objet d'erreur contient le champ "clé", qui est l'id de l'élément DOM d'entrée, vous permettant ainsi de renseigner facilement des messages d'erreur.

Si vous avez des formulaires enfants à l'intérieur du parent, n'oubliez pas d'ajouter l'option cascade_validation à l'intérieur du setDefaults du formulaire parent.

2
RobbeR

Pour Symfony 3.2 et supérieur, utilisez ceci,

public function buildErrorArray(FormInterface $form)
{
    $errors = array();

    foreach ($form->getErrors() as $key => $error) {
        if ($form->isRoot()) {
            $errors['#'][] = $error->getMessage();
        } else {
            $errors[] = $error->getMessage();
        }
    }

    foreach ($form->all() as $child) {
        if (!$child->isValid()) {
            $errors[$child->getName()] = (string) $child->getErrors(true, false);
        }
    }
    return $errors;
}

Utilisez str_replace si vous voulez vous débarrasser du texte 'Error:' ennuyant dans chaque texte de description d'erreur.

$errors[$child->getName()] = str_replace('ERROR:', '', (string) $child->getErrors(true, false));
2
Anjana Silva

Je suis venu avec cette solution. Il fonctionne parfaitement avec le dernier Symfony 2.4.

Je vais essayer de donner quelques explications.

Utiliser un validateur séparé

Je pense que c'est une mauvaise idée d'utiliser une validation séparée pour valider des entités et renvoyer des messages de violation de contrainte, comme suggéré par d'autres auteurs.

  1. Vous devrez valider manuellement toutes les entités, spécifier des groupes de validation, etc. Avec des formes hiérarchiques complexes, ce n'est pas du tout pratique et vous échapperez rapidement.

  2. De cette façon, vous validerez le formulaire deux fois: une fois avec formulaire et une fois avec un validateur séparé. C'est une mauvaise idée du point de vue des performances.

Je suggère d’itérer récursivement le type de formulaire avec ses enfants pour collecter les messages d’erreur.

Utilisation de certaines méthodes suggérées avec une instruction IF exclusive

Certaines réponses suggérées par d'autres auteurs contiennent des instructions SI mutuellement exclusives, telles que: if ($form->count() > 0) ou if ($form->hasChildren()).

Autant que je sache, chaque formulaire peut comporter des erreurs, ainsi que des enfants. Je ne suis pas un expert du composant Symfony Forms , mais dans la pratique, vous n'obtiendrez pas certaines erreurs de la forme, comme Erreur de protection CSRF ou champs supplémentaires erreur. Je suggère de supprimer cette séparation.

Utilisation de la structure de résultats dénormalisée

Certains auteurs suggèrent de placer toutes les erreurs dans un tableau simple. Ainsi, tous les messages d'erreur du formulaire lui-même et de ses enfants seront ajoutés au même tableau avec différentes stratégies d'indexation: basées sur le nombre pour les propres erreurs de type et sur celles basées sur le nom pour les enfants. Je suggère d'utiliser une structure de données normalisée de la forme:

errors:
    - "Self error"
    - "Another self error"

children
    - "some_child":
        errors:
            - "Children error"
            - "Another children error"

        children
            - "deeper_child":
                errors:
                    - "Children error"
                    - "Another children error"

    - "another_child":
        errors:
            - "Children error"
            - "Another children error"

De cette façon, le résultat peut être facilement itéré plus tard.

Ma solution

Alors voici ma solution à ce problème:

use Symfony\Component\Form\Form;

/**
 * @param Form $form
 * @return array
 */
protected function getFormErrors(Form $form)
{
    $result = [];

    // No need for further processing if form is valid.
    if ($form->isValid()) {
        return $result;
    }

    // Looking for own errors.
    $errors = $form->getErrors();
    if (count($errors)) {
        $result['errors'] = [];
        foreach ($errors as $error) {
            $result['errors'][] = $error->getMessage();
        }
    }

    // Looking for invalid children and collecting errors recursively.
    if ($form->count()) {
        $childErrors = [];
        foreach ($form->all() as $child) {
            if (!$child->isValid()) {
                $childErrors[$child->getName()] = $this->getFormErrors($child);
            }
        }
        if (count($childErrors)) {
            $result['children'] = $childErrors;
        }
    }

    return $result;
}

J'espère que ça va aider quelqu'un.

1
Slava Fomin II

$ form-> getErrors () fonctionne pour moi.

1
ahyong

À partir de Symfony 2.1, à utiliser avec l'affichage de l'erreur Twig, j'ai modifié la fonction pour ajouter un FormError au lieu de les récupérer, ce qui vous permet de mieux contrôler les erreurs et de ne pas avoir à utiliser error_bubbling sur chaque entrée. Si vous ne le définissez pas de la manière suivante, {{form_errors (form)}} restera vide:

/**
 * @param \Symfony\Component\Form\Form $form
 *
 * @return void
 */
private function setErrorMessages(\Symfony\Component\Form\Form $form) {      

    if ($form->count() > 0) {
        foreach ($form->all() as $child) {
            if (!$child->isValid()) {
                if( isset($this->getErrorMessages($child)[0]) ) {
                    $error = new FormError( $this->getErrorMessages($child)[0] );
                    $form->addError($error);
                }
            }
        }
    }

}
1

SYMFONY 3.1

J'ai simplement implémenté une méthode statique pour gérer l'affichage des erreurs

static function serializeFormErrors(Form\Form $form)
{
    $errors = array();
    /**
     * @var  $key
     * @var Form\Form $child
     */
    foreach ($form->all() as $key => $child) {
        if (!$child->isValid()) {
            foreach ($child->getErrors() as $error) {
                $errors[$key] = $error->getMessage();
            }
        }
    }

    return $errors;
}

Dans l'espoir d'aider

1
Shigiang Liu

Pour Symfony 2.1:

Ceci est ma solution finale en rassemblant de nombreuses autres solutions:

protected function getAllFormErrorMessages($form)
{
    $retval = array();
    foreach ($form->getErrors() as $key => $error) {
        if($error->getMessagePluralization() !== null) {
            $retval['message'] = $this->get('translator')->transChoice(
                $error->getMessage(), 
                $error->getMessagePluralization(), 
                $error->getMessageParameters(), 
                'validators'
            );
        } else {
            $retval['message'] = $this->get('translator')->trans($error->getMessage(), array(), 'validators');
        }
    }
    foreach ($form->all() as $name => $child) {
        $errors = $this->getAllFormErrorMessages($child);
        if (!empty($errors)) {
           $retval[$name] = $errors; 
        }
    }
    return $retval;
}
0
Fernando P. G.