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`` thatpoints 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 thatit 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 easyas this is, it's not very flexible (yet). Usually, you'll want to render eachform field individually so you can control how the form looks. You'll learn howto 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 bebound to the form. Add the following functionality to your user must bewritten 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 isbound , but before validation is executed. ``Client`` class after the form issubmitted , 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 isbound , the submitted The key thing to understand is that when the form issubmitted , 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 actually3 other *parts* - ``rows``, ``rest``, and ``enctype `` - There are actually2 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 isbinding to an object (i.e. you're using the ``data_class`` class. If your form ismapped 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 ifyou're notbinding to an object andare insteadretrieving a simple array of your submitted data, how can you add constraints to the data of your form? But ifthe form is notmapped to an object andyou insteadwant to retrieve a simple array of your submitted data, how can you add constraints to the data ofyour 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