Doctrine flush() error: Expected value of type "Doctrine\Common\Collections\Collection|array"

Felix Letkemann picture Felix Letkemann · May 6, 2016 · Viewed 10.4k times · Source

I have a strange problem using a many-to-many relation in Symfony (with Doctrine), I've never had before in symfony projects with many-to-many relations and I can't find any difference to the other projects.

I have the two entitys Product and Tag and a many-to-many relation to each other. Unfortunately, if I try to add a product to a tag or vice versa, the error

Expected value of type "Doctrine\Common\Collections\Collection|array" for association field "TestBundle\Entity\Product#$tags", got "TestBundle\Entity\Tag" instead.

appears. The code used to add a tag to a product:

    $tag1 = $em->getRepository('TestBundle:Tag')->findOneBy(array(
        'tag' => "Bla"
    ));

    $tag1->addProduct($product);
    $em->persist($tag1);
    $em->persist($product);

    $em->flush();

Of course, the variable $tag1 and $product both contain a valid entity. The YAML file for the many-to-many relations (I cut away irrelevant parts):

TestBundle\Entity\Tag:
type: entity
table: tags
repositoryClass: TestBundle\Repository\TagRepository
id:
    id:
        type: integer
        id: true
        generator:
            strategy: AUTO
fields:
    tag:
        type: string
        length: 255
        unique: true
manyToMany:
    products:
        targetEntity: Product
        mappedBy: tags
lifecycleCallbacks: {  }

Product:

TestBundle\Entity\Product:
type: entity
table: products
repositoryClass: TestBundle\Repository\ProductRepository
id:
    id:
        type: integer
        id: true
        generator:
            strategy: AUTO
fields:
    name:
        type: string
        length: 255
        unique: true
manyToOne:
    manufacturer:
        targetEntity: Manufacturer
        inversedBy: products
        joinColumn:
            name: manufacturer_id
            referencedColumnName: id
            onDelete: CASCADE
manyToMany:
    tags:
        targetEntity: Product
        inversedBy: products
        joinTable:
            name: tags2products
            joinColumns:
                tag_id:
                    referencedColumnName: id
            inverseJoinColumns:
                product_id:
                    referencedColumnName: id
lifecycleCallbacks: {  }

The setter and getter functions also don't contain any special tricks: The Tag.php entity file contains:

 /**
 * Constructor
 */
public function __construct()
{
    $this->product = new \Doctrine\Common\Collections\ArrayCollection();
}

/**
 * Add product
 *
 * @param \TestBundle\Entity\Product $product
 *
 * @return Tag
 */
public function addProduct(\TestBundle\Entity\Product $product)
{
    $product->addTag($this);
    $this->product[] = $product;
    return $this;
}


public function removeProduct(\TestBundle\Entity\Product $product)
{
    $this->product->removeElement($product);
}

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

While the Product.php contains:

 /**
 * Add tag
 *
 * @param \TestBundle\Entity\Tag $tag
 *
 * @return Product
 */
public function addTag(Tag $tag)
{
    $this->tags->add($tag);
    //$this->tags[] = $tag;
    return $this;
}

/**
 * Remove tag
 *
 * @param \TestBundle\Entity\Tag $webpage
 */

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

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

I also tried to add a $this->tags = new ArrayCollection(); to the constructor of the product, but it didnt change anything.

Also, there is no problem adding, reading and persisting tags to products. The error is thrown as soon as I call $em->flush().

Does anybody know why my Product entity expects a array collection? I never told him to expect one! Thank you very much in advance!

Answer

Alexandru Cosoi picture Alexandru Cosoi · May 6, 2016

The error is telling you that the property "#tags" of the entity TestBundle\Entity\Product that you are trying to flush, contains an object of type TestBundle\Entity\Tag instead of a collection of this object. Doctrine expects this collection/array because the metadata for that property states that TestBundle\Entity\Product is in a many-yo-many with TestBundle\Entity\Tag and the relation is done via the property "#tags". This should happen if:

//You did this
$this->tags = $tag;

//instead of what you actually did which is correct
$this->tags->add($tag);

//Or 

$this->tags[] = $tag;

But the code that you posted here should not produce that exception.

Are you sure there is no other place where an accessor method is called that changes the tags property of TestBundle\Entity\Product? Something like and event listener?