[symfony] Symfony2 : How to get form validation errors after binding the request to the form

Here's my saveAction code (where the form passes the data to)

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

My question is: how do I get the errors if $form->isValid() returns false?

This question is related to symfony

The answer is


For Symfony 3.2 and above use this,

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

Use str_replace if you want to get rid of the annoying 'Error:' text in each error description text.

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

Translated Form Error Messages (Symfony2.3)

My version of solving the problem:

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

In Symfony 2.5 you can get all fields errors very easy:

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

For my flash messages I was happy with $form->getErrorsAsString()

Edit (from Benji_X80): For SF3 use $form->getErrors(true, false);


For Symfony 2.1 onwards for use with Twig error display I altered the function to add a FormError instead of simply retrieving them, this way you have more control over errors and do not have to use error_bubbling on each individual input. If you do not set it in the manner below {{ form_errors(form) }} will remain blank:

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

}

Translated Form Error Messages (Symfony2.1)

I have been struggling a lot to find this information so I think it's definitely worth adding a note on translation of form errors.

@Icode4food answer will return all errors of a form. However, the array that is returned does not take into account either message pluralization or translation.

You can modify the foreach loop of @Icode4food answer to have a combo:

  • Get all errors of a particular form
  • Return a translated error
  • Take pluralization into account if necessary

Here it is:

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

This answer has been put together from 3 different posts:


Symfony 3 and newer

I recently made a function which creates a tree of form errors. This will be helpful for returning list of errors back to front-end. This is based on form types having:

'error_bubbling' => false

Code:

public static function getFormErrorsTree(FormInterface $form): array
{
    $errors = [];

    if (count($form->getErrors()) > 0) {
        foreach ($form->getErrors() as $error) {
            $errors[] = $error->getMessage();
        }
    } else {
        foreach ($form->all() as $child) {
            $childTree = self::getFormErrorsTree($child);

            if (count($childTree) > 0) {
                $errors[$child->getName()] = $childTree;
            }
        }
    }

    return $errors;
}

Output:

Array
(
    [name] => Array
        (
            [0] => This value is not valid.
        )

    [emails] => Array
        (
            [0] => Array
                (
                    [0] => Given e-mail is not valid.
                    [1] => Given e-mail is not valid #2.
                )
            [1] => Array
                (
                    [0] => Given e-mail is not valid.
                    [1] => Given e-mail is not valid #2.
                )

        )

)

Notice: I know that errors from deeper level fields can be overwritten if higher level has errors, but this is on purpose for my usage.


Based on @Jay Seth's answer, I made a version of FormErrors class especially for 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;
    }
}

Usage (e.g. in your action):

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

Symfony version: 2.8.4

Example JSON response:

{
    "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"
    }]
}

The error object contains the "key" field, which is the id of the input DOM element, so you can easily populate error messages.

If you have child forms inside the parent, don't forget to add the cascade_validation option inside the parent form's setDefaults.


To get proper (translatable) messages, currently using SF 2.6.3, here is my final function (as none of above's seem to work anymore):

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

as the Form::getErrors() method now returns an instance of FormErrorIterator, unless you switch the second argument ($flatten) to true. (It will then return a FormError instance, and you will have to call the getMessage() method directly, without the current() method:

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

)

The most important thing is actually to set the first argument to true in order to get the errors. Leaving the second argument ($flatten) to his default value (true) will return FormError instances, while it will return FormErrorIterator instances when set to false.


For Symfony 2.1:

This is my final solution putting in together many others 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;
}

You can also use the validator service to get constraint violations:

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

For Symfony 5 I got this working :

public function getErrorMessages(Form $form) {
  
    $errors = [];        

    foreach ($form->all() as $child) {
        foreach ($child->getErrors() as $error) {
                $name = $child->getName();
                $errors[$name] = $error->getMessage();
        }
    }

    return $errors;
}

I got this kind of JSON thing with it :

errors: {
    firstname: "The firstname must be between 2 and 30 characters long.",
    lastname: "The lastname must be between 2 and 40 characters long."
    subject: "Please choose a subject."
    text: "Empty messages are not allowed."
}

It's nice and sweet enough for me, but I guess it will not pick up errors at the global level, like timed out tokens or other stuff like that. Anyway, I've been quite a bit longtime looking for a solution suiting my case, and I'm happy contributing here hoping it'll help some developer somewhere sometime somehow.


Below is the solution that worked for me. This function is in a controller and will return a structured array of all the error messages and the field that caused them.

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 and newer:

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

I came up with this solution. It works solid with the latest Symfony 2.4.

I will try to give some explanations.

Using separate validator

I think it's a bad idea to use separate validation to validate entities and return constraint violation messages, like suggested by other writers.

  1. You will need to manually validate all the entities, specify validation groups, etc, etc. With complex hierarchical forms it's not practical at all and will get out of hands quickly.

  2. This way you will be validating form twice: once with form and once with separate validator. This is a bad idea from the performance perspective.

I suggest to recursively iterate form type with it's children to collect error messages.

Using some suggested methods with exclusive IF statement

Some answers suggested by another authors contain mutually exclusive IF statements like this: if ($form->count() > 0) or if ($form->hasChildren()).

As far as I can see, every form can have errors as well as children. I'm not expert with Symfony Forms component, but in practice you will not get some errors of the form itself, like CSRF protection error or extra fields error. I suggest to remove this separation.

Using denormalized result structure

Some authors suggest to put all errors inside of a plain array. So all the error messages of the form itself and of it's children will be added to the same array with different indexing strategies: number-based for type's own errors and name-based for children errors. I suggest to use normalized data structure of the form:

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"

That way result can be easily iterated later.

My solution

So here's my solution to this problem:

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

I hope it'll help someone.


Symfony 2.3 / 2.4:

This function get's all the errors. The ones on the form like "The CSRF token is invalid. Please try to resubmit the form." as well as additional errors on the form children which have no error bubbling.

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

To get all errors as a string:

$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#form https://github.com/symfony/symfony/blob/master/UPGRADE-3.0.md#form (at the bottom: The method Form::getErrorsAsString() was removed)


The function for symfony 2.1 and newer, without any deprecated function:

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

SYMFONY 3.1

I simply implemented a static method to handle the display of errors

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

Hoping to help


SYMFONY 3.X

Other SF 3.X methods given here did not work for me because I could submit empty data to the form (but I have NotNull/NotBlanck constraints). In this case the error string would look like this :

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.
"

Which is not very usefull. So I made this:

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

Which would return that :

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."
}

If you're using custom validators, Symfony doesn't return errors generated by those validators in $form->getErrors(). $form->getErrorsAsString() will return all the errors you need, but its output is unfortunately formatted as a string, not an array.

The method you use to get all the errors (regardless of where they came from), depends on which version of Symfony you're using.

Most of the suggested solutions involve creating a recursive function that scans all child forms, and extracts the relevant errors into one array. Symfony 2.3 doesn't have the $form->hasChildren() function, but it does have $form->all().

Here is a helper class for Symfony 2.3, which you can use to extract all errors from any form. (It's based on code from a comment by yapro on a related bug ticket in Symfony's github account.)

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

Calling code:

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.
        }
    }

}

Use the Validator to get the errors for a specific entity

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

API reference:


$form->getErrors() works for me.