Skip to content

Added information about the new form(), form_start() and form_end() helpers #2092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 21, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions book/controller.rst
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ working with forms, for example::
{
$form = $this->createForm(...);

$form->bind($request);
$form->handleRequest($request);
// ...
}

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

$form->bind($this->getRequest());
$form->handleRequest($this->getRequest());

if ($form->isValid()) {
// do some sort of processing

Expand Down
215 changes: 135 additions & 80 deletions book/forms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,35 +145,27 @@ helper functions:
.. code-block:: html+jinja

{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}

<input type="submit" />
</form>
{{ form(form) }}

.. code-block:: html+php

<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->
<form action="<?php echo $view['router']->generate('task_new') ?>" method="post" <?php echo $view['form']->enctype($form) ?> >
<?php echo $view['form']->widget($form) ?>

<input type="submit" />
</form>
<?php echo $view['form']->form($form) ?>

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

.. note::

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

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

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

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

// ...
Expand All @@ -217,53 +209,54 @@ controller::
->add('dueDate', 'date')
->getForm();

if ($request->isMethod('POST')) {
$form->bind($request);
$form->handleRequest($request);

if ($form->isValid()) {
// perform some action, such as saving the task to the database
if ($form->isValid()) {
// perform some action, such as saving the task to the database

return $this->redirect($this->generateUrl('task_success'));
}
return $this->redirect($this->generateUrl('task_success'));
}

// ...
}

.. versionadded:: 2.1
The ``bind`` method was made more flexible in Symfony 2.1. It now accepts
the raw client data (same as before) or a Symfony Request object. This
is preferred over the deprecated ``bindRequest`` method.

Now, when submitting the form, the controller binds the submitted data to the
form, which translates that data back to the ``task`` and ``dueDate`` properties
of the ``$task`` object. This all happens via the ``bind()`` method.

.. note::

As soon as ``bind()`` is called, the submitted data is transferred
to the underlying object immediately. This happens regardless of whether
or not the underlying data is actually valid.
.. versionadded:: 2.3
The :method:`Symfony\Component\Form\FormInterface::handleRequest` method was
added in Symfony 2.3. Before you had to do some manual work to achieve the
same result.

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

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

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

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

.. note::
You can use the method :method:`Symfony\Component\Form\FormInterface::isBound`
to check whether a form was submitted, regardless of whether or not the
submitted data is actually valid.

#. When the user submits the form with valid data, the submitted data is again
written into the form, but this time :method:`Symfony\Component\Form\FormInterface::isValid`
returns ``true``. Now you have the opportunity to perform some actions using
the ``$task`` object (e.g. persisting it to the database) before redirecting
the user to some other page (e.g. a "thank you" or "success" page).

Redirecting a user after a successful form submission prevents the user
from being able to hit "refresh" and re-post the data.
.. note::

Redirecting a user after a successful form submission prevents the user
from being able to hit "refresh" and re-post the data.

.. index::
single: Forms; Validation
Expand Down Expand Up @@ -429,7 +422,7 @@ to an array callback, or a ``Closure``::
}

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

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

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

{{ form_row(form.task) }}
{{ form_row(form.dueDate) }}

{{ form_rest(form) }}

<input type="submit" />
</form>
{{ form_end(form) }}

.. code-block:: html+php

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

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

<?php echo $view['form']->rest($form) ?>

<input type="submit" />
</form>
<?php echo $view['form']->end($form) ?>

Take a look at each part:

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

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

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

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

.. code-block:: html+jinja

{{ form_widget(form.task, { 'attr': {'class': 'task_field'} }) }}
{{ form_widget(form.task, {'attr': {'class': 'task_field'}}) }}

.. code-block:: html+php

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

.. index::
single: Forms; Changing the action and method

.. _book-forms-changing-action-and-method:

Changing the Action and Method of a Form
----------------------------------------

So far, we have used the ``form_start()`` helper to render the form's start tag
and assumed that each form is submitted to the same URL in a POST request.
Sometimes you want to change these parameters. You can do so in a few different
ways. If you build your form in the controller, you can use ``setAction()`` and
``setMethod()``::

$form = $this->createFormBuilder($task)
->setAction($this->generateUrl('target_route'))
->setMethod('GET')
->add('task', 'text')
->add('dueDate', 'date')
->getForm();

.. note::

This example assumes that you've created a route called ``target_route``
that points to the controller that processes the form.

In :ref:`book-form-creating-form-classes` you will learn how to outsource the
form building code into separate classes. When using such a form class in the
controller, you can pass the action and method as form options::

$form = $this->createForm(new TaskType(), $task, array(
'action' => $this->generateUrl('target_route'),
'method' => 'GET',
));

At last, you can override the action and method in the template by passing them
to the ``form()`` or the ``form_start()`` helper:

.. configuration-block::

.. code-block:: html+jinja

{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
{{ form(form, {'action': path('target_route'), 'method': 'GET'}) }}

{{ form_start(form, {'action': path('target_route'), 'method': 'GET'}) }}

.. code-block:: html+php

<!-- src/Acme/TaskBundle/Resources/views/Default/newAction.html.php -->
<?php echo $view['form']->form($form, array(
'action' => $view['router']->generate('target_route'),
'method' => 'GET',
)) ?>

<?php echo $view['form']->start($form, array(
'action' => $view['router']->generate('target_route'),
'method' => 'GET',
)) ?>

.. note::

If the form's method is not GET or POST, but PUT, PATCH or DELETE, Symfony2
will insert a hidden field with the name "_method" that stores this method.
The form will be submitted in a normal POST request, but Symfony2's router
is capable of detecting the "_method" parameter and will interpret the
request as PUT, PATCH or DELETE request. Read the cookbook chapter
":doc:`/cookbook/routing/method_parameters`" for more information.

.. index::
single: Forms; Creating form classes

Expand Down Expand Up @@ -912,7 +967,7 @@ you can fetch it from the form::

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

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

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

<form ...>
{{ form(form) }}

.. code-block:: html+php

Expand All @@ -1152,7 +1207,7 @@ renders the form:

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

<form ...>
<?php echo $view['form']->form($form) ?>

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

.. note::

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

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

if ($request->isMethod('POST')) {
$form->bind($request);
$form->handleRequest($request);

// data is an array with "name", "email", and "message" keys
$data = $form->getData();
}
if ($form->isBound()) {
// data is an array with "name", "email", and "message" keys
$data = $form->getData();
}

// ... render the form
}
Expand Down Expand Up @@ -1526,15 +1581,15 @@ Adding Validation

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

.. _form-option-constraints:

But if you're not binding to an object and are instead retrieving a simple
array of your submitted data, how can you add constraints to the data of your
form?
But if the form is not mapped to an object and you instead want to retrieve a
simple array of your submitted data, how can you add constraints to the data of
your form?

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