📜 ⬆️ ⬇️

Using Data Transformers in symfony2

Forms - in Symfony2, one of the most powerful tools, they represent many possibilities. Many secrets of working with Symfony2 are described in the Book of Recipes . I want to provide you with a translation of one recipe for working with forms, in Symfony 2 - using the date of transformers .
Often there is a need to convert the data entered by the user into a form in another format for use in your program. You can easily do this manually in the controller, but what if you want to use this form in different places? Let's say you have a “Task” object (task) associated with a code-to-one relationship with an “Issue” object (problem), for each “Task” it can be specified optionally “Issue”, which it solves. If we add a drop-down list of Issue problems to the Task editing form, it will be very difficult for us to navigate it. You can add a text field instead of a drop-down list and simply enter the Issue number.
You can try the conversion in the controller, but this is not the best idea. It would be much better if the Issue number was automatically converted to an Issue object. In this case, the “Data Transformers” come into play.

Creating transformers.


First we create the IssueToNumberTransformer class - this class will be responsible for the conversion from the Issue number to the Issue object:
// 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; } } 

You can create a new “Issue” object when the user has entered an unknown number and not throwing out a TransformationFailedException.

Using Transformers


Now we have a transformer, you just need to add it to our “Issue” field in one form or another.
 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 our transformer to it $builder->add( $builder->create('issue', 'text') ->addModelTransformer($transformer) ); } // ... } 


In this example, you must pass the EntityManager as an option when creating a form. Later, you will learn how to create a custom field for an “Issue” number to avoid having to send an EntityManager
 $taskForm = $this->createForm(new TaskType(), $task, array( 'em' => $this->getDoctrine()->getEntityManager(), )); 

')
Cool, we did it! Now the user will be able to enter the number in the text field, and it will be converted to an “Issue” object. This means that after successful binding ($ form-> bindRequest ($ request)), the form framework will deliver the real “Issue” object to the :: setIssue () method instead of the “Issue” number.
Note that when adding a transformer you need to use a slightly more complex syntax than when adding a field. The following is not a valid example of how the transformer will be applied to the entire form, and not to a specific field:
 //    -       //        $builder->add('issue', 'text') ->addModelTransformer($transformer); 


Model view and transformers


New in version 2.1: the name of the transformer methods was changed in Symfony 2.1. prependNormTransformer became addModelTransformer and appendClientTransformer became addViewTransformer.
In the example above, the transformer was used as a “transformer model”. In fact, there are two different types of transformers and three different types of input data.
In any form, there are three different data types:

  1. Model data is data in the format that is used in your application (for example, for example, an Issue type object). If you call the methods :: GetData or :: SetData in the form, you will be dealing with “Model data”.
  2. Norm Data are normalized versions of your data, they are usually the same as “Model data” data (although not in our example). Directly, they are not often used.
  3. View Data is a format that is used to fill out form fields. This is also the format in which the user will transmit data (submit the form). When we call the Form :: bind ($ data) method, $ data is presented in the format “View Data”

There are two different types of transformers that help us convert data from one view to another.

Which transformer you need depends on the specific situation.
To use "View Transformer", call the addViewTransformer method.

So why do we need data transformers?


In our example, the field is a text field, and we expect that the text field will always return scalar data in the “norm” and “view” formats. And in this case, the most acceptable transformer is the “model transformer”, which converts “norm data” into “model data” and back (the “Issue” number to the “Isuuse” object and back).
The difference between transformers is very subtle and you should always think that normalized data should represent “norm”. For example, for the “norm” text field, the normalized data is a text line, and for the “date” field, a DateTime object.

Using Transformers in "Custom Fields"


In the example that we described above, we use a transformer for the text field. It was quite simple to do this, but this approach has two drawbacks:
  1. You must always remember to apply a transformer when using the “isuue” field
  2. You should take care of the transfer in the em => EntityManager option whenever you create a form that uses a transformer.

Because we probably need to create a custom "custom" field type. First, create a class for a custom field type:
 // 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, we register our type of service and mark it with the form.type tag so that the field is recognized as a custom type:
 <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> 


Now you can use our special type issue_selector:
 // 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'; } } 

Source: https://habr.com/ru/post/152539/


All Articles