Skip to content

Commit 7f89c45

Browse files
committed
Merge pull request #2092 from bschussek/issue5493
[WCM] Added information about the new form(), form_start() and form_end() helpers
2 parents ed05a78 + 0763900 commit 7f89c45

File tree

11 files changed

+283
-207
lines changed

11 files changed

+283
-207
lines changed

book/controller.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ working with forms, for example::
325325
{
326326
$form = $this->createForm(...);
327327

328-
$form->bind($request);
328+
$form->handleRequest($request);
329329
// ...
330330
}
331331

@@ -663,7 +663,8 @@ For example, imagine you're processing a form submit::
663663
{
664664
$form = $this->createForm(...);
665665

666-
$form->bind($this->getRequest());
666+
$form->handleRequest($this->getRequest());
667+
667668
if ($form->isValid()) {
668669
// do some sort of processing
669670

book/forms.rst

Lines changed: 135 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -145,35 +145,27 @@ helper functions:
145145
.. code-block:: html+jinja
146146

147147
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
148-
<form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
149-
{{ form_widget(form) }}
150-
151-
<input type="submit" />
152-
</form>
148+
{{ form(form) }}
153149

154150
.. code-block:: html+php
155151

156152
<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->
157-
<form action="<?php echo $view['router']->generate('task_new') ?>" method="post" <?php echo $view['form']->enctype($form) ?> >
158-
<?php echo $view['form']->widget($form) ?>
159-
160-
<input type="submit" />
161-
</form>
153+
<?php echo $view['form']->form($form) ?>
162154

163155
.. image:: /images/book/form-simple.png
164156
:align: center
165157

166158
.. note::
167159

168-
This example assumes that you've created a route called ``task_new``
169-
that points to the ``AcmeTaskBundle:Default:new`` controller that
170-
was created earlier.
160+
This example assumes that you submit the form in a "POST" request and to
161+
the same URL that it was displayed in. You will learn later how to
162+
change the request method and the target URL of the form.
171163

172-
That's it! By printing ``form_widget(form)``, each field in the form is
173-
rendered, along with a label and error message (if there is one). As easy
174-
as this is, it's not very flexible (yet). Usually, you'll want to render each
175-
form field individually so you can control how the form looks. You'll learn how
176-
to do that in the ":ref:`form-rendering-template`" section.
164+
That's it! By printing ``form(form)``, each field in the form is rendered, along
165+
with a label and error message (if there is one). As easy as this is, it's not
166+
very flexible (yet). Usually, you'll want to render each form field individually
167+
so you can control how the form looks. You'll learn how to do that in the
168+
":ref:`form-rendering-template`" section.
177169

178170
Before moving on, notice how the rendered ``task`` input field has the value
179171
of the ``task`` property from the ``$task`` object (i.e. "Write a blog post").
@@ -201,7 +193,7 @@ Handling Form Submissions
201193

202194
The second job of a form is to translate user-submitted data back to the
203195
properties of an object. To make this happen, the submitted data from the
204-
user must be bound to the form. Add the following functionality to your
196+
user must be written into the form. Add the following functionality to your
205197
controller::
206198

207199
// ...
@@ -217,53 +209,54 @@ controller::
217209
->add('dueDate', 'date')
218210
->getForm();
219211

220-
if ($request->isMethod('POST')) {
221-
$form->bind($request);
212+
$form->handleRequest($request);
222213

223-
if ($form->isValid()) {
224-
// perform some action, such as saving the task to the database
214+
if ($form->isValid()) {
215+
// perform some action, such as saving the task to the database
225216

226-
return $this->redirect($this->generateUrl('task_success'));
227-
}
217+
return $this->redirect($this->generateUrl('task_success'));
228218
}
229219

230220
// ...
231221
}
232222

233-
.. versionadded:: 2.1
234-
The ``bind`` method was made more flexible in Symfony 2.1. It now accepts
235-
the raw client data (same as before) or a Symfony Request object. This
236-
is preferred over the deprecated ``bindRequest`` method.
237-
238-
Now, when submitting the form, the controller binds the submitted data to the
239-
form, which translates that data back to the ``task`` and ``dueDate`` properties
240-
of the ``$task`` object. This all happens via the ``bind()`` method.
241-
242-
.. note::
243-
244-
As soon as ``bind()`` is called, the submitted data is transferred
245-
to the underlying object immediately. This happens regardless of whether
246-
or not the underlying data is actually valid.
223+
.. versionadded:: 2.3
224+
The :method:`Symfony\Component\Form\FormInterface::handleRequest` method was
225+
added in Symfony 2.3. Before you had to do some manual work to achieve the
226+
same result.
247227

248228
This controller follows a common pattern for handling forms, and has three
249229
possible paths:
250230

251-
#. When initially loading the page in a browser, the request method is ``GET``
252-
and the form is simply created and rendered;
231+
#. When initially loading the page in a browser, the form is simply created and
232+
rendered. :method:`Symfony\Component\Form\FormInterface::handleRequest`
233+
recognizes that the form was not submitted and does nothing.
234+
:method:`Symfony\Component\Form\FormInterface::isValid` returns ``false``
235+
if the form was not submitted.
253236

254-
#. When the user submits the form (i.e. the method is ``POST``) with invalid
255-
data (validation is covered in the next section), the form is bound and
256-
then rendered, this time displaying all validation errors;
237+
#. When the user submits the form, :method:`Symfony\Component\Form\FormInterface::handleRequest`
238+
recognizes this and immediately writes the submitted data back into the
239+
``task`` and ``dueDate`` properties of the ``$task`` object. Then this object
240+
is validated. If it is invalid (validation is covered in the next section),
241+
:method:`Symfony\Component\Form\FormInterface::isValid` returns ``false``
242+
again, so the form is rendered together with all validation errors;
257243

258-
#. When the user submits the form with valid data, the form is bound and
259-
you have the opportunity to perform some actions using the ``$task``
260-
object (e.g. persisting it to the database) before redirecting the user
261-
to some other page (e.g. a "thank you" or "success" page).
244+
.. note::
262245

263-
.. note::
246+
You can use the method :method:`Symfony\Component\Form\FormInterface::isBound`
247+
to check whether a form was submitted, regardless of whether or not the
248+
submitted data is actually valid.
249+
250+
#. When the user submits the form with valid data, the submitted data is again
251+
written into the form, but this time :method:`Symfony\Component\Form\FormInterface::isValid`
252+
returns ``true``. Now you have the opportunity to perform some actions using
253+
the ``$task`` object (e.g. persisting it to the database) before redirecting
254+
the user to some other page (e.g. a "thank you" or "success" page).
264255

265-
Redirecting a user after a successful form submission prevents the user
266-
from being able to hit "refresh" and re-post the data.
256+
.. note::
257+
258+
Redirecting a user after a successful form submission prevents the user
259+
from being able to hit "refresh" and re-post the data.
267260

268261
.. index::
269262
single: Forms; Validation
@@ -429,7 +422,7 @@ to an array callback, or a ``Closure``::
429422
}
430423

431424
This will call the static method ``determineValidationGroups()`` on the
432-
``Client`` class after the form is bound, but before validation is executed.
425+
``Client`` class after the form is submitted, but before validation is executed.
433426
The Form object is passed as an argument to that method (see next example).
434427
You can also define whole logic inline by using a Closure::
435428

@@ -605,35 +598,30 @@ of code. Of course, you'll usually need much more flexibility when rendering:
605598
.. code-block:: html+jinja
606599

607600
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
608-
<form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
601+
{{ form_start(form) }}
609602
{{ form_errors(form) }}
610603

611604
{{ form_row(form.task) }}
612605
{{ form_row(form.dueDate) }}
613606

614-
{{ form_rest(form) }}
615-
616607
<input type="submit" />
617-
</form>
608+
{{ form_end(form) }}
618609

619610
.. code-block:: html+php
620611

621612
<!-- src/Acme/TaskBundle/Resources/views/Default/newAction.html.php -->
622-
<form action="<?php echo $view['router']->generate('task_new') ?>" method="post" <?php echo $view['form']->enctype($form) ?>>
613+
<?php echo $view['form']->start($form) ?>
623614
<?php echo $view['form']->errors($form) ?>
624615

625616
<?php echo $view['form']->row($form['task']) ?>
626617
<?php echo $view['form']->row($form['dueDate']) ?>
627618

628-
<?php echo $view['form']->rest($form) ?>
629-
630619
<input type="submit" />
631-
</form>
620+
<?php echo $view['form']->end($form) ?>
632621

633622
Take a look at each part:
634623

635-
* ``form_enctype(form)`` - If at least one field is a file upload field, this
636-
renders the obligatory ``enctype="multipart/form-data"``;
624+
* ``form_start(form)`` - Renders the start tag of the form.
637625

638626
* ``form_errors(form)`` - Renders any errors global to the whole form
639627
(field-specific errors are displayed next to each field);
@@ -642,10 +630,8 @@ Take a look at each part:
642630
form widget for the given field (e.g. ``dueDate``) inside, by default, a
643631
``div`` element;
644632

645-
* ``form_rest(form)`` - Renders any fields that have not yet been rendered.
646-
It's usually a good idea to place a call to this helper at the bottom of
647-
each form (in case you forgot to output a field or don't want to bother
648-
manually rendering hidden fields). This helper is also useful for taking
633+
* ``form_end()`` - Renders the end tag of the form and any fields that have not
634+
yet been rendered. This is useful for rendering hidden fields and taking
649635
advantage of the automatic :ref:`CSRF Protection<forms-csrf>`.
650636

651637
The majority of the work is done by the ``form_row`` helper, which renders
@@ -740,7 +726,7 @@ field:
740726

741727
.. code-block:: html+jinja
742728

743-
{{ form_widget(form.task, { 'attr': {'class': 'task_field'} }) }}
729+
{{ form_widget(form.task, {'attr': {'class': 'task_field'}}) }}
744730

745731
.. code-block:: html+php
746732

@@ -783,6 +769,75 @@ available in the :doc:`reference manual</reference/forms/twig_reference>`.
783769
Read this to know everything about the helpers available and the options
784770
that can be used with each.
785771

772+
.. index::
773+
single: Forms; Changing the action and method
774+
775+
.. _book-forms-changing-action-and-method:
776+
777+
Changing the Action and Method of a Form
778+
----------------------------------------
779+
780+
So far, we have used the ``form_start()`` helper to render the form's start tag
781+
and assumed that each form is submitted to the same URL in a POST request.
782+
Sometimes you want to change these parameters. You can do so in a few different
783+
ways. If you build your form in the controller, you can use ``setAction()`` and
784+
``setMethod()``::
785+
786+
$form = $this->createFormBuilder($task)
787+
->setAction($this->generateUrl('target_route'))
788+
->setMethod('GET')
789+
->add('task', 'text')
790+
->add('dueDate', 'date')
791+
->getForm();
792+
793+
.. note::
794+
795+
This example assumes that you've created a route called ``target_route``
796+
that points to the controller that processes the form.
797+
798+
In :ref:`book-form-creating-form-classes` you will learn how to outsource the
799+
form building code into separate classes. When using such a form class in the
800+
controller, you can pass the action and method as form options::
801+
802+
$form = $this->createForm(new TaskType(), $task, array(
803+
'action' => $this->generateUrl('target_route'),
804+
'method' => 'GET',
805+
));
806+
807+
At last, you can override the action and method in the template by passing them
808+
to the ``form()`` or the ``form_start()`` helper:
809+
810+
.. configuration-block::
811+
812+
.. code-block:: html+jinja
813+
814+
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
815+
{{ form(form, {'action': path('target_route'), 'method': 'GET'}) }}
816+
817+
{{ form_start(form, {'action': path('target_route'), 'method': 'GET'}) }}
818+
819+
.. code-block:: html+php
820+
821+
<!-- src/Acme/TaskBundle/Resources/views/Default/newAction.html.php -->
822+
<?php echo $view['form']->form($form, array(
823+
'action' => $view['router']->generate('target_route'),
824+
'method' => 'GET',
825+
)) ?>
826+
827+
<?php echo $view['form']->start($form, array(
828+
'action' => $view['router']->generate('target_route'),
829+
'method' => 'GET',
830+
)) ?>
831+
832+
.. note::
833+
834+
If the form's method is not GET or POST, but PUT, PATCH or DELETE, Symfony2
835+
will insert a hidden field with the name "_method" that stores this method.
836+
The form will be submitted in a normal POST request, but Symfony2's router
837+
is capable of detecting the "_method" parameter and will interpret the
838+
request as PUT, PATCH or DELETE request. Read the cookbook chapter
839+
":doc:`/cookbook/routing/method_parameters`" for more information.
840+
786841
.. index::
787842
single: Forms; Creating form classes
788843

@@ -912,7 +967,7 @@ you can fetch it from the form::
912967

913968
For more information, see the :doc:`Doctrine ORM chapter</book/doctrine>`.
914969

915-
The key thing to understand is that when the form is bound, the submitted
970+
The key thing to understand is that when the form is submitted, the submitted
916971
data is transferred to the underlying object immediately. If you want to
917972
persist that data, you simply need to persist the object itself (which already
918973
contains the submitted data).
@@ -1143,7 +1198,7 @@ renders the form:
11431198

11441199
{% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' 'AcmeTaskBundle:Form:fields2.html.twig' %}
11451200

1146-
<form ...>
1201+
{{ form(form) }}
11471202

11481203
.. code-block:: html+php
11491204

@@ -1152,7 +1207,7 @@ renders the form:
11521207

11531208
<?php $view['form']->setTheme($form, array('AcmeTaskBundle:Form', 'AcmeTaskBundle:Form')) ?>
11541209

1155-
<form ...>
1210+
<?php echo $view['form']->form($form) ?>
11561211

11571212
The ``form_theme`` tag (in Twig) "imports" the fragments defined in the given
11581213
template and uses them when rendering the form. In other words, when the
@@ -1231,7 +1286,7 @@ are 4 possible *parts* of a form that can be rendered:
12311286

12321287
.. note::
12331288

1234-
There are actually 3 other *parts* - ``rows``, ``rest``, and ``enctype`` -
1289+
There are actually 2 other *parts* - ``rows`` and ``rest`` -
12351290
but you should rarely if ever need to worry about overriding them.
12361291

12371292
By knowing the field type (e.g. ``textarea``) and which part you want to
@@ -1486,12 +1541,12 @@ an array of the submitted data. This is actually really easy::
14861541
->add('message', 'textarea')
14871542
->getForm();
14881543

1489-
if ($request->isMethod('POST')) {
1490-
$form->bind($request);
1544+
$form->handleRequest($request);
14911545

1492-
// data is an array with "name", "email", and "message" keys
1493-
$data = $form->getData();
1494-
}
1546+
if ($form->isBound()) {
1547+
// data is an array with "name", "email", and "message" keys
1548+
$data = $form->getData();
1549+
}
14951550

14961551
// ... render the form
14971552
}
@@ -1526,15 +1581,15 @@ Adding Validation
15261581

15271582
The only missing piece is validation. Usually, when you call ``$form->isValid()``,
15281583
the object is validated by reading the constraints that you applied to that
1529-
class. If your form is binding to an object (i.e. you're using the ``data_class``
1584+
class. If your form is mapped to an object (i.e. you're using the ``data_class``
15301585
option or passing an object to your form), this is almost always the approach
15311586
you want to use. See :doc:`/book/validation` for more details.
15321587

15331588
.. _form-option-constraints:
15341589

1535-
But if you're not binding to an object and are instead retrieving a simple
1536-
array of your submitted data, how can you add constraints to the data of your
1537-
form?
1590+
But if the form is not mapped to an object and you instead want to retrieve a
1591+
simple array of your submitted data, how can you add constraints to the data of
1592+
your form?
15381593

15391594
The answer is to setup the constraints yourself, and attach them to the individual
15401595
fields. The overall approach is covered a bit more in the :ref:`validation chapter<book-validation-raw-values>`,

0 commit comments

Comments
 (0)