Skip to content

Commit 24094dc

Browse files
committed
Merge pull request knpuniversity#8 from knpuniversity/form-errors
Blog post about Form errors
2 parents 9088cd6 + 8299ce0 commit 24094dc

File tree

3 files changed

+221
-1
lines changed

3 files changed

+221
-1
lines changed

index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ The Blog
1010
rest-revisited
1111
rss
1212
requirejs-bower-grunt
13-
13+
symfony-debugging-form-errors

metadata.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,13 @@ blogs:
6666
check out a way to organize things that may be even better!
6767
author: weaverryan
6868
category: tech
69+
70+
"symfony-debugging-form-errors":
71+
title: Accessing and Debugging Symfony Form Errors
72+
published: 2014-01-10
73+
preview: |
74+
Inspired by an old StackOverflow post, we'll investigate why
75+
it's so hard to get *all* of the errors of a form and unlock
76+
some new tricks by understanding the form hierarchy.
77+
author: weaverryan
78+
category: tech

symfony-debugging-form-errors.rst

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
Accessing and Debugging Symfony Form Errors
2+
===========================================
3+
4+
I recently saw an old post on StackOverflow called
5+
`How to get form validation errors after binding the request to the form`_.
6+
It has a lot of answers, most of them are only partially right and a lot
7+
are outdated. So, I wanted to look at the *right* answer, and why it's that
8+
way :).
9+
10+
.. tip::
11+
12+
To see real examples of using forms and customizing form rendering, start
13+
off with our Symfony2 Series (`Episode 2`_ and `Episode 4`_ especially).
14+
15+
Debugging
16+
---------
17+
18+
First, if you're trying to figure out what errors you have and which fields
19+
each is attached to, you should use the
20+
:symfonymethod:`Form::getErrorsAsString() <Symfony\\Component\\Form\\Form::getErrorsAsString>`
21+
method that was introduced in Symfony 2.1 (so a long time ago!). Use it temporarily
22+
in a controller to see what's going on:
23+
24+
public function formAction(Request $request)
25+
{
26+
$form = $this->createForm(new ProductType());
27+
28+
$form->handleRequest($request);
29+
if ($form->isValid()) {
30+
// ... form handling
31+
}
32+
33+
var_dump($form->getErrorsAsString());die;
34+
35+
$this->render(
36+
'DemoBundle:Product:form.html.twig',
37+
array('form' => $form->createView())
38+
);
39+
}
40+
41+
That's it. To make things even simpler, you also have the `Form panel`_ of
42+
the web debug toolbar in Symfony 2.4. So, debugging form errors
43+
44+
Why $form->getErrors() doesn't Work
45+
-----------------------------------
46+
47+
Raise your hand virtually if you've tried doing this to debug a form::
48+
49+
public function formAction(Request $request)
50+
{
51+
$form = $this->createForm(new ProductType());
52+
53+
$form->handleRequest($request);
54+
if ($form->isValid()) {
55+
// ... form handling
56+
}
57+
58+
var_dump($form->getErrors());die;
59+
60+
$this->render(
61+
'DemoBundle:Product:form.html.twig',
62+
array('form' => $form->createView())
63+
);
64+
}
65+
66+
What do you get? Almost definitely an empty array, even when the form has
67+
lots of errors.
68+
69+
The reason is that a form is actually more than just one ``Form`` object:
70+
it's a tree of ``Form`` object. Each field is represented by its *own* ``Form``
71+
object and the errors for that field are stored on it.
72+
73+
Assuming the form has ``name`` and ``price`` fields, how can we get the errors
74+
for each field?
75+
76+
.. code-block::
77+
78+
$globalErrors = $form->getErrors()
79+
$nameErrors = $form['name']->getErrors();
80+
$priceErrors = $form['price']->getErrors();
81+
82+
By saying ``$form['name']``, we get the ``Form`` object that represents *just*
83+
the ``name`` field, which gives us access to *just* the errors attached to
84+
that field. This means that there's no *one* spot to get *all* of the errors
85+
on your entire form. Saying ``$form->getErrors()`` gives you only "global"
86+
errors: errors that aren't attached to any specific field (e.g. a CSRF token
87+
failure).
88+
89+
Render all the Errors at the Top of your Form
90+
---------------------------------------------
91+
92+
One common question is how you might render all of your errors in one big
93+
list at the top of your form. Again, there's no *one* spot where you can
94+
get a big array of *all* of the fields, so you'd need to build it yourself::
95+
96+
// a simple, but incomplete way to collect all errors
97+
$errors = array();
98+
foreach ($form as $fieldName => $formField) {
99+
$errors[$fieldName] = $formField->getErrors();
100+
}
101+
102+
Hopefully you understand the idea of going over each field to collect all
103+
of the errors together. In reality, since a form can be many-levels deep,
104+
this solution isn't good enough. Fortunately, someone already posted a more
105+
complete on on `Stack Overflow`_ (see the 2.1 version).
106+
107+
From here, you can pass these into your template and render each. Of course,
108+
you'll need to make sure that you don't call ``form_errors`` on any of your
109+
fields, since you're printing the errors manually (``form_row`` calls ``form_errors``
110+
automatically normally).
111+
112+
.. note::
113+
114+
Each field also has a ``error_bubbling`` option. If this is set to ``true``
115+
(it defaults to ``false`` for most fields), then the error will "bubble"
116+
and attach itself to the parent form. This means that if you set this
117+
option to ``true`` for *every* field, all errors would be rendered at
118+
the top by calling ``form_errors(form)``.
119+
120+
Accessing Errors Inside Twig
121+
----------------------------
122+
123+
We can also do some magic in Twig with errors using form variables, an idea
124+
that's *absolutely fundamental* to customizing how your forms render.
125+
126+
.. note::
127+
128+
If you're new to form theming and variables and want to master them,
129+
check out `Episode 4`_ of our Symfony series.
130+
131+
Normally, field errors are rendered in Twig by calling ``form_errors`` on
132+
each individual field:
133+
134+
.. code-block:: html+jinja
135+
136+
{{ form_errors(form) }}
137+
138+
{{ form_label(form.name) }}
139+
{{ form_widget(form.name) }}
140+
{{ form_errors(form.name) }}
141+
142+
.. note::
143+
144+
The ``form_row`` function calls ``form_errors`` internally.
145+
146+
Just like in the controller, the errors are attached to the individual fields
147+
themselves. Hopefully it make sense now why ``form_errors(form)`` renders *global*
148+
errors and ``form_errors(form.name)`` renders the errors attached to the
149+
name field.
150+
151+
.. tip::
152+
153+
Once you're in Twig, each field ``form``, ``form.name`` is an instance
154+
of :symfonyclass:`Symfony\\Component\\Form\\FormView`.
155+
156+
If you need to customize how the errors are rendered, you can always use
157+
`form theming`_. But you can also do it by leverage form variables:
158+
159+
{{ form_errors(form) }}
160+
161+
{{ form_label(form.name) }}
162+
{{ form_widget(form.name) }}
163+
164+
{% if form.name.vars.errors|length > 0 %}
165+
<ul class="form-errors name">
166+
{% for error in form.name.vars.errors %}
167+
{{ error }}
168+
{% endfor %}
169+
</ul>
170+
{% endif %}
171+
172+
The key here is ``form.name.vars``: a strange array of "variables" that you
173+
have access to on *every* field. One of the variables you have access to
174+
is ``errors``, but there are many others, like ``label`` and ``required``.
175+
Each variable is normally used internally to render the field, but you can
176+
use them manually if you need to:
177+
178+
.. code-block:: html+jinja
179+
180+
<label for="form.name.vars.id">
181+
{{ form.name.vars.label }} {{ form.name.vars.required ? '*' : '' }}
182+
</label>
183+
184+
To find out what variables a field has, just dump them:
185+
186+
.. code-block:: html+jinja
187+
188+
{{ dump(form.price.vars) }}
189+
190+
.. tip::
191+
192+
When you are form theming, these variables become accessible in your
193+
form theme template as local variables inside the form blocks (e.g.
194+
simply ``label`` or ``id``).
195+
196+
Takeaways
197+
---------
198+
199+
The most important thing to remember is that a form is a big tree. The top
200+
level ``Form`` has children, each which is also a ``Form`` object (or a
201+
``FormView`` object when you're in Twig). If you want to access information
202+
about a field (is it required? what are its errors?), you need to first get
203+
access to the *child* form and go from there.
204+
205+
.. _`How to get form validation errors after binding the request to the form`: http://stackoverflow.com/questions/6978723/symfony2-how-to-get-form-validation-errors-after-binding-the-request-to-the-fo
206+
.. _`Form panel`: http://symfony.com/blog/new-in-symfony-2-4-great-form-panel-in-the-web-profiler
207+
.. _`Stack Overflow`: http://stackoverflow.com/a/8216192/805814
208+
.. _`Episode 4`: http://knpuniversity.com/screencast/symfony2-ep4/form-customizations
209+
.. _`Episode 2`: http://knpuniversity.com/screencast/symfony2-ep2/registration-form
210+
.. _`form theming`: http://knpuniversity.com/screencast/symfony2-ep4/form-customizations

0 commit comments

Comments
 (0)