Reputation: 343
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.
/**
* @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 ... */
/**
* @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 ... */
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 ... */
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 ... */
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
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