Skip to content
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

More 2.8 form updates #5909

Merged
merged 5 commits into from
Nov 30, 2015
Merged
Show file tree
Hide file tree
Changes from 3 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
21 changes: 2 additions & 19 deletions best_practices/forms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ some developers configure form buttons in the controller::

use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use AppBundle\Entity\Post;
use AppBundle\Form\PostType;

Expand All @@ -121,7 +122,7 @@ some developers configure form buttons in the controller::
{
$post = new Post();
$form = $this->createForm(PostType::class, $post);
$form->add('submit', 'submit', array(
$form->add('submit', SubmitType::class, array(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2.8 still requires 5.3.6, do you think it's safe to use 5.5 syntaxes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing use statement btw

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to show the 5.5 syntax, because it's almost unusable if you can't use this syntax. But the idea is to also show the 5.3 syntax with a note about this in some cases early on in chapters. Basically, I want people to know that they need 5.5 to use the new syntax, but that they can use the old aliases until they actually need to upgrade to Symfony 3 (which requires 5.5). It's tricky.

And I think the use statement is here - for SubmitType and PostType?

'label' => 'Create',
'attr' => array('class' => 'btn btn-default pull-right')
));
Expand Down Expand Up @@ -205,21 +206,3 @@ Second, we recommend using ``$form->isSubmitted()`` in the ``if`` statement
for clarity. This isn't technically needed, since ``isValid()`` first calls
``isSubmitted()``. But without this, the flow doesn't read well as it *looks*
like the form is *always* processed (even on the GET request).

Custom Form Field Types
-----------------------

.. best-practice::

Add the ``app_`` prefix to your custom form field types to avoid collisions.

Custom form field types inherit from the ``AbstractType`` class, which defines the
``getName()`` method to configure the name of that form type. These names must
be unique in the application.

If a custom form type uses the same name as any of the Symfony's built-in form
types, it will override it. The same happens when the custom form type matches
any of the types defined by the third-party bundles installed in your application.

Add the ``app_`` prefix to your custom form field types to avoid name collisions
that can lead to hard to debug errors.
28 changes: 13 additions & 15 deletions book/forms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,10 @@ represented by its fully qualified class name. Among other things, it determines
which HTML form tag(s) is rendered for that field.

.. versionadded:: 2.8
To denote the form type, you have to use the fully qualified class name.
Before Symfony 2.8, you could use an alias for each type like ``'name'`` or
``'date'``.
To denote the form type, you have to use the fully qualified class name - like
TextType::class in PHP 5.5+ or ``Symfony\Component\Form\Extension\Core\Type\TextType``.
Before Symfony 2.8, you could use an alias for each type like ``text`` or
``date``. For more details, see the `2.8 UPGRADE Log`_.

Finally, you added a submit button with a custom label for submitting the form to
the server.
Expand Down Expand Up @@ -1004,7 +1005,7 @@ ways. If you build your form in the controller, you can use ``setAction()`` and
->setAction($this->generateUrl('target_route'))
->setMethod('GET')
->add('task', TextType::class)
->add('dueDate', DateType::clas)
->add('dueDate', DateType::class)
->add('save', SubmitType::class)
->getForm();

Expand All @@ -1017,7 +1018,10 @@ In :ref:`book-form-creating-form-classes` you will learn how to move the
form building code into separate classes. When using an external form class
in the controller, you can pass the action and method as form options::

$form = $this->createForm(new TaskType(), $task, array(
use AppBundle\Form\Type\TaskType;
// ...

$form = $this->createForm(TaskType::class, $task, array(
'action' => $this->generateUrl('target_route'),
'method' => 'GET',
));
Expand Down Expand Up @@ -1230,18 +1234,11 @@ Define your form type as a service.
.. code-block:: php

// src/AppBundle/Resources/config/services.php
use AppBundle\Form\Type\TaskType;
use ;

$definition = new Definition(TaskType::class, array(
new Reference('app.my_service'),
));
$container
->setDefinition(
'app.form.type.task',
$definition
)
$container->register('app.form.type.task', 'AppBundle\Form\Type\TaskType')
->addArgument(new Reference('app.my_service'))
->addTag('form.type')
;

Read :ref:`form-cookbook-form-field-service` for more information.

Expand Down Expand Up @@ -1978,3 +1975,4 @@ Learn more from the Cookbook
.. _`form_div_layout.html.twig`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
.. _`Cross-site request forgery`: http://en.wikipedia.org/wiki/Cross-site_request_forgery
.. _`view on GitHub`: https://github.com/symfony/symfony/tree/master/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form
.. _`2.8 UPGRADE Log`: https://github.com/symfony/symfony/blob/2.8/UPGRADE-2.8.md#form
2 changes: 1 addition & 1 deletion book/validation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ workflow looks like the following from inside a controller::
public function updateAction(Request $request)
{
$author = new Author();
$form = $this->createForm(new AuthorType(), $author);
$form = $this->createForm(AuthorType::class, $author);

$form->handleRequest($request);

Expand Down
15 changes: 7 additions & 8 deletions cookbook/bundles/override.rst
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,16 @@ associations. Learn more about this feature and its limitations in
Forms
-----

In order to override a form type, it has to be registered as a service (meaning
it is tagged as ``form.type``). You can then override it as you would override any
service as explained in `Services & Configuration`_. This, of course, will only
work if the type is referred to by its alias rather than being instantiated,
e.g.::
As of Symfony 2.8, form types are referred to by their fully-qualified class name::

$builder->add('name', 'custom_type');
$builder->add('name', CustomType::class);

rather than::
This means that you cannot override this by creating a sub-class of ``CustomType``
and registering it as a service, and tagging it with ``form.type`` (you *could*
do this in earlier version).

$builder->add('name', new CustomType());
Instead, you should use a "form type extension" to modify the existing form type.
For more information, see :doc:`/cookbook/form/create_form_type_extension`.

.. _override-validation:

Expand Down
5 changes: 3 additions & 2 deletions cookbook/controller/upload_file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,15 @@ Then, add a new ``brochure`` field to the form that manages the ``Product`` enti
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;

class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('brochure', 'file', array('label' => 'Brochure (PDF file)'))
->add('brochure', FileType::class, array('label' => 'Brochure (PDF file)'))
// ...
;
}
Expand Down Expand Up @@ -116,7 +117,7 @@ Finally, you need to update the code of the controller that handles the form::
public function newAction(Request $request)
{
$product = new Product();
$form = $this->createForm(new ProductType(), $product);
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);

if ($form->isValid()) {
Expand Down
69 changes: 34 additions & 35 deletions cookbook/form/create_custom_field_type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ for form fields, which is ``<BundleName>\Form\Type``. Make sure the field extend

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;

class GenderType extends AbstractType
{
Expand All @@ -40,12 +41,7 @@ for form fields, which is ``<BundleName>\Form\Type``. Make sure the field extend

public function getParent()
{
return 'choice';
}

public function getName()
{
return 'gender';
return ChoiceType::class;
}
}

Expand All @@ -54,8 +50,12 @@ for form fields, which is ``<BundleName>\Form\Type``. Make sure the field extend
The location of this file is not important - the ``Form\Type`` directory
is just a convention.

.. versionadded:: 2.8
In 2.8, the ``getName()`` method was removed. Now, fields are always referred
to by their fully-qualified class name.

Here, the return value of the ``getParent`` function indicates that you're
extending the ``choice`` field type. This means that, by default, you inherit
extending the ``ChoiceType`` field. This means that, by default, you inherit
all of the logic and rendering of that field type. To see some of the logic,
check out the `ChoiceType`_ class. There are three methods that are particularly
important:
Expand Down Expand Up @@ -89,24 +89,26 @@ important:
Also, if you need to modify the "view" of any of your child types from
your parent type, use the ``finishView()`` method.

The ``getName()`` method returns an identifier which should be unique in
your application. This is used in various places, such as when customizing
how your form type will be rendered.

The goal of this field was to extend the choice type to enable selection of
a gender. This is achieved by fixing the ``choices`` to a list of possible
genders.

Creating a Template for the Field
---------------------------------

Each field type is rendered by a template fragment, which is determined in
part by the value of your ``getName()`` method. For more information, see
Each field type is rendered by a template fragment, which is determined in part by
the class name of your type. For more information, see
:ref:`cookbook-form-customization-form-themes`.

In this case, since the parent field is ``choice``, you don't *need* to do
any work as the custom field type will automatically be rendered like a ``choice``
type. But for the sake of this example, suppose that when your field is "expanded"
.. note::

The first part of the prefix (e.g. ``gender``) comes from the class name
(``GenderType`` -> ``gender``). This can be controlled by overriding ``getBlockPrefix()``
in ``GenderType``.

In this case, since the parent field is ``ChoiceType``, you don't *need* to do
any work as the custom field type will automatically be rendered like a ``ChoiceType``.
But for the sake of this example, suppose that when your field is "expanded"
(i.e. radio buttons or checkboxes, instead of a select field), you want to
always render it in a ``ul`` element. In your form theme template (see above
link for details), create a ``gender_widget`` block to handle this:
Expand Down Expand Up @@ -154,7 +156,7 @@ link for details), create a ``gender_widget`` block to handle this:
.. note::

Make sure the correct widget prefix is used. In this example the name should
be ``gender_widget``, according to the value returned by ``getName``.
be ``gender_widget`` (see :ref:`cookbook-form-customization-form-themes`).
Further, the main config file should point to the custom form template
so that it's used when rendering all forms.

Expand Down Expand Up @@ -241,18 +243,19 @@ new instance of the type in one of your forms::

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use AppBundle\Form\Type\GenderType;

class AuthorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('gender_code', new GenderType(), array(
$builder->add('gender_code', GenderType::class, array(
'placeholder' => 'Choose a gender',
));
}
}

But this only works because the ``GenderType()`` is very simple. What if
But this only works because the ``GenderType`` is very simple. What if
the gender codes were stored in configuration or in a database? The next
section explains how more complex field types solve this problem.

Expand Down Expand Up @@ -311,14 +314,14 @@ the ``genders`` parameter value as the first argument to its to-be-created
arguments:
- "%genders%"
tags:
- { name: form.type, alias: gender }
- { name: form.type }

.. code-block:: xml

<!-- src/AppBundle/Resources/config/services.xml -->
<service id="app.form.type.gender" class="AppBundle\Form\Type\GenderType">
<argument>%genders%</argument>
<tag name="form.type" alias="gender" />
<tag name="form.type" />
</service>

.. code-block:: php
Expand All @@ -331,20 +334,16 @@ the ``genders`` parameter value as the first argument to its to-be-created
'AppBundle\Form\Type\GenderType',
array('%genders%')
))
->addTag('form.type', array(
'alias' => 'gender',
))
->addTag('form.type')
;

.. tip::

Make sure the services file is being imported. See :ref:`service-container-imports-directive`
for details.

Be sure that the ``alias`` attribute of the tag corresponds with the value
returned by the ``getName`` method defined earlier. You'll see the importance
of this in a moment when you use the custom field type. But first, add a ``__construct``
method to ``GenderType``, which receives the gender configuration::
First, add a ``__construct`` method to ``GenderType``, which receives the gender
configuration::

// src/AppBundle/Form/Type/GenderType.php
namespace AppBundle\Form\Type;
Expand Down Expand Up @@ -374,28 +373,28 @@ method to ``GenderType``, which receives the gender configuration::
}

Great! The ``GenderType`` is now fueled by the configuration parameters and
registered as a service. Additionally, because you used the ``form.type`` alias in its
configuration, using the field is now much easier::
registered as a service. Because you used the ``form.type`` alias in its configuration,
your service will be used instead of creating a *new* ``GenderType``. In other words,
your controller *does not need to change*, it still looks like this::

// src/AppBundle/Form/Type/AuthorType.php
namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

// ...
use AppBundle\Form\Type\GenderType;

class AuthorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('gender_code', 'gender', array(
$builder->add('gender_code', GenderType::class, array(
'placeholder' => 'Choose a gender',
));
}
}

Notice that instead of instantiating a new instance, you can just refer to
it by the alias used in your service configuration, ``gender``. Have fun!
Have fun!

.. _`ChoiceType`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
.. _`FieldType`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php
Loading