Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • SensioLabs Professional services to help you with Symfony
    • Platform.sh for Symfony Best platform to deploy Symfony apps
    • SymfonyInsight Automatic quality checks for your apps
    • Symfony Certification Prove your knowledge and boost your career
    • Blackfire Profile and monitor performance of your apps
  • Other
  • Blog
  • Download
sponsored by SensioLabs
  1. Home
  2. Documentation
  3. Cookbook
  4. Form
  5. How to use Data Transformers
  • Documentation
  • Book
  • Reference
  • Bundles
  • Cloud

Table of Contents

  • Creating the Transformer
  • Using the Transformer
    • Model and View Transformers
  • So why use the model transformer?
  • Using Transformers in a custom field type

How to use Data Transformers

Edit this page

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

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

How to use Data Transformers

You'll often find the need to transform the data the user entered in a form into something else for use in your program. You could easily do this manually in your controller, but what if you want to use this specific form in different places?

Say you have a one-to-one relation of Task to Issue, e.g. a Task optionally has an issue linked to it. Adding a listbox with all possible issues can eventually lead to a really long listbox in which it is impossible to find something. You might want to add a textbox instead, where the user can simply enter the issue number.

You could try to do this in your controller, but it's not the best solution. It would be better if this issue were automatically converted to an Issue object. This is where Data Transformers come into play.

Creating the Transformer

First, create an `IssueToNumberTransformer` class - this class will be responsible for converting to and from the issue number and the Issue object:

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
57
58
59
60
61
62
63
64
65
66
67
68
// src/Acme/TaskBundle/Form/DataTransformer/IssueToNumberTransformer.php
namespace Acme\TaskBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\TaskBundle\Entity\Issue;

class IssueToNumberTransformer implements DataTransformerInterface
{
    /**
     * @var ObjectManager
     */
    private $om;

    /**
     * @param ObjectManager $om
     */
    public function __construct(ObjectManager $om)
    {
        $this->om = $om;
    }

    /**
     * Transforms an object (issue) to a string (number).
     *
     * @param  Issue|null $issue
     * @return string
     */
    public function transform($issue)
    {
        if (null === $issue) {
            return "";
        }

        return $issue->getNumber();
    }

    /**
     * Transforms a string (number) to an object (issue).
     *
     * @param  string $number
     *
     * @return Issue|null
     *
     * @throws TransformationFailedException if object (issue) is not found.
     */
    public function reverseTransform($number)
    {
        if (!$number) {
            return null;
        }

        $issue = $this->om
            ->getRepository('AcmeTaskBundle:Issue')
            ->findOneBy(array('number' => $number))
        ;

        if (null === $issue) {
            throw new TransformationFailedException(sprintf(
                'An issue with number "%s" does not exist!',
                $number
            ));
        }

        return $issue;
    }
}

Tip

If you want a new issue to be created when an unknown number is entered, you can instantiate it rather than throwing the TransformationFailedException.

Using the Transformer

Now that you have the transformer built, you just need to add it to your issue field in some form.

You can also use transformers without creating a new custom form type by calling addModelTransformer (or addViewTransformer - see Model and View Transformers) on any field builder:

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
use Symfony\Component\Form\FormBuilderInterface;
use Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer;

class TaskType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // ...

        // this assumes that the entity manager was passed in as an option
        $entityManager = $options['em'];
        $transformer = new IssueToNumberTransformer($entityManager);

        // add a normal text field, but add your transformer to it
        $builder->add(
            $builder->create('issue', 'text')
                ->addModelTransformer($transformer)
        );
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\TaskBundle\Entity\Task',
        ));

        $resolver->setRequired(array(
            'em',
        ));

        $resolver->setAllowedTypes(array(
            'em' => 'Doctrine\Common\Persistence\ObjectManager',
        ));

        // ...
    }

    // ...
}

This example requires that you pass in the entity manager as an option when creating your form. Later, you'll learn how you could create a custom issue field type to avoid needing to do this in your controller:

1
2
3
$taskForm = $this->createForm(new TaskType(), $task, array(
    'em' => $this->getDoctrine()->getManager(),
));

Cool, you're done! Your user will be able to enter an issue number into the text field and it will be transformed back into an Issue object. This means that, after a successful bind, the Form framework will pass a real Issue object to Task::setIssue() instead of the issue number.

If the issue isn't found, a form error will be created for that field and its error message can be controlled with the invalid_message field option.

