From b48feda2fa3c4a2d8f1f274c61ba605a7fa4a4ce Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 27 Jun 2015 21:31:50 -0400 Subject: [PATCH 1/6] Completely re-reading the data transformers chapter: 1) Show an easy example using the CallbackTransformer in the beginning 2) Remove complexity related to any factories that were there before 3) Moved theoretical section to the bottom --- book/forms.rst | 2 + cookbook/form/data_transformers.rst | 519 ++++++++++++++-------------- 2 files changed, 266 insertions(+), 255 deletions(-) diff --git a/book/forms.rst b/book/forms.rst index 38186bba172..71b6695bb75 100644 --- a/book/forms.rst +++ b/book/forms.rst @@ -1141,6 +1141,8 @@ the choice is ultimately up to you. $form->get('dueDate')->setData(new \DateTime()); +.. _form-as-services: + Defining your Forms as Services ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/cookbook/form/data_transformers.rst b/cookbook/form/data_transformers.rst index 850db1a5adb..5ae723232c5 100644 --- a/cookbook/form/data_transformers.rst +++ b/cookbook/form/data_transformers.rst @@ -4,29 +4,143 @@ 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. +Data transformers are used to translate the data for a field into a format that can +be displayed in a form (and back on submit). They're already used internally for +many field types. For example, the :doc:`date field type ` +can be rendered as a ``yyyy-MM-dd``-formatted input textbox. Internally, a data transformer +converts the starting ``DateTime`` value of the field into the ``yyyy-MM-dd`` string +to render the form, and then back into a ``DateTime`` object on submit. .. caution:: When a form field has the ``inherit_data`` option set, Data Transformers won't be applied to that field. +Simple Example: Sanitizing HTML on User Input +--------------------------------------------- + +Suppose you have a Task form with a description ``textarea`` type:: + + // src/AppBundle/Form/TaskType.php + // ... + + class TaskType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('description', 'textarea'); + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'AppBundle\Entity\Task' + )); + } + + // ... + } + +But, there are two complications: + +#. Your users are allowed to use *some* HTML tags, but not others: you need a way + to call :phpfunction:`striptags` after the form is submitted. + +#. To be friendly, you want to convert ``
`` tags into line breaks (``\n``) before + rendering the field so the text is easier to edit. + +This is a *perfect* time to attach a custom data transformer to the ``description`` +field. The easiest way to do this is with the :class:`Symfony\\Component\\Form\\CallbackTransformer` +class:: + + // src/AppBundle/Form/TaskType.php + + use Symfony\Component\Form\CallbackTransformer; + // ... + + class TaskType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('description', 'textarea'); + + $builder->get('description') + ->addModelTransformer(new CallbackTransformer( + // transform
to \n so the textarea reads easier + function ($originalDescription) { + return preg_replace('##i', "\n", $originalDescription); + }, + function ($submittedDescription) { + // remove most HTML tags (but not br,p) + $cleaned = strip_tags($submittedDescription, '

'); + + // transform any \n to real
+ return str_replace("\n", '
', $cleaned); + } + )); + } + + // ... + } + +The ``CallbackTransformer`` takes to callback functions as arguments. The first transforms +the original value into a format that'll be used to render the field. The second +does the reverse: it transforms the submitted value back into the format you'll use +in your code. + +.. tip:: + + The ``addModelTransformer()`` method accepts *any* object that implements + :class:`Symfony\\Component\\Form\\DataTransformerInterface` - so you can create + your own classes, instead of putting all the logic in the form (see the next section). + +Harder Examle: Transforming an Issue Number into an Issue Entity +---------------------------------------------------------------- + +Say you have a many-to-one relation from the Task entity to an Issue entity (i.e. each +Task has an optional foreign key to its related Issue). Adding a listbox with all +possible issues could eventually get *really* long and take a long time to load. +Instead, you decide you want to add a textbox, where the user can simply enter the +issue number. + +Start by setting up the text field like normal:: + + // src/AppBundle/Form/TaskType.php + // ... + + class TaskType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('description', 'textarea') + ->add('issue', 'text'); + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'AppBundle\Entity\Task' + )); + } + + // ... + } + +Good start! But if you stopped here and submitted the form, the Task's ``issue`` +property would be a string (e.g. "55"). How can you transform this into an ``Issue`` +entity on submit? + 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:: +You could use the ``CallbackTransformer`` like earlier. But since this is a bit more +complex, creating a new transformer class will keep the ``TaskType`` form class simpler. + +Create an ``IssueToNumberTransformer`` class: it will be responsible for converting +to and from the issue number and the ``Issue`` object:: // src/AppBundle/Form/DataTransformer/IssueToNumberTransformer.php namespace AppBundle\Form\DataTransformer; @@ -38,17 +152,11 @@ for converting to and from the issue number and the ``Issue`` object:: class IssueToNumberTransformer implements DataTransformerInterface { - /** - * @var ObjectManager - */ - private $om; + private $em; - /** - * @param ObjectManager $om - */ - public function __construct(ObjectManager $om) + public function __construct(ObjectManager $em) { - $this->om = $om; + $this->em = $em; } /** @@ -63,31 +171,36 @@ for converting to and from the issue number and the ``Issue`` object:: return ''; } - return $issue->getNumber(); + return $issue->getId(); } /** * Transforms a string (number) to an object (issue). * - * @param string $number + * @param string $issueNumber * @return Issue|null * @throws TransformationFailedException if object (issue) is not found. */ - public function reverseTransform($number) + public function reverseTransform($issueNumber) { - if (!$number) { + // no issue number? It's optional, so that's ok + if (!$issueNumber) { return null; } - $issue = $this->om + $issue = $this->em ->getRepository('AppBundle:Issue') - ->findOneBy(array('number' => $number)) + // query for the issue with this id + ->find($issueNumber) ; if (null === $issue) { + // causes a validation error + // this message is not shown to the user + // see the invalid_message option throw new TransformationFailedException(sprintf( 'An issue with number "%s" does not exist!', - $number + $issueNumber )); } @@ -95,10 +208,15 @@ for converting to and from the issue number and the ``Issue`` object:: } } -.. tip:: +Just like in the first example, a transformer has two directions. The ``transform()`` +method is responsible for converting the data used in your code to a format that +can be rendered in your form (e.g. an ``Issue`` object to its ``id``, a string). +The ``reverseTransform()`` method does the reverse: it converts the submitted value +back into the format you want (e.g. convert the ``id`` back to the ``Issue`` object). - If you want a new issue to be created when an unknown number is entered, you - can instantiate it rather than throwing the ``TransformationFailedException``. +To cause a validation error, throw a :class:`Symfony\\Component\\Form\\Exception\\TransformationFailedException`. +But the message you pass to this exception won't be shown to the user. You'll set +that message with the ``invalid_message`` option (see below). .. note:: @@ -109,140 +227,56 @@ for converting to and from the issue number and the ``Issue`` object:: Using the Transformer --------------------- -As seen above our transformer requires an instance of an object manager. While for most -use-cases it is sufficient to use the default entity manager, you will sometimes need -to explicitly choose the one to use. To achieve this, you can use a factory:: - - // src/AppBundle/Form/DataTransformer/IssueToNumberTransformerFactory.php - namespace AppBundle\Form\DataTransformer; +Next, you need to instantiate the ``IssueToNumberTransformer`` class from inside +``TaskType`` and add it to the ``issue`` field. But to do that, you'll need an instance +of the entity manager (because ``IssueToNumberTransformer`` needs this). - use Doctrine\Common\Persistence\ManagerRegistry; - - class IssueToNumberTransformerFactory - { - /** - * @var ManagerRegistry - */ - private $registry; - - public function __construct(ManagerRegistry $registry) - { - $this->registry = $registry; - } - - public function create($om) - { - return new IssueToNumberTransformer($this->registry->getManager($om)); - } - } - -.. configuration-block:: - - .. code-block:: yaml - - services: - app.issue_transformer_factory: - class: AppBundle\Form\DataTransformer\IssueToNumberTransformerFactory - arguments: ["@doctrine"] - public: false - - app.type.task: - class: AppBundle\Form\TaskType - arguments: ["@app.issue_transformer_factory"] - tags: - - { name: form.type, alias: app_task } - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - // ... - - $container - ->setDefinition('app.issue_transformer_factory', new Definition( - 'AppBundle\Form\DataTransformer\IssueToNumberTransformerFactory' - ), array( - new Reference('doctrine'), - )) - ->setPublic(false) - ; - - $container - ->setDefinition('app.type.task', new Definition( - 'AppBundle\Form\TaskType' - ), array( - new Reference('app.issue_transformer_factory'), - )) - ->addTag('form.type', array('alias' => 'app_task')) - ; - -Now that you have the capability to build the transformer with the desired object manager, you -just need to create it from 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:: +No problem! Just add a ``__construct()`` function to ``TaskType`` and force this +to be passed in. Then, you can easily create and add the transformer:: // src/AppBundle/Form/TaskType.php - namespace AppBundle\Form; - - use AppBundle\Form\DataTransformer\IssueToNumberTransformerFactory; - use Symfony\Component\Form\FormBuilderInterface; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; + use AppBundle\Form\DataTransformer\IssueToNumberTransformer; + use Doctrine\Common\Persistence\ObjectManager; class TaskType extends AbstractType { - /** - * @var IssueToNumberTransformerFactory - */ - private $factory; + private $em; - public function __construct(IssueToNumberTransformerFactory $factory) + public function __construct(ObjectManager $em) { - $this->factory = $factory; + $this->em = $em; } public function buildForm(FormBuilderInterface $builder, array $options) { - $transformer = $this->factory->create($options['om']); + $builder + ->add('description', 'textarea') + ->add('issue', 'text', array( + // validation message if the data transformer fails + 'invalid_message' => 'That is not a valid issue number' + )); - $builder->add( - $builder->create('issue', 'text') - ->addModelTransformer($transformer) - ); - } + // ... - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver - ->setDefaults(array( - 'data_class' => 'AppBundle\Entity\Task', - )) - ->setRequired(array('om')) - ; + $builder->get('issue') + ->addModelTransformer(new IssueToNumberTransformer($this->em)); } + + // ... } -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:: +Now, when you create your ``TaskType``, you'll need to pass in the entity manager:: - $taskForm = $this->createForm('app_task', $task, array( - 'om' => 'default', - )); + // e.g. in a controller somewhere + $em = $this->getDoctrine()->getManager(); + $form = $this->createForm(new TaskType($em), $task); + + // ... + +.. note:: + + To make this step easier (especially if ``TaskType`` is embedded into other + form type classes), you might choose to :ref:`register your form type as a service `. 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 @@ -254,103 +288,47 @@ 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:: + Be careful when adding your transformers. For example, the following is **wrong**, + as the transformer would be applied to the entire form, instead of just this + field:: // 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 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In the above example, the transformer was used as a "model" transformer. -In fact, there are two different types of transformers and three different -types of underlying data. - -.. image:: /images/cookbook/form/DataTransformersTypes.png - :align: center +Creating a Reusable issue_selector Field +---------------------------------------- -In any form, the three 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::submit($data)``, the ``$data`` is in the "view" data format. - -The two 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" +In the above example, you applied the transformer to a normal ``text`` field. But +if you do this transformation a lot, it might be better to +:doc:`create a custom field type `. +that does this automatically. -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 :doc:`create a custom field type `. First, create the custom field type class:: // src/AppBundle/Form/IssueSelectorType.php + namespace AppBundle\Form; - use AppBundle\Form\DataTransformer\IssueToNumberTransformerFactory; + use AppBundle\Form\DataTransformer\IssueToNumberTransformer; + use Doctrine\Common\Persistence\ObjectManager; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class IssueSelectorType extends AbstractType { - private $factory; - - public function __construct(IssueToNumberTransformerFactory $factory) + private $em; + + public function __construct(ObjectManager $em) { - $this->factory = $factory; + $this->em = $em; } public function buildForm(FormBuilderInterface $builder, array $options) { - $transformer = $this->factory->create($options['om']); + $transformer = new IssueToNumberTransformer($this->em); $builder->addModelTransformer($transformer); } @@ -358,7 +336,6 @@ First, create the custom field type class:: { $resolver->setDefaults(array( 'invalid_message' => 'The selected issue does not exist', - 'om' => 'default' )); } @@ -373,6 +350,9 @@ First, create the custom field type class:: } } +Great! This will act and render like a text field (``getParent()``), but will automatically +have the data transformer *and* a nice default value for the ``invalid_message`` option. + Next, register your type as a service and tag it with ``form.type`` so that it's recognized as a custom field type: @@ -381,26 +361,18 @@ it's recognized as a custom field type: .. code-block:: yaml services: - app.issue_transformer_factory: - class: AppBundle\Form\DataTransformer\IssueToNumberTransformerFactory - arguments: ["@doctrine"] - public: false app.type.issue_selector: class: AppBundle\Form\IssueSelectorType - arguments: ["@app.issue_transformer_factory"] + arguments: ["@doctrine.orm.default_entity_manager"] tags: - { name: form.type, alias: issue_selector } - .. code-block:: xml - - - + .. code-block:: xml - + @@ -409,21 +381,12 @@ it's recognized as a custom field type: use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; // ... - - $container - ->setDefinition('app.issue_transformer_factory', new Definition( - 'AppBundle\Form\DataTransformer\IssueToNumberTransformerFactory' - ), array( - new Reference('doctrine'), - )) - ->setPublic(false) - ; - + $container ->setDefinition('app.type.issue_selector', new Definition( 'AppBundle\Form\IssueSelectorType' ), array( - new Reference('app.issue_transformer_factory'), + new Reference('doctrine.orm.default_entity_manager'), )) ->addTag('form.type', array( 'alias' => 'issue_selector', @@ -434,23 +397,69 @@ Now, whenever you need to use your special ``issue_selector`` field type, it's quite easy:: // src/AppBundle/Form/TaskType.php - namespace AppBundle\Form; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; + use AppBundle\Form\DataTransformer\IssueToNumberTransformer; class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder - ->add('task') - ->add('dueDate', null, array('widget' => 'single_text')) + ->add('description', 'textarea') ->add('issue', 'issue_selector'); } - public function getName() - { - return 'task'; - } + // ... } + +About Model and View Transformers +--------------------------------- + +In the above example, the transformer was used as a "model" transformer. +In fact, there are two different types of transformers and three different +types of underlying data. + +.. image:: /images/cookbook/form/DataTransformersTypes.png + :align: center + +In any form, the three 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::submit($data)``, the ``$data`` is in the "view" data format. + +The two 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. From 9be07f0b77edf3d35bb917a90cb9b3196490718f Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 28 Jun 2015 12:25:40 -0400 Subject: [PATCH 2/6] Many tweaks thanks to review --- cookbook/form/data_transformers.rst | 63 ++++++++++++++++------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/cookbook/form/data_transformers.rst b/cookbook/form/data_transformers.rst index 5ae723232c5..d6e102e5f89 100644 --- a/cookbook/form/data_transformers.rst +++ b/cookbook/form/data_transformers.rst @@ -22,8 +22,11 @@ Simple Example: Sanitizing HTML on User Input Suppose you have a Task form with a description ``textarea`` type:: // src/AppBundle/Form/TaskType.php - // ... + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + + // ... class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) @@ -45,7 +48,7 @@ Suppose you have a Task form with a description ``textarea`` type:: But, there are two complications: #. Your users are allowed to use *some* HTML tags, but not others: you need a way - to call :phpfunction:`striptags` after the form is submitted. + to call :phpfunction:`striptags` after the form is submitted; #. To be friendly, you want to convert ``
`` tags into line breaks (``\n``) before rendering the field so the text is easier to edit. @@ -108,8 +111,8 @@ issue number. Start by setting up the text field like normal:: // src/AppBundle/Form/TaskType.php - // ... + // ... class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) @@ -134,7 +137,7 @@ property would be a string (e.g. "55"). How can you transform this into an ``Iss entity on submit? Creating the Transformer ------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~ You could use the ``CallbackTransformer`` like earlier. But since this is a bit more complex, creating a new transformer class will keep the ``TaskType`` form class simpler. @@ -146,17 +149,17 @@ to and from the issue number and the ``Issue`` object:: namespace AppBundle\Form\DataTransformer; use AppBundle\Entity\Issue; - use Doctrine\Common\Persistence\ObjectManager; + use Doctrine\Common\Persistence\EntityManager; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; class IssueToNumberTransformer implements DataTransformerInterface { - private $em; + private $entityManager; - public function __construct(ObjectManager $em) + public function __construct(EntityManager $entityManager) { - $this->em = $em; + $this->entityManager = $entityManager; } /** @@ -185,10 +188,10 @@ to and from the issue number and the ``Issue`` object:: { // no issue number? It's optional, so that's ok if (!$issueNumber) { - return null; + return; } - $issue = $this->em + $issue = $this->entityManager ->getRepository('AppBundle:Issue') // query for the issue with this id ->find($issueNumber) @@ -225,7 +228,7 @@ that message with the ``invalid_message`` option (see below). an empty string, 0 for integers or 0.0 for floats). Using the Transformer ---------------------- +~~~~~~~~~~~~~~~~~~~~~ Next, you need to instantiate the ``IssueToNumberTransformer`` class from inside ``TaskType`` and add it to the ``issue`` field. But to do that, you'll need an instance @@ -235,16 +238,18 @@ No problem! Just add a ``__construct()`` function to ``TaskType`` and force this to be passed in. Then, you can easily create and add the transformer:: // src/AppBundle/Form/TaskType.php + use AppBundle\Form\DataTransformer\IssueToNumberTransformer; - use Doctrine\Common\Persistence\ObjectManager; + use Doctrine\Common\Persistence\EntityManager; + // ... class TaskType extends AbstractType { - private $em; + private $entityManager; - public function __construct(ObjectManager $em) + public function __construct(EntityManager $entityManager) { - $this->em = $em; + $this->entityManager = $entityManager; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -259,7 +264,7 @@ to be passed in. Then, you can easily create and add the transformer:: // ... $builder->get('issue') - ->addModelTransformer(new IssueToNumberTransformer($this->em)); + ->addModelTransformer(new IssueToNumberTransformer($this->entityManager)); } // ... @@ -268,8 +273,8 @@ to be passed in. Then, you can easily create and add the transformer:: Now, when you create your ``TaskType``, you'll need to pass in the entity manager:: // e.g. in a controller somewhere - $em = $this->getDoctrine()->getManager(); - $form = $this->createForm(new TaskType($em), $task); + $entityManager = $this->getDoctrine()->getManager(); + $form = $this->createForm(new TaskType($entityManager), $task); // ... @@ -312,23 +317,23 @@ First, create the custom field type class:: namespace AppBundle\Form; use AppBundle\Form\DataTransformer\IssueToNumberTransformer; - use Doctrine\Common\Persistence\ObjectManager; + use Doctrine\ORM\EntityManager; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class IssueSelectorType extends AbstractType { - private $em; + private $entityManager; - public function __construct(ObjectManager $em) + public function __construct(EntityManager $entityManager) { - $this->em = $em; + $this->entityManager = $entityManager; } public function buildForm(FormBuilderInterface $builder, array $options) { - $transformer = new IssueToNumberTransformer($this->em); + $transformer = new IssueToNumberTransformer($this->entityManager); $builder->addModelTransformer($transformer); } @@ -423,15 +428,15 @@ types of underlying data. In any form, the three 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``, +#. **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 +#. **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 +#. **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::submit($data)``, the ``$data`` is in the "view" data format. @@ -463,3 +468,7 @@ 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. + +.. tip:: + + As a general rule, the normalized data should contain as much information as possible. From 1514fddda25d4095c2c80b000856e2b20a89f576 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 28 Jun 2015 12:27:39 -0400 Subject: [PATCH 3/6] using the aliases entity manager service name --- cookbook/form/data_transformers.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cookbook/form/data_transformers.rst b/cookbook/form/data_transformers.rst index d6e102e5f89..e66d9a86c70 100644 --- a/cookbook/form/data_transformers.rst +++ b/cookbook/form/data_transformers.rst @@ -365,19 +365,20 @@ it's recognized as a custom field type: .. code-block:: yaml + # app/config/services.yml services: app.type.issue_selector: class: AppBundle\Form\IssueSelectorType - arguments: ["@doctrine.orm.default_entity_manager"] + arguments: ["@doctrine.orm.entity_manager"] tags: - { name: form.type, alias: issue_selector } .. code-block:: xml - + - + @@ -391,7 +392,7 @@ it's recognized as a custom field type: ->setDefinition('app.type.issue_selector', new Definition( 'AppBundle\Form\IssueSelectorType' ), array( - new Reference('doctrine.orm.default_entity_manager'), + new Reference('doctrine.orm.entity_manager'), )) ->addTag('form.type', array( 'alias' => 'issue_selector', From 41bb9078ac4cff6fff3c36122525ee0db75df8ca Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 28 Jun 2015 12:29:06 -0400 Subject: [PATCH 4/6] adding use statement --- cookbook/form/data_transformers.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/cookbook/form/data_transformers.rst b/cookbook/form/data_transformers.rst index e66d9a86c70..9986073eec5 100644 --- a/cookbook/form/data_transformers.rst +++ b/cookbook/form/data_transformers.rst @@ -60,6 +60,7 @@ class:: // src/AppBundle/Form/TaskType.php use Symfony\Component\Form\CallbackTransformer; + use Symfony\Component\Form\FormBuilderInterface; // ... class TaskType extends AbstractType From 28d7f89a8341380257c2c582267a3ec86fcd7c64 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 28 Jun 2015 12:30:27 -0400 Subject: [PATCH 5/6] fixed typo thanks to @OskarStark --- cookbook/form/data_transformers.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cookbook/form/data_transformers.rst b/cookbook/form/data_transformers.rst index 9986073eec5..ac607335f11 100644 --- a/cookbook/form/data_transformers.rst +++ b/cookbook/form/data_transformers.rst @@ -100,8 +100,8 @@ in your code. :class:`Symfony\\Component\\Form\\DataTransformerInterface` - so you can create your own classes, instead of putting all the logic in the form (see the next section). -Harder Examle: Transforming an Issue Number into an Issue Entity ----------------------------------------------------------------- +Harder Example: Transforming an Issue Number into an Issue Entity +----------------------------------------------------------------- Say you have a many-to-one relation from the Task entity to an Issue entity (i.e. each Task has an optional foreign key to its related Issue). Adding a listbox with all From 0762848fa4ae0c658d9fefc1c2981f528a699be7 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 8 Jul 2015 17:53:16 -0400 Subject: [PATCH 6/6] Adding an example of how the other format for attaching transformers looks --- cookbook/form/data_transformers.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cookbook/form/data_transformers.rst b/cookbook/form/data_transformers.rst index ac607335f11..cfc8cf1035b 100644 --- a/cookbook/form/data_transformers.rst +++ b/cookbook/form/data_transformers.rst @@ -100,6 +100,14 @@ in your code. :class:`Symfony\\Component\\Form\\DataTransformerInterface` - so you can create your own classes, instead of putting all the logic in the form (see the next section). +You can also add the transformer, right when adding the field by changing the format +slightly:: + + $builder->add( + $builder->create('description', 'textarea') + ->addModelTransformer(...) + ); + Harder Example: Transforming an Issue Number into an Issue Entity -----------------------------------------------------------------