Felix Letkemann
Felix Letkemann

Reputation: 31

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

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!

Upvotes: 3

Views: 11140

Answers (2)

Felix Letkemann
Felix Letkemann

Reputation: 31

I finally found out what the strange problem was. Thank you Alexandr Cosoi for confirming the way I tried to add my entity. The Problem was a configuration error I didn't notice.

manyToMany:
tags:
    targetEntity: Product
    inversedBy: products
    joinTable:
        name: tags2products
        joinColumns:
            tag_id:
                referencedColumnName: id
        inverseJoinColumns:
            product_id:
                referencedColumnName: id

targetEntity was set to Product but it had to be set to "Tag". Changing it just solved my problem as expected. :)

Upvotes: 0

Alexandru Cosoi
Alexandru Cosoi

Reputation: 1000

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?

Upvotes: 4

Related Questions