From 4634ce3ce6be9474123ff407e837156f62cae151 Mon Sep 17 00:00:00 2001 From: pvanliefland Date: Thu, 11 Oct 2012 09:46:19 +0200 Subject: [PATCH 1/3] [#814] [Form] Added documentation for Form Type Extensions --- cookbook/form/create_form_type_extension.rst | 329 +++++++++++++++++++ cookbook/form/index.rst | 1 + cookbook/map.rst.inc | 1 + 3 files changed, 331 insertions(+) create mode 100644 cookbook/form/create_form_type_extension.rst diff --git a/cookbook/form/create_form_type_extension.rst b/cookbook/form/create_form_type_extension.rst new file mode 100644 index 00000000000..ecc9be8266e --- /dev/null +++ b/cookbook/form/create_form_type_extension.rst @@ -0,0 +1,329 @@ +.. index:: + single: Form; Form type extension + +How to Create a Form Type Extension +==================================== + +:doc:`Custom form field types` are great when +you need field types with a specific purpose, such as a gender selector, +or a VAT number input. + +But sometimes, you don't really need to add new field types - you want +to add features on top of existing types. This is where form type +extensions come in. + +Form type extensions have 2 main use cases: + +#. You want to add a **generic feature to several types** (such as + adding a "help" text to every field type) +#. You want to add a **specific feature to a single type** (such + as adding a "download" feature to the "file" field type) + +In both those cases, it might be possible to achieve your goal with custom +form rendering, or custom form field types. But using form type extensions +can be cleaner (by limiting the amount of business logic in templates) +and more flexible (you can add several type extensions to a single form +type). + +Form type extensions can achieve most of what custom field types can do, +but instead of being field types of their own, **they plug into existing types**. + +Imagine that you manage a ``Media`` entity, and that each media is associated +to a file. Your ``Media`` form uses a file type, but when editing the entity, +you would like to see its image automatically rendered next to the file +input. + +You could of course do this by customizing how this field is rendered in a template. But field +type extensions allow you to do this in a nice DRY fashion. + +Defining the Form Type Extension +--------------------------------- + +Your first task will be to create the form type extension class. Let's +call it ``ImageTypeExtension``. You will store the class in a file called +``ImageTypeExtension.php``, in the ``\Form\Type`` directory. + +When creating a form type extension, you can either implement the +:class:`Symfony\\Component\\Form\\FormTypeExtensionInterface` interface, +or extend the :class:`Symfony\\Component\\Form\\AbstractTypeExtension` +class. Most of the time, you will end up extending the abstract class. +That's what you will do in this tutorial:: + + // src/Acme/DemoBundle/Form/Type/ImageTypeExtension.php + namespace Acme\DemoBundle\Form\Type; + + use Symfony\Component\Form\AbstractTypeExtension; + + class ImageTypeExtension extends AbstractTypeExtension + { + + /** + * Returns the name of the type being extended. + * + * @return string The name of the type being extended + */ + public function getExtendedType() + { + return 'file'; + } + + } + +The only method you **must** implement is the ``getExtendedType`` function. +It is used to indicate the name of the form type that will be extended +by your extension. + +.. tip:: + + The value you return in the ``getExtendedType`` method corresponds + to the value returned by the ``getName`` method in the form type class + you wish to extend. + +In addition to the ``getExtendedType`` function, you will probably want +to override one of the following methods: + +* ``buildForm()`` + +* ``buildView()`` + +* ``getDefaultOptions()`` + +* ``getAllowedOptionValues()`` + +* ``buildViewBottomUp()`` + +For more information on what those methods do, you can refer to the +:doc:`Creating Custom Field Types` +cookbook article. + +Registering your Form Type Extension as a Service +-------------------------------------------------- + +The next step is to make Symfony aware of your extension. All you +need to do is to declare it as a service by using the ``form.type_extension`` +tag: + +.. configuration-block:: + + .. code-block:: yaml + + services: + acme_demo_bundle.image_type_extension: + class: Acme\DemoBundle\Form\Type\ImageTypeExtension + tags: + - { name: form.type_extension, alias: file } + + .. code-block:: xml + + + + + + .. code-block:: php + + $container + ->register('acme_demo_bundle.image_type_extension', 'Acme\DemoBundle\Form\Type\ImageTypeExtension') + ->addTag('form.type_extension', array('alias' => 'file')); + +The ``alias`` key of the tag is the type of field that this extension should +be applied to. In your case, as you want to extend the ``file`` field type, +you will use ``file`` as an alias. + +Adding the extension business logic +----------------------------------- + +The goal of your extension is to display a nice image next to file inputs +(when the underlying model contains images). For that purpose, let's assume +that you use an approach similar to the one described in +:doc:`How to handle File Uploads with Doctrine`: +you have a Media model with a file property (corresponding to the file field +in the form) and a path property (corresponding to the image path in the +database). + +.. code-block:: php + + // src/Acme/DemoBundle/Entity/Media.php + namespace Acme\DemoBundle\Entity; + + use Doctrine\ORM\Mapping as ORM; + use Symfony\Component\Validator\Constraints as Assert; + + /** + * @ORM\Entity + * @ORM\Table + */ + class Media + { + + // ... + + /** + * @var string + * + * @ORM\Column(name="path", type="string", length=255) + */ + private $path; + + /** + * @var \Symfony\Component\HttpFoundation\File\UploadedFile + * @Assert\File(maxSize="2M") + */ + public $file; + + // ... + + /** + * Get the image url + * + * @return null|string + */ + public function getWebPath() + { + // ... $webPath being the full image url, to be used in templates + + return $webPath; + } + +Your form type extension class will need to do two things: + +1) Override the ``getDefaultOptions`` method in order to add an image_path + option +2) Override the ``buildForm`` and ``buildView`` methods in order to pass the image + url to the view + +The logic is the following: when adding a form field of type ``file``, +you will be able to specify a new option: ``image_path``. This option will +tell the file field how to get the actual image url in order to display +it in the view. + +.. code-block:: php + + // src/Acme/DemoBundle/Form/Type/ImageTypeExtension.php + namespace Acme\DemoBundle\Form\Type; + + use Symfony\Component\Form\AbstractTypeExtension; + use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormView; + use Symfony\Component\Form\FormInterface; + use Symfony\Component\Form\Util\PropertyPath; + + class ImageTypeExtension extends AbstractTypeExtension + { + + /** + * Returns the name of the type being extended. + * + * @return string The name of the type being extended + */ + public function getExtendedType() + { + return 'file'; + } + + /** + * Add the image_path option + * + * @param array $options + */ + public function getDefaultOptions(array $options) + { + return array('image_path' => null); + } + + /** + * Store the image_path option as a builder attribute + * + * @param \Symfony\Component\Form\FormBuilder $builder + * @param array $options + */ + public function buildForm(FormBuilder $builder, array $options) + { + if (null !== $options['image_path']) { + $builder->setAttribute('image_path', $options['image_path']); + } + } + + /** + * Pass the image url to the view + * + * @param \Symfony\Component\Form\FormView $view + * @param \Symfony\Component\Form\FormInterface $form + */ + public function buildView(FormView $view, FormInterface $form) + { + if ($form->hasAttribute('image_path')) { + $parentData = $form->getParent()->getData(); + + $propertyPath = new PropertyPath($form->getAttribute('image_path')); + $imageUrl = $propertyPath->getValue($parentData); + $view->set('image_url', $imageUrl); + } + } + + } + +Override the file widget template fragment +------------------------------------------ + +Each field type is rendered by a template fragment. Those template fragments +can be overridden in order to customize form rendering; for more information, +you can refer to the :ref:`cookbook-form-customization-form-themes` article. + +In your extension class, you have added a new variable (``image_url``), but +you still need to take advantage of this new variable in your templates. +You need to override the ``file_widget`` block: + +.. code-block:: html+jinja + + {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} + {% extends 'form_div_layout.html.twig' %} + + {% block file_widget %} + {% spaceless %} + + {{ block('field_widget') }} + {% if image_url is not null %} + + {% endif %} + + {% endspaceless %} + {% endblock %} + +.. note:: + + You will need to change your config file or to explicitly specify how + you want your form to be themed in order for Symfony to use your overridden + block. See :ref:`cookbook-form-customization-form-themes` for more + information. + +Using the Form Type Extension +------------------------------ + +From now on, when adding a field of type ``file`` in your form, you can +specify an ``image_path`` option that will be used to display an image +next to the file field. As an example:: + + // src/Acme/DemoBundle/Form/Type/MediaType.php + namespace Acme\DemoBundle\Form; + + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\FormBuilder; + + class MediaType extends AbstractType + { + + public function buildForm(FormBuilder $builder, array $options) + { + $builder + ->add('name', 'text') + ->add('file', 'file', array('image_path' => 'webPath')); + } + + public function getName() + { + return 'media'; + } + } + +When displaying the form, if the underlying model has already been associated +with an image, you will see it displayed next to the file input. \ No newline at end of file diff --git a/cookbook/form/index.rst b/cookbook/form/index.rst index 1dba8aa6f98..e60f02c9a68 100644 --- a/cookbook/form/index.rst +++ b/cookbook/form/index.rst @@ -9,4 +9,5 @@ Form dynamic_form_generation form_collections create_custom_field_type + create_form_type_extension use_virtuals_forms diff --git a/cookbook/map.rst.inc b/cookbook/map.rst.inc index e838e76be3b..64808463e75 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -71,6 +71,7 @@ * :doc:`/cookbook/form/dynamic_form_generation` * :doc:`/cookbook/form/form_collections` * :doc:`/cookbook/form/create_custom_field_type` + * :doc:`/cookbook/form/create_form_type_extension` * :doc:`/cookbook/form/use_virtuals_forms` * (validation) :doc:`/cookbook/validation/custom_constraint` * (doctrine) :doc:`/cookbook/doctrine/file_uploads` From 29d197076e2dfebf344f2caf29af711b63e7eadb Mon Sep 17 00:00:00 2001 From: pvanliefland Date: Fri, 12 Oct 2012 09:42:29 +0200 Subject: [PATCH 2/3] [#814] [Form] Fine-tuned punctuations and style --- cookbook/form/create_form_type_extension.rst | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/cookbook/form/create_form_type_extension.rst b/cookbook/form/create_form_type_extension.rst index ecc9be8266e..1a084bcc3e8 100644 --- a/cookbook/form/create_form_type_extension.rst +++ b/cookbook/form/create_form_type_extension.rst @@ -15,9 +15,9 @@ extensions come in. Form type extensions have 2 main use cases: #. You want to add a **generic feature to several types** (such as - adding a "help" text to every field type) + adding a "help" text to every field type); #. You want to add a **specific feature to a single type** (such - as adding a "download" feature to the "file" field type) + as adding a "download" feature to the "file" field type). In both those cases, it might be possible to achieve your goal with custom form rendering, or custom form field types. But using form type extensions @@ -56,7 +56,6 @@ That's what you will do in this tutorial:: class ImageTypeExtension extends AbstractTypeExtension { - /** * Returns the name of the type being extended. * @@ -154,7 +153,6 @@ database). */ class Media { - // ... /** @@ -186,10 +184,10 @@ database). Your form type extension class will need to do two things: -1) Override the ``getDefaultOptions`` method in order to add an image_path - option -2) Override the ``buildForm`` and ``buildView`` methods in order to pass the image - url to the view +#. Override the ``getDefaultOptions`` method in order to add an image_path + option; +#. Override the ``buildForm`` and ``buildView`` methods in order to pass the image + url to the view. The logic is the following: when adding a form field of type ``file``, you will be able to specify a new option: ``image_path``. This option will @@ -209,7 +207,6 @@ it in the view. class ImageTypeExtension extends AbstractTypeExtension { - /** * Returns the name of the type being extended. * @@ -311,7 +308,6 @@ next to the file field. As an example:: class MediaType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) { $builder From 4a238cbe8f335cc34e90d82294dda3fd2eac12ad Mon Sep 17 00:00:00 2001 From: pvanliefland Date: Fri, 12 Oct 2012 11:13:45 +0200 Subject: [PATCH 3/3] [#814] [Form] Added html+php syntax --- cookbook/form/create_form_type_extension.rst | 32 +++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/cookbook/form/create_form_type_extension.rst b/cookbook/form/create_form_type_extension.rst index 1a084bcc3e8..31047475a1a 100644 --- a/cookbook/form/create_form_type_extension.rst +++ b/cookbook/form/create_form_type_extension.rst @@ -270,21 +270,31 @@ In your extension class, you have added a new variable (``image_url``), but you still need to take advantage of this new variable in your templates. You need to override the ``file_widget`` block: -.. code-block:: html+jinja +.. configuration-block:: + + .. code-block:: html+jinja + + {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} + {% extends 'form_div_layout.html.twig' %} + + {% block file_widget %} + {% spaceless %} - {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} - {% extends 'form_div_layout.html.twig' %} + {{ block('field_widget') }} + {% if image_url is not null %} + + {% endif %} - {% block file_widget %} - {% spaceless %} + {% endspaceless %} + {% endblock %} - {{ block('field_widget') }} - {% if image_url is not null %} - - {% endif %} + .. code-block:: html+php - {% endspaceless %} - {% endblock %} + + widget($form) ?> + + + .. note::