5

I have a many to many relationship between Tag and Article entities, the insertion works well but the creation of the edit form (the editAction function) does not work. All the code is there:

Article.php
<?php
namespace Diapalema\DiapalemaBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

class Article
{ 
/**
 * @var int
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;

/**
 * @var string
 *
 * @ORM\Column(name="titre", type="string", length=255)
 */
private $titre;

/**
 * @var \DateTime
 *
 * @ORM\Column(name="created", type="datetimetz")
 */
private $created;

/**
 * @var \DateTime
 *
 * @ORM\Column(name="updated", type="datetimetz")
 */
private $updated;

/**
 * @ORM\ManyToOne(targetEntity="User", inversedBy="article", cascade=
 {"persist"})
 */
private $auteur;

/**
 * BlogArticle constructor.
 * @param \DateTime $created
 * @param \DateTime $updated
 */
public function __construct()
{
    $this->created = $created;
    $this->updated = $updated;
    $this->tags = new ArrayCollection();
}

public function addTag(Tag $tag)
{
    $this->tags->add($tag);
    return $this;
}

public function addTags($tags)
{
    foreach($tags as $tag){
        $this->addTag($tag);
    }
    return $this;
}

public function removeTag(Tag $tag)
{
    $this->tags->removeElement($tag);
    return $this;
}

public function removeTags($tags)
{
    foreach($tags as $tag){
        $this->removeTag($tag);
    }
    return $this;
}

public function setTags(Tag $tag)
{
    $this->tags[] = $tag;
    return $this;
}

/**
 * Get tags
 *
 * @return \Doctrine\Common\Collections\Collection
 */
public function getTags()
{
    return $this->tags;
}

/**
 * Get id
 *
 * @return int
 */
public function getId()
{
    return $this->id;
}
}

Tag.php

 class Tag
 {
 /**
 * @var int
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;

/**
 * @var string
 *
 * @ORM\Column(name="tagname", type="string", length=255)
 */
private $tagname;

/**
 * 
 @ORM\ManyToMany(targetEntity="Diapalema\DiapalemaBundle\Entity\Article", 
 mappedBy="tags", cascade={"persist","remove"})
 */
private $articles;

/**
 * Tag constructor.
 */
public function __construct()
{
    $this->articles = new ArrayCollection();
}

/**
 * @param Article $articles
 */
public function setArticles($articles)
{
    $this->articles = $articles;
}

public function getArticles()
{
    return $this->articles;
}

public function addArticles(Article $article)
{
    $this->articles[] = $article;
    $article->addTag($this);
    return $this;
}

public function removeArticles(Article $article)
{
    $this->articles->removeElement($article);
    $article->removeTag($this);
}

/**
 * Get id
 *
 * @return int
 */
public function getId()
{
    return $this->id;
}
}

StringToTagsTransformer.php

<?php
namespace Diapalema\DiapalemaBundle\Form;

use Symfony\Component\Form\DataTransformerInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Collections\ArrayCollection;
use Diapalema\DiapalemaBundle\Entity\Tag;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Ldap\Adapter\ExtLdap\Collection;

class StringToTagsTransformer implements DataTransformerInterface
{

private $om;
public function __construct(ObjectManager $om)
{
    $this->om = $om;
}

private function stringToArray($string)
{
    $tags = explode(',', $string);
    foreach ($tags as &$text) {
        $text = trim($text);
    }
    return array_unique($tags);
}

public function transform($value)
{
    if (null === $value) {
        return null;
    }
    if (!($value instanceof ArrayCollection)) {
        throw new UnexpectedTypeException($value, 
        'Doctrine\Common\Collections\ArrayCollection');
    }
    $tags = array();
    foreach ($value as $tag) {
        array_push($tags, $tag->getTagname());
    }
    return implode(',', $tags);
}

public function reverseTransform($value)
{
    $tagCollection = new ArrayCollection();
    if ('' === $value || null === $value) {
        return $tagCollection;
    }
    if (!is_string($value)) {
        throw new UnexpectedTypeException($value, 'string');
    }
    foreach ($this->stringToArray($value) as $name) {
        $tag = $this->om->getRepository('DiapalemaBundle:Tag')
            ->findOneBy(array('tagname' => $name));

        if (null === $tag) {
            $tag = new Tag();
            $tag->setTagname($name);

            $this->om->persist($tag);
        }
        $tagCollection->add($tag);
    }
    return $tagCollection;
   }
 }

