diff --git a/book/controller.rst b/book/controller.rst old mode 100644 new mode 100755 index ff9206158c1..80b3ce786fc --- a/book/controller.rst +++ b/book/controller.rst @@ -339,7 +339,7 @@ working with forms, for example:: { $form = $this->createForm(...); - $form->bind($request); + $form->handleRequest($request); // ... } @@ -392,8 +392,13 @@ itself. Extending the base class is *optional* in Symfony; it contains useful shortcuts but nothing mandatory. You can also extend - :class:`Symfony\\Component\\DependencyInjection\\ContainerAware`. The service - container object will then be accessible via the ``container`` property. + :class:`Symfony\\Component\\DependencyInjection\\ContainerAware` or use + the class:`Symfony\\Component\\DependencyInjection\\ContainerAwareTrait` trait + (if you have PHP 5.4). The service container object will then be accessible + via the ``container`` property. + +.. versionadded:: 2.4 + The ``ContainerAwareTrait`` is new in Symfony 2.4. .. note:: @@ -686,7 +691,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 diff --git a/book/forms.rst b/book/forms.rst index f15d9fac1bd..f5621c51a5b 100644 --- a/book/forms.rst +++ b/book/forms.rst @@ -101,6 +101,7 @@ from inside a controller:: $form = $this->createFormBuilder($task) ->add('task', 'text') ->add('dueDate', 'date') + ->add('save', 'submit') ->getForm(); return $this->render('AcmeTaskBundle:Default:new.html.twig', array( @@ -125,6 +126,11 @@ In this example, you've added two fields to your form - ``task`` and ``dueDate`` corresponding to the ``task`` and ``dueDate`` properties of the ``Task`` class. You've also assigned each a "type" (e.g. ``text``, ``date``), which, among other things, determines which HTML form tag(s) is rendered for that field. +Finally, you added a submit button for submitting the form to the server. + +.. versionadded:: 2.3 + Support for submit buttons was added in Symfony 2.3. Before that, you had + to add buttons to the form's HTML manually. Symfony2 comes with many built-in types that will be discussed shortly (see :ref:`book-forms-type-reference`). @@ -145,35 +151,30 @@ helper functions: .. code-block:: html+jinja {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} -
+ {{ form(form) }} .. code-block:: html+php - + 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). The ``form`` function also +surrounds everything in the necessary HTML ``form`` tag. 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"). @@ -190,18 +191,17 @@ it into a format that's suitable for being rendered in an HTML form. (e.g. ``isPublished()`` or ``hasReminder()``) instead of a getter (e.g. ``getPublished()`` or ``getReminder()``). - .. versionadded:: 2.1 - Support for "hasser" methods was added in Symfony 2.1. - .. index:: - single: Forms; Handling form submission + single: Forms; Handling form submissions + +.. _book-form-handling-form-submissions: 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:: // ... @@ -215,55 +215,94 @@ controller:: $form = $this->createFormBuilder($task) ->add('task', 'text') ->add('dueDate', 'date') + ->add('save', 'submit') ->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. +.. versionadded:: 2.3 + The :method:`Symfony\\Component\\Form\\FormInterface::handleRequest` method was + added in Symfony 2.3. Previously, the ``$request`` was passed to the + ``submit`` method - a strategy which is deprecated and will be removed + in Symfony 3.0. For details on that method, see :ref:`cookbook-form-submit-request`. + +This controller follows a common pattern for handling forms, and has three +possible paths: -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. +#. 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. -.. note:: +#. 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; - 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. + .. note:: -This controller follows a common pattern for handling forms, and has three -possible paths: + You can use the method :method:`Symfony\\Component\\Form\\FormInterface::isSubmitted` + to check whether a form was submitted, regardless of whether or not the + submitted data is actually valid. -#. When initially loading the page in a browser, the request method is ``GET`` - and the form is simply created and rendered; +#. 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). -#. 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; + .. note:: -#. 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). + Redirecting a user after a successful form submission prevents the user + from being able to hit "refresh" and re-post the data. -.. note:: +.. index:: + single: Forms; Multiple Submit Buttons - Redirecting a user after a successful form submission prevents the user - from being able to hit "refresh" and re-post the data. +.. _book-form-submitting-multiple-buttons: + +Submitting Forms with Multiple Buttons +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.3 + Support for buttons in forms was added in Symfony 2.3. + +When your form contains more than one submit button, you will want to check +which of the buttons was clicked to adapt the program flow in your controller. +Let's add a second button with the caption "Save and add" to our form:: + + $form = $this->createFormBuilder($task) + ->add('task', 'text') + ->add('dueDate', 'date') + ->add('save', 'submit') + ->add('saveAndAdd', 'submit') + ->getForm(); + +In your controller, use the button's +:method:`Symfony\\Component\\Form\\ClickableInterface::isClicked` method for +querying if the "Save and add" button was clicked:: + + if ($form->isValid()) { + // ... perform some action, such as saving the task to the database + + $nextAction = $form->get('saveAndAdd')->isClicked() + ? 'task_new' + : 'task_success'; + + return $this->redirect($this->generateUrl($nextAction)); + } .. index:: single: Forms; Validation @@ -415,16 +454,46 @@ method:: In both of these cases, *only* the ``registration`` validation group will be used to validate the underlying object. -Groups based on Submitted Data -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. index:: + single: Forms; Disabling validation + +Disabling Validation +~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.3 + The ability to set ``validation_groups`` to false was added in Symfony 2.3, + although setting it to an empty array achieved the same result in previous + versions. + +Sometimes it is useful to suppress the validation of a form altogether. For +these cases, you can skip the call to :method:`Symfony\\Component\\Form\\FormInterface::isValid` +in your controller. If this is not possible, you can alternatively set the +``validation_groups`` option to ``false`` or an empty array:: + + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'validation_groups' => false, + )); + } + +Note that when you do that, the form will still run basic integrity checks, +for example whether an uploaded file was too large or whether non-existing +fields were submitted. If you want to suppress validation completely, remove +the :method:`Symfony\\Component\\Form\\FormInterface::isValid` call from your +controller. -.. versionadded:: 2.1 - The ability to specify a callback or Closure in ``validation_groups`` - is new to version 2.1 +.. index:: + single: Forms; Validation groups based on submitted data + +Groups based on the Submitted Data +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you need some advanced logic to determine the validation groups (e.g. based on submitted data), you can set the ``validation_groups`` option -to an array callback, or a ``Closure``:: +to an array callback:: use Symfony\Component\OptionsResolver\OptionsResolverInterface; @@ -439,9 +508,9 @@ 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:: +You can also define whole logic inline by using a ``Closure``:: use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; @@ -460,6 +529,44 @@ You can also define whole logic inline by using a Closure:: )); } +.. index:: + single: Forms; Validation groups based on clicked button + +Groups based on the Clicked Button +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.3 + Support for buttons in forms was added in Symfony 2.3. + +When your form contains multiple submit buttons, you can change the validation +group depending on which button is used to submit the form. For example, +consider a form in a wizard that lets you advance to the next step or go back +to the previous step. Let's assume also that when returning to the previous +step, the data of the form should be saved, but not validated. + +First, we need to add the two buttons to the form:: + + $form = $this->createFormBuilder($task) + // ... + ->add('nextStep', 'submit') + ->add('previousStep', 'submit') + ->getForm(); + +Then, we configure the button for returning to the previous step to run +specific validation groups. In this example, we want it to suppress validation, +so we set its ``validation_groups`` options to false:: + + $form = $this->createFormBuilder($task) + // ... + ->add('previousStep', 'submit', array( + 'validation_groups' => false, + )) + ->getForm(); + +Now the form will skip your validation constraints. It will still validate +basic integrity constraints, such as checking whether an uploaded file was too +large or whether you tried to submit text in a number field. + .. index:: single: Forms; Built-in field types @@ -549,6 +656,7 @@ guess from the validation rules that both the ``task`` field is a normal $form = $this->createFormBuilder($task) ->add('task') ->add('dueDate', null, array('widget' => 'single_text')) + ->add('save', 'submit') ->getForm(); } @@ -616,35 +724,26 @@ 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_end(form) }} .. code-block:: html+php - + 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); @@ -653,10 +752,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