Symfony
sponsored by SensioLabs
Menu
  • About
  • Documentation
  • Screencasts
  • Cloud
  • Certification
  • Community
  • Businesses
  • News
  • Download
  1. Home
  2. Documentation
  3. Cookbook
  4. Form
  5. How to Create a Form Type Extension
  • Documentation
  • Book
  • Reference
  • Bundles
  • Cloud
Search by Algolia

Table of Contents

  • Defining the Form Type Extension
  • Registering your Form Type Extension as a Service
  • Adding the extension Business Logic
  • Override the File Widget Template Fragment
  • Using the Form Type Extension

How to Create a Form Type Extension

Edit this page

Warning: You are browsing the documentation for Symfony 2.2, which is no longer maintained.

Read the updated version of this page for Symfony 6.2 (the current stable version).

How to Create a Form Type Extension

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:

  1. You want to add a generic feature to several types (such as adding a "help" text to every field type);
  2. 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. By standard, form extensions usually live in the Form\Extension directory of one of your bundles.

When creating a form type extension, you can either implement the FormTypeExtensionInterface interface or extend the AbstractTypeExtension class. In most cases, it's easier to extend the abstract class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Acme/DemoBundle/Form/Extension/ImageTypeExtension.php
namespace Acme\DemoBundle\Form\Extension;

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()
  • setDefaultOptions()
  • finishView()

For more information on what those methods do, you can refer to the 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:

  • YAML
  • XML
  • PHP
1
2
3
4
5
services:
    acme_demo_bundle.image_type_extension:
        class: Acme\DemoBundle\Form\Extension\ImageTypeExtension
        tags:
            - { name: form.type_extension, alias: file }
1
2
3
4
5
<service id="acme_demo_bundle.image_type_extension"
    class="Acme\DemoBundle\Form\Extension\ImageTypeExtension"
>
    <tag name="form.type_extension" alias="file" />
</service>
1
2
3
4
5
6
$container
    ->register(
        'acme_demo_bundle.image_type_extension',
        'Acme\DemoBundle\Form\Extension\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 nice images 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 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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// src/Acme/DemoBundle/Entity/Media.php
namespace Acme\DemoBundle\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Media
{
    // ...

    /**
     * @var string The path - typically stored in the database
     */
    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 in order to extend the file form type:

  1. Override the setDefaultOptions 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// src/Acme/DemoBundle/Form/Extension/ImageTypeExtension.php
namespace Acme\DemoBundle\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

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 OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setOptional(array('image_path'));
    }

    /**
     * Pass the image URL to the view
     *
     * @param FormView $view
     * @param FormInterface $form
     * @param array $options
     */
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        if (array_key_exists('image_path', $options)) {
            $parentData = $form->getParent()->getData();

            if (null !== $parentData) {
                $accessor = PropertyAccess::getPropertyAccessor();
                $imageUrl = $accessor->getValue($parentData, $options['image_path']);
            } else {
                 $imageUrl = null;
            }

            // set an "image_url" variable that will be available when rendering this field
            $view->vars['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 How to customize Form Rendering 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. Specifically, you need to override the file_widget block:

  • Twig
  • PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}

{% block file_widget %}
    {% spaceless %}

    {{ block('form_widget') }}
    {% if image_url is not null %}
        <img src="{{ asset(image_url) }}"/>
    {% endif %}

    {% endspaceless %}
{% endblock %}
1
2
3
4
5
<!-- src/Acme/DemoBundle/Resources/views/Form/file_widget.html.php -->
<?php echo $view['form']->widget($form) ?>
<?php if (null !== $image_url): ?>
    <img src="<?php echo $view['assets']->getUrl($image_url) ?>"/>
<?php endif ?>

Note

You will need to change your config file or explicitly specify how you want your form to be themed in order for Symfony to use your overridden block. See How to customize Form Rendering 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. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Acme/DemoBundle/Form/Type/MediaType.php
namespace Acme\DemoBundle\Form\Type;

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

class MediaType extends AbstractType
{
    public function buildForm(FormBuilderInterface $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.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
We stand with Ukraine.
Version:
Online Symfony certification, take it now!

Online Symfony certification, take it now!

Be trained by SensioLabs experts (2 to 6 day sessions -- French or English).

Be trained by SensioLabs experts (2 to 6 day sessions -- French or English).

↓ Our footer now uses the colors of the Ukrainian flag because Symfony stands with the people of Ukraine.

Avatar of Dennis Haarbrink, a Symfony contributor

Thanks Dennis Haarbrink for being a Symfony contributor

1 commit • 9 lines changed

View all contributors that help us make Symfony

Become a Symfony contributor

Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

Learn how to contribute

Symfony™ is a trademark of Symfony SAS. All rights reserved.

  • What is Symfony?
    • Symfony at a Glance
    • Symfony Components
    • Case Studies
    • Symfony Releases
    • Security Policy
    • Logo & Screenshots
    • Trademark & Licenses
    • symfony1 Legacy
  • Learn Symfony
    • Symfony Docs
    • Symfony Book
    • Reference
    • Bundles
    • Best Practices
    • Training
    • eLearning Platform
    • Certification
  • Screencasts
    • Learn Symfony
    • Learn PHP
    • Learn JavaScript
    • Learn Drupal
    • Learn RESTful APIs
  • Community
    • SymfonyConnect
    • Support
    • How to be Involved
    • Code of Conduct
    • Events & Meetups
    • Projects using Symfony
    • Downloads Stats
    • Contributors
    • Backers
  • Blog
    • Events & Meetups
    • A week of symfony
    • Case studies
    • Cloud
    • Community
    • Conferences
    • Diversity
    • Documentation
    • Living on the edge
    • Releases
    • Security Advisories
    • SymfonyInsight
    • Twig
    • SensioLabs
  • Services
    • SensioLabs services
    • Train developers
    • Manage your project quality
    • Improve your project performance
    • Host Symfony projects
    Deployed on
Follow Symfony
Search by Algolia