TagType.php

 <?php
 namespace Diapalema\DiapalemaBundle\Form;

 use Diapalema\DiapalemaBundle\Entity\Tag;
 use Symfony\Component\Form\AbstractType;
 use Symfony\Component\Form\Extension\Core\Type\TextType;
 use Doctrine\Common\Persistence\ObjectManager;
 use Symfony\Component\Form\FormBuilderInterface;
 use Symfony\Component\OptionsResolver\OptionsResolver;

 class TagType extends AbstractType
 {

 private $om;
 public function __construct(ObjectManager $om)
 {
    $this->om = $om;
 }

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

 public function configureOptions(OptionsResolver $resolver)
 {
    $resolver->setDefaults(
        array(
            'data_class' => Tag::class
        )
    );
 }

 public function getParent()
 {
    return TextType::class;
 }

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

ArticleType.php

<?php

namespace Diapalema\DiapalemaBundle\Form;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ArticleType extends AbstractType
{
/**
 * {@inheritdoc}
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('tags',TagType::class, array(
            'required' => false,
            'label' => 'form.tag',
            'translation_domain' => 'FOSUserBundle',
            'attr' => array(
                'class' => 'form-control',
                'multiple' => true,
            ))
        );
}
 /**
 * {@inheritdoc}
 */
public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Diapalema\DiapalemaBundle\Entity\Article'
    ));
}

/**
 * {@inheritdoc}
 */
public function getBlockPrefix()
{
    return 'diapalema_diapalemabundle_article';
}
}

ArticleController.php

 /**
 * Displays a form to edit an existing Article entity.
 *
 */
public function editAction($id)
{
    $em = $this->getDoctrine()->getManager();
    $entity = $em->getRepository('DiapalemaBundle:Article')->find($id);
    if (!$entity) {
        throw $this->createNotFoundException('Impossible de trouver 
        l\'entité concernée.');
    }
    $editForm = $this->createEditForm($entity);
    return $this-> 
    render('DiapalemaBundle:Admin/Layout:edit_article.html.twig', array(
        'entity'      => $entity,
        'edit_form'   => $editForm->createView()
    ));
 }

/**
 * Creates a form to edit a Article entity.
 * @param Article $entity The entity
 * @return \Symfony\Component\Form\Form The form
 */
private function createEditForm(Article $entity)
{
    $form = $this->createForm(ArticleType::class, $entity);
    $form->add('submit', SubmitType::class, array(
        'label' => 'form.editbutton',
        'translation_domain' => 'FOSUserBundle',
        'attr' =>
            array(
                'class' => 'btn btn-success'
            )
    ));
    return $form;
}

services.yml

services:
app.type.tag:
    class: Diapalema\DiapalemaBundle\Form\TagType
    arguments: ['@doctrine.orm.entity_manager']
    tags:
        - { name: form.type, alias: tag }

I have the following error: Expected argument of type "Doctrine\Common\Collections\ArrayCollection","Doctrine\ORM\PersistentCollection" given. Help me!

1 Answers1

3

The problem comes when you expect to get ArrayCollection in StringToTagsTransformer

if (!($value instanceof ArrayCollection)) {
    throw new UnexpectedTypeException($value, 
    'Doctrine\Common\Collections\ArrayCollection');
}

Actually this happens because ArrayCollection class is used from Doctrine only for eagerly fetched relations and by default relations are always fetched lazily. So you have to fetch it eagerly or just check if $value is instance of Doctrine\Common\Collections\Collection which is the interface implemented by both classes

use Doctrine\Common\Collections\Collection;

if (!($value instanceof Collection)) {
    throw new UnexpectedTypeException($value, Collection::class);
}
DrKey
  • 3,365
  • 2
  • 29
  • 46
  • So I change the ArrayCollection type by Collection ? – Moustapha Cissé Jan 22 '18 at 08:42
  • Exactly @MoustaphaCissé since it's an interface implemented by both `PersistentCollection` and `ArrayCollection` classes. – DrKey Jan 22 '18 at 08:45
  • I have this error now @DrKey : The form's view data is expected to be an instance of class Diapalema\DiapalemaBundle\Entity\Tag, but is a(n) string. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) string to an instance of Diapalema\DiapalemaBundle\Entity\Tag. – Moustapha Cissé Jan 23 '18 at 08:31
  • in twig : `
    {{ form_label(edit_form.tags) }} {{ form_widget(edit_form.tags) }}
    `
    – Moustapha Cissé Jan 23 '18 at 08:36
  • 1
    changed to **public function setMyMethod(\Doctrine\Common\Collections\Collection $array)** did the trick! – m47730 Apr 11 '18 at 15:15