thomastuts
thomastuts

Reputation: 3539

Symfony2: embedding form with many-to-many entities throws exception

Issue

I've been trying to follow the tutorial to embed a form in another one. What I'm trying to do here is add a task, and add multiple categories to it. I'm using the example at http://symfony.com/doc/current/book/forms.html#embedding-a-single-object, but I added some ORM annotations to make the relation many-to-many. As such, here is my code for the Task & Category entities:

Code

Task entity

<?php

namespace Acme\TaskBundle\Entity;

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

/**
 * Task
 *
 * @ORM\Table()
 * @ORM\Entity
 */
class Task
{
    /**
     * @ORM\ManyToMany(targetEntity="Category", inversedBy="tasks")
     * @ORM\JoinTable(name="tasks_categories")
     *
     * @Assert\Type(type="Acme\TaskBundle\Entity\Category")
     */
    protected $categories;

    // ...



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

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


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

    /**
     * Set name
     *
     * @param string $name
     * @return Task
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
    /**
     * Constructor
     */
    public function __construct()
    {
        $this->categories = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Add categories
     *
     * @param \Acme\TaskBundle\Entity\Category $categories
     * @return Task
     */
    public function addCategorie(\Acme\TaskBundle\Entity\Category $categories)
    {
        $this->categories[] = $categories;

        return $this;
    }

    /**
     * Remove categories
     *
     * @param \Acme\TaskBundle\Entity\Category $categories
     */
    public function removeCategorie(\Acme\TaskBundle\Entity\Category $categories)
    {
        $this->categories->removeElement($categories);
    }

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

Category entity

<?php

namespace Acme\TaskBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;

use Doctrine\ORM\Mapping as ORM;

/**
 * Category
 *
 * @ORM\Table()
 * @ORM\Entity
 */
class Category
{
    /**
     * @ORM\ManyToMany(targetEntity="Task", mappedBy="categories")
     */
    private $tasks;

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

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


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

    /**
     * Set name
     *
     * @param string $name
     * @return Category
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }
    /**
     * Constructor
     */
    public function __construct()
    {
        $this->tasks = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Add tasks
     *
     * @param \Acme\TaskBundle\Entity\Task $tasks
     * @return Category
     */
    public function addTask(\Acme\TaskBundle\Entity\Task $tasks)
    {
        $this->tasks[] = $tasks;

        return $this;
    }

    /**
     * Remove tasks
     *
     * @param \Acme\TaskBundle\Entity\Task $tasks
     */
    public function removeTask(\Acme\TaskBundle\Entity\Task $tasks)
    {
        $this->tasks->removeElement($tasks);
    }

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

Both forms have been auto-generated by using the doctrine:generate:form command. I changed the TaskType form to include the categories:

TaskType form

<?php

namespace Acme\TaskBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class TaskType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name')
            ->add('categories', new CategoryType())
        ;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\TaskBundle\Entity\Task',
            'cascade_validation' => true,
        ));
    }

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

Now when I go to the create page for tasks, I get this error:

The form's view data is expected to be an instance of class Acme\TaskBundle\Entity\Category, but is an instance of class Doctrine\Common\Collections\ArrayCollection. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms an instance of class Doctrine\Common\Collections\ArrayCollection to an instance of Acme\TaskBundle\Entity\Category.

I honestly have no idea how to fix it since this seemed a pretty straight-forward thing but apparently it isn't. Could someone help me out here please?

Upvotes: 0

Views: 1564

Answers (1)

Gintro
Gintro

Reputation: 635

in your Task Entity remove the validation for categories.
Symfony tries to validate a ArrayCollection as one Category!(hence the error)

* @Assert\Type(type="Acme\TaskBundle\Entity\Category")
*/
$categories;

It isn't necessary since it is a collection. (validation will be based on what type of objects are in the collection)
If you created a CategoryType form then this form should return Acme\TaskBundle\Entity\Category for it's data class.

class CategoryType {
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\TaskBundle\Entity\Category',
        .....
        ));
    }
}

Also, in your TaskType

$builder
    ->add('name')
    ->add('categories', new CategoryType()) // new CategoryType()
                                            // is not really needed here,
                                            // symfony will automatically detect
                                            // it's relation and create a new 
                                            // CategoryType if necessary.

Upvotes: 2

Related Questions