Back to Blog
Jan 10th, 2014

Accessing and Debugging Symfony Form Errors

Accessing and Debugging Symfony Form Errors

I recently saw an old post on StackOverflow called How to get form validation errors after binding the request to the form. It has a lot of answers, most of them are only partially right and a lot are outdated. So, I wanted to look at the right answer, and why it's that way :).

Tip

To see real examples of using forms and customizing form rendering, start off with our Symfony2 Series (Episode 2 and Episode 4.

Debugging

First, if you're trying to figure out what errors you have and which field each is attached to, you should use the Form::getErrorsAsString() method that was introduced in Symfony 2.1 (so a long time ago!). Use it temporarily in a controller to see what's going on::

public function formAction(Request $request)
{
    $form = $this->createForm(new ProductType());

    $form->handleRequest($request);
    if ($form->isValid()) {
        // ... form handling
    }

    var_dump($form->getErrorsAsString());die;

    $this->render(
        'DemoBundle:Product:form.html.twig',
        array('form' => $form->createView())
    );
}

That's it. To make things even simpler, you also have the Form panel of the web debug toolbar in Symfony 2.4. So, debugging form errors

Why $form->getErrors() doesn't Work

Tip

As of Symfony 2.5, we have a new tool! $form->getErrors(true) will return all of the errors from all of the fields on your form.

Raise your hand virtually if you've tried doing this to debug a form::

public function formAction(Request $request)
{
    $form = $this->createForm(new ProductType());

    $form->handleRequest($request);
    if ($form->isValid()) {
        // ... form handling
    }

    var_dump($form->getErrors());die;

    $this->render(
        'DemoBundle:Product:form.html.twig',
        array('form' => $form->createView())
    );
}

What do you get? Almost definitely an empty array, even when the form has lots of errors. Yea, I've been there too.

The reason is that a form is actually more than just one Form object: it's a tree of Form objects. Each field is represented by its own Form object and the errors for that field are stored on it.

Assuming the form has name and price fields, how can we get the errors for each field?

$globalErrors = $form->getErrors()
$nameErrors = $form['name']->getErrors();
$priceErrors = $form['price']->getErrors();

By saying $form['name'], we get the Form object that represents just the name field, which gives us access to just the errors attached to that field. This means that there's no one spot to get all of the errors on your entire form. Saying $form->getErrors() gives you only "global" errors, i.e. errors that aren't attached to any specific field (e.g. a CSRF token failure).

Render all the Errors at the Top of your Form

Tip

As of Symfony 2.5, you can use $form->getErrors(true) to get an array of all the errors in your form. Yay!

One common question is how you might render all of your errors in one big list at the top of your form. Again, there's no one spot where you can get a big array of all of the errors, so you'd need to build it yourself::

// a simple, but incomplete (see below) way to collect all errors
$errors = array();
foreach ($form as $fieldName => $formField) {
    // each field has an array of errors
    $errors[$fieldName] = $formField->getErrors();
}

We can iterate over $form (a Form object) to get all of its fields. And again, remember that each field ($formField here), is also a Form object, which is why we can call Form::getErrors() on each.

In reality, since a form can be many-levels deep, this solution isn't good enough. Fortunately, someone already posted a more complete one on Stack Overflow (see the 2.1 version).

From here, you can pass these into your template and render each. Of course, you'll need to make sure that you don't call {{ form_errors() }} on any of your fields, since you're printing the errors manually (and remember that form_row calls form_errors automatically).

Tip

Each field also has an error_bubbling option. If this is set to true (it defaults to false for most fields), then the error will "bubble" and attach itself to the parent form. This means that if you set this option to true for every field, all errors would be attached to the top-level Form object and could be rendered by calling {{ form_errors(form) }} in Twig.

Accessing Errors Inside Twig

We can also do some magic in Twig with errors using magical things called form variables. These guys are absolutely fundamental to customizing how your forms render.

Tip

If you're new to form theming and variables and need to master them, check out Episode 4 of our Symfony series.

Normally, field errors are rendered in Twig by calling form_errors on each individual field:

{{ form_errors(form) }}

{{ form_label(form.name) }}
{{ form_widget(form.name) }}
{{ form_errors(form.name) }}

Tip

The form_row function calls form_errors internally.

Just like in the controller, the errors are attached to the individual fields themselves. Hopefully it make sense now why form_errors(form) renders global errors and form_errors(form.name) renders the errors attached to the name field.

Tip

Once you're in Twig, each field (e.g. form, form.name) is an instance of :symfonyclass:Symfony\\Component\\Form\\FormView.

If you need to customize how the errors are rendered, you can always use form theming. But you can also do it by leverage form variables:

{{ form_errors(form) }}

{{ form_label(form.name) }}
{{ form_widget(form.name) }}

{% if form.name.vars.errors|length > 0 %}
<ul class="form-errors name">
    {% for error in form.name.vars.errors %}
        {{ error }}
    {% endfor %}
</ul>
{% endif %}

The key here is form.name.vars: a strange array of "variables" that you have access to on every field. One of the variables you have access to is errors, but there are many others, like label and required. Each variable is normally used internally to render the field, but you can use them manually if you need to:

<label for="form.name.vars.id">
    {{ form.name.vars.label }} {{ form.name.vars.required ? '*' : '' }}
</label>

To find out what variables a field has, just dump them:

{{ dump(form.price.vars) }}

Now that you know about the magic form.vars, you could also use it to render all validation errors at the top of your form. This will only work for simple forms (i.e. forms without nested fields), which most forms are:

{% set messages = [] %}
{% for child in form.children %}
    {% for error in child.vars.errors %}
        {% set messages = messages|merge([error.message]) %}
    {% endfor %}
{% endfor %}
{% if messages|length %}
    <ul>
        {% for message in messages %}
            <li>{{ message }}</li>
        {% endfor %}
    </ul>
{% endif %}

Tip

When you are form theming, these variables become accessible in your form theme template as local variables inside the form blocks (e.g. simply label or id).

Takeaways

The key lesson is this: each form is a big tree. The top level Form has children, each which is also a Form object (or a FormView object when you're in Twig). If you want to access information about a field (is it required? what are its errors?), you need to first get access to the child form and go from there.

Learn More

Stuck on other Symfony topics or want to learn Symfony from the context of an actual project? Check out our Getting Started with Symfony Series and cut down on your learning curve!

8 Comments

Sort By
Login or Register to join the conversation
Default user avatar Lebnik 6 years ago

Thank you. I get all fields errors on next code:
```
$errors = array();
foreach ($form as $fieldName => $formField) {

foreach ($formField->getErrors(true) as $error) {

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

}

}

2 | Reply |
Default user avatar Mathieu Dierckx Lebnik 6 years ago

Nice, simple solution thanks.
The only error it will not get is when the anti-csrf token is not valid anymore.

Anyways it helped me to send errors in JSON response.

| Reply |

The csrf token should be stored on the top-level form - so you can get it (and any other global errors) by adding this to the "list" of errors: $form->getErrors().

Cheers!

| Reply |
Default user avatar Justin Finkelstein 6 years ago

I spent a bit of time working out how to compile all of the errors into a block which could be displayed outside of each form element, so I'm hoping sharing this might save someone a bit of time. It's not the most efficient way of doing things, but should work for simple forms:

{% block form_errors %}
{% spaceless %}
{% for child in form.children %}
{% if child.vars.errors|length > 0 %}
{% for error in child.vars.errors %}

{{ error.message }}


{% endfor %}
{% endif %}
{% if child.children|length >0 %}
{{ form_errors(child) }}
{% endif %}
{% endfor %}
{% endspaceless %}
{% endblock %}

To enable this, override the form_row:

{% block form_row %}
{% spaceless %}
{{ form_label(form) }}
{{ form_widget(form) }}
{% endspaceless %}
{% endblock form_row %}

| Reply |
Default user avatar SimpleWeek 6 years ago

>>> As of Symfony 2.5, you can use $form->getErrors(true) to get an array of all the errors in your form. Yay!

This is awesome! I can't believe.

| Reply |
Default user avatar eMeRiKa 6 years ago

Thank you for your article Form::getErrorsAsString() helps me a lot.

I have a form with a collection of gameType, my gameType has a collection of opponentType. With error_bubbling => false I can put the error of the opponents level but I can't go deeper. Is there a limitation with error_bubbling ?

games:
0:
ERROR: This value is too long. It should have 20 characters or less.
opponents:
0:
score:
No errors

| Reply |

Hey @disqus_mL8NmUKLb8!

I don't fully understand what you want to do, but I will say that "error_bubbling" defaults to false, on almost every field by default. That makes sense, because with error_bubbling false, it says "don't attach this error to the parent, attach it to this exact field". So, if you're setting error_bubbling to false, you're probably not doing anything - that's likely already the default for that field.

Even with the collection field type, the errors should show up attached to the field that caused them. So for example, if your opponentType has a "name" field that must have less than 20 characters, that error should show up next to *that* field - it won't (by default) show up at a higher level. From your code, it looks like you somehow have an error on an individual field (e.g. error on opponentType), but that this error is somehow being attached at the game level (instead of at the opponents.0.name level). If that's the case - it's not error_bubbling to blame - you have something weird going on. It could be that the validation error is occurring on a field that is *not* in your form (e.g. you have an Assert\Length on a name field, but name is not in this form). In that case, the form framework is forced to attach the error to the parent field. Check into this.

If I haven't answered your question (I couldn't *quite* understand the situation, it has some complexities!), feel free to add more details and I'll do my best :).

Cheers!

| Reply |
Default user avatar webDEVILopers 6 years ago

I've added a PR to the symfony cookbook to improve the docs. Feel free to vote for it here: github.com/symfony/symfony-...

| Reply |

Delete comment?

Share this comment

astronaut with balloons in space

"Houston: no signs of life"
Start the conversation!