Caution

Notice that adding a transformer requires using a slightly more complicated syntax when adding the field. The following is wrong, as the transformer would be applied to the entire form, instead of just this field:

1
2
3
4
// THIS IS WRONG - TRANSFORMER WILL BE APPLIED TO THE ENTIRE FORM
// see above example for correct code
$builder->add('issue', 'text')
    ->addModelTransformer($transformer);

Model and View Transformers

2.1

The names and method of the transformers were changed in Symfony 2.1. prependNormTransformer became addModelTransformer and appendClientTransformer became addViewTransformer.

In the above example, the transformer was used as a "model" transformer. In fact, there are two different type of transformers and three different types of underlying data.

In any form, the 3 different types of data are:

1) Model data - This is the data in the format used in your application (e.g. an Issue object). If you call Form::getData or Form::setData,
you're dealing with the "model" data.

2) Norm Data - This is a normalized version of your data, and is commonly the same as your "model" data (though not in our example). It's not commonly used directly.

3) View Data - This is the format that's used to fill in the form fields themselves. It's also the format in which the user will submit the data. When you call Form::bind($data), the $data is in the "view" data format.

The 2 different types of transformers help convert to and from each of these types of data:

Model transformers:
  • transform: "model data" => "norm data"
  • reverseTransform: "norm data" => "model data"
View transformers:
  • transform: "norm data" => "view data"
  • reverseTransform: "view data" => "norm data"

Which transformer you need depends on your situation.

To use the view transformer, call addViewTransformer.

So why use the model transformer?

In this example, the field is a text field, and a text field is always expected to be a simple, scalar format in the "norm" and "view" formats. For this reason, the most appropriate transformer was the "model" transformer (which converts to/from the norm format - string issue number - to the model format - Issue object).

The difference between the transformers is subtle and you should always think about what the "norm" data for a field should really be. For example, the "norm" data for a text field is a string, but is a DateTime object for a date field.

Using Transformers in a custom field type

In the above example, you applied the transformer to a normal text field. This was easy, but has two downsides:

1) You need to always remember to apply the transformer whenever you're adding a field for issue numbers

2) You need to worry about passing in the em option whenever you're creating a form that uses the transformer.

Because of these, you may choose to create a create a custom field type. First, create the custom field type class:

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
// src/Acme/TaskBundle/Form/Type/IssueSelectorType.php
namespace Acme\TaskBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class IssueSelectorType extends AbstractType
{
    /**
     * @var ObjectManager
     */
    private $om;

    /**
     * @param ObjectManager $om
     */
    public function __construct(ObjectManager $om)
    {
        $this->om = $om;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $transformer = new IssueToNumberTransformer($this->om);
        $builder->addModelTransformer($transformer);
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'invalid_message' => 'The selected issue does not exist',
        ));
    }

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

    public function getName()
    {
        return 'issue_selector';
    }
}

Next, register your type as a service and tag it with form.type so that it's recognized as a custom field type:

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
services:
    acme_demo.type.issue_selector:
        class: Acme\TaskBundle\Form\Type\IssueSelectorType
        arguments: ["@doctrine.orm.entity_manager"]
        tags:
            - { name: form.type, alias: issue_selector }
1
2
3
4
<service id="acme_demo.type.issue_selector" class="Acme\TaskBundle\Form\Type\IssueSelectorType">
    <argument type="service" id="doctrine.orm.entity_manager"/>
    <tag name="form.type" alias="issue_selector" />
</service>
1
2
3
4
5
6
7
8
$container
    ->setDefinition('acme_demo.type.issue_selector', array(
        new Reference('doctrine.orm.entity_manager'),
    ))
    ->addTag('form.type', array(
        'alias' => 'issue_selector',
    ))
;

Now, whenever you need to use your special issue_selector field type, it's quite easy:

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

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

class TaskType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('task')
            ->add('dueDate', null, array('widget' => 'single_text'))
            ->add('issue', 'issue_selector');
    }

    public function getName()
    {
        return 'task';
    }
}
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version
    We stand with Ukraine.
    Version:
    Become certified from home

    Become certified from home

    Check Code Performance in Dev, Test, Staging & Production

    Check Code Performance in Dev, Test, Staging & Production

    Symfony footer

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

    Avatar of Ivan Nemets, a Symfony contributor

    Thanks Ivan Nemets for being a Symfony contributor

    1 commit • 20 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