Map a form's text field to an entity's ArrayCollection
Asked Answered
J

1

6

I am using tags on a form using tagsinput :

enter image description here

This plugin ends-up with a single text field containing tags separated by a comma (eg: tag1,tag2,...)

Those tags are currently managed on a non-mapped form field:

    $builder
       // ...
       ->add('tags', 'text', array(
               'mapped' => false,
               'required' => false,
       ))
    ;

And finally, they are stored on an ArrayCollection, as this is a bad practice to store multiple values in a database field:

/**
 * @var ArrayCollection[FiddleTag]
 *
 * @ORM\OneToMany(targetEntity="FiddleTag", mappedBy="fiddle", cascade={"all"}, orphanRemoval=true)
 */
protected $tags;

To map my form to my entity, I can do some code in my controller like this:

    $data->clearTags();
    foreach (explode(',', $form->get('tags')->getData()) as $tag)
    {
        $fiddleTag = new FiddleTag();
        $fiddleTag->setTag($tag);
        $data->addTag($fiddleTag);
    }

But this looks the wrong way at first sight.

I am wondering what is the best practice to map my entity to my form, and my form to my entity.

Joub answered 28/12, 2014 at 22:51 Comment(0)
E
9

This is tricky since you aren't just embedding a collection of Tag forms that are say, all separate text fields. I suppose you could do that with some trickery, but what about using a data transformer instead? You could convert a comma-separated list of tags to an ArrayCollection and pass that back to the form, and on the flip-side, take the collection and return the tags as a comma-separated string.

Data transformer

FiddleTagsTransformer.php

<?php

namespace Fuz\AppBundle\Transformer;

use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Form\DataTransformerInterface;
use Fuz\AppBundle\Entity\FiddleTag;

class FiddleTagTransformer implements DataTransformerInterface
{

    public function transform($tagCollection)
    {
        $tags = array();

        foreach ($tagCollection as $fiddleTag)
        {
            $tags[] = $fiddleTag->getTag();
        }

        return implode(',', $tags);
    }

    public function reverseTransform($tags)
    {
        $tagCollection = new ArrayCollection();

        foreach (explode(',', $tags) as $tag)
        {
            $fiddleTag = new FiddleTag();
            $fiddleTag->setTag($tag);
            $tagCollection->add($fiddleTag);
        }

        return $tagCollection;
    }

}

Note: you cannot specify ArrayCollection type to public function transform($tagCollection) because your implementation should match the interface.

Form type

The second step is to replace your form field declaration so it will use the data transformer transparently, you'll not even need to do anything in your controller:

FiddleType.php

$builder
   // ...
   ->add(
        $builder
            ->create('tags', 'text', array(
                    'required' => false,
            ))
            ->addModelTransformer(new FiddleTagTransformer())
   )
;

Validation

You can use @Assert\Count to limit the number of allowed tags, and @Assert\Valid if your FiddleTag entity has some validation constraints itself.

Fiddle.php

/**
 * @var ArrayCollection[FiddleTag]
 *
 * @ORM\OneToMany(targetEntity="FiddleTag", mappedBy="fiddle", cascade={"all"}, orphanRemoval=true)
 * @Assert\Count(max = 5, maxMessage = "You can't set more than 5 tags.")
 * @Assert\Valid()
 */
protected $tags;

Further reading

See the Symfony2 doc about data transformers: http://symfony.com/doc/current/cookbook/form/data_transformers.html

See these posts for some other ideas:

Parsing comma separated string into multiple database entries (eg. Tags)

How does Symfony 2 find custom form types?

Evita answered 29/12, 2014 at 1:56 Comment(3)
A data transformer is exactly what I needed. I did not even know about it until now, thank you.Joub
No problem - I threw that code together pretty quickly so if there's something quirky that doesn't work let me know.Evita
I will edit your answer when the job'll be done if I find any, no problem. You gave me the right direction.Joub

© 2022 - 2024 — McMap. All rights reserved.