TheGremlyn
TheGremlyn

Reputation: 343

Symfony Data Transformer with Collection Field

I'm trying to figure out how to get a data transformer working on a collection field. Currently a user can create a Campaign and attached up to three Tags. As it is, every time a user enters a string for a tag (it is open text input), a new entry for that tag is created whether it exists already or not. What I want to do is create a new Tag if a matching one doesn't exist, but use an existing Tag if it does.

I have tried all manner of combinations and placements of the transformer, and many seem to generate various errors. Most commonly I see:

Expected argument of type "object, array or empty", "string" given

I have gotten it into a non-erroring state with the code is below, which does output a text field but it doesn't give the field any attributes (id or name) of the Campaign form to link it up and therefore does me no good.

Campaign Entity

/**
 * @ORM\Entity
 * @ORM\Table(name="campaigns")
 */
class Campaign
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string")
     */
    protected $name;

    /**
     * @var integer
     *
     * @ORM\Column(name="length", type="integer")
     */
    protected $length;

    /**
     * @var string
     *
     * @ORM\Column(name="start_date", type="datetime")
     */
    protected $startDate;

    /* ... snip ... */

    /**
     * @ORM\ManyToMany(targetEntity="Tag", inversedBy="campaigns", cascade={"persist"})
     * @ORM\JoinTable(name="campaigns_tags")
     */
    protected $tags;

    /* ... snip ... */

Tag Entity

/**
 * @ORM\Entity
 * @ORM\Table(name="tags")
 */
class Tag
{
    use TimestampTrait;

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var string
     *
     * @ORM\Column(name="tag", type="string")
     */
    protected $tag;

    /* ... snip ... */

Campaign Form

class CampaignFormType extends AbstractType
{
    public function __construct(TagFormType $tag_form, SecurityContext $security_context)
    {
        $this->tag_form = $tag_form;
        $this->user = $security_context->getToken()->getUser();
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name', 'text', array(
            'label' => 'Campaign Name:'
        ))
                ->add('length', 'integer', array(
                    'label' => 'Length (weeks):'
                ))
                ->add('startDate', 'date', array(
                    'label'  => 'Start Date:',
                    'widget' => 'single_text'
                ))
                ->add('tags', 'collection', array(
                    'type'         => $this->tag_form,
                    'allow_add'    => TRUE,
                    'by_reference' => FALSE
                ));
    /* ... snip ... */

Tag Form

class TagFormType extends AbstractType
{
    /**
     * @var EntityManager
     */
    private $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

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

    public function getParent()
    {
        return 'text';
    }
    /* ... snip ... */

Tag Transformer

class TagToStringTransformer implements DataTransformerInterface
{
    /**
     * @var EntityManager
     */
    private $em;

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

    /**
     * @param Tag|null $tag
     *
     * @return string
     */
    public function transform($tag)
    {
        if (empty($value)) {
            return "";
        }

        return $tag->getTag();
    }

    /**
     * @param string $string
     *
     * @return Tag|null
     */
    public function reverseTransform($string)
    {
        if (empty($string)) {
            return NULL;
        }

        $tag = $this->em->getRepository('AppBundle:Tag')->findOneBy(array('tag' => $string));

        // If the tag doesn't exist, create it
        if (empty($tag)) {
            $tag = new Tag();

            $tag->setTag($string);
        }

        return $tag;
    }
}

UPDATE I dig some more digging on this and changed the TagFormType a little to look like the code below and that seems to properly output the fields with the expected name/id attributes:

class TagFormType extends AbstractType
{
    /**
     * @var EntityManager
     */
    private $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $transformer = new TagToStringTransformer($this->em);
        $builder->add($builder->create('tag', 'text', array('label' => FALSE))->addModelTransformer($transformer));
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(
            array(
                'data_class' => 'AppBundle\Entity\Tag',
            )
        );
    }

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

Upvotes: 2

Views: 6574

Answers (1)

TheGremlyn
TheGremlyn

Reputation: 343

The problem I was having seems to stem from how I was creating the field. Simply adding the field did not work properly, but if I switched to $builder->add($builder->create('tag', 'text', array('label' => FALSE))->addModelTransformer($transformer)); in the TagFormType class then it almost works as expected. The only problem was that the transformer was working on the tag field of the tag entity, turning that into a Tag object, instead of converting each tag of the Campaign form to it's already existing conuterpart (or returning a new one if it didn't exist).

The answer to get this working the way I needed it: I implemented a form event listener on POST_SUBMIT. I take each submitted tag object and check to see if they exist already or not. If they exist, I swap out the object.

Upvotes: 2

Related Questions