Symfony2和SonataAdmin:ManyToMany字段在创建时被插入两次(导致重复的主键错误)但是在更新时工作

I'm trying to add tags to a Story entity. I've created my own bundle, form type and data transformer to make use of textext and I thought all was working well...but it turns out that this is only the case when adding tags to existing stories. If I try and add tags at the same time as creating a story I get an exception thrown because it is trying to create the record in the joining table twice, resulting in a duplicate primary key error.

I don't think that the problem lies with my custom form type and data transformer because I've debugged into the controller after binding the request to the form but before persisting and everything here seems absolutely fine - my story entity contains only the tags I've added, no duplicates.

Here is the configuration of the tags attribute, in case it helps:

/**
 * @ORM\ManyToMany(targetEntity="Tag")
 * @ORM\JoinTable(name="story__story_tags",
 *   joinColumns={@ORM\JoinColumn(name="story_id", referencedColumnName="id")},
 *   inverseJoinColumns={@ORM\JoinColumn(name="tag_id", referencedColumnName="id")}
 * )
 */
protected $tags;

Here is an excerpt from my log output:

INSERT INTO stories (created_at, updated_at, published_at, author_id, media_id, title, short_title, summary, text, slug, active, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ({"1":{"date":"2012-09-11 15:07:20","timezone_type":3,"timezone":"Europe\/London"},"2":{"date":"2012-09-11 15:07:20","timezone_type":3,"timezone":"Europe\/London"},"3":{"date":"2012-09-11 14:56:00","timezone_type":3,"timezone":"Europe\/London"},"4":10,"5":68,"6":"Story with tags","7":"","8":"Test","9":"<p>test<\/p>","10":"story-with-tags","11":true,"12":"media"})
INSERT INTO story__media (id, story_media_id, media_size, show_caption) VALUES (?, ?, ?, ?) ({"1":"130","2":null,"3":"0","4":false})
SELECT s0_.slug AS slug0 FROM stories s0_ LEFT JOIN story__gallery s1_ ON s0_.id = s1_.id LEFT JOIN story__media s2_ ON s0_.id = s2_.id LEFT JOIN story__competition s3_ ON s0_.id = s3_.id WHERE s0_.slug LIKE 'story-with-tags-872875%' AND s0_.id <> ? ([130])
INSERT INTO story__counters (story_id, hits, shares, comments, updated_at) VALUES (?, ?, ?, ?, ?) ({"1":130,"2":0,"3":0,"4":0,"5":{"date":"2012-09-11 15:07:20","timezone_type":3,"timezone":"Europe\/London"}})
INSERT INTO story__sections (story_id, section_id, section_position) VALUES (?, ?, ?) ({"1":130,"2":212,"3":1})
UPDATE stories SET slug = ?, updated_at = ? WHERE id = ? (["story-with-tags-872875",{"date":"2012-09-11 15:07:20","timezone_type":3,"timezone":"Europe\/London"},130])
website/story/130 (DELETE) 11.00 ms
website/story/130 (PUT) 5.12 ms
Context: { title: 'Story with tags', summary: Test, text: '<p>test</p>', author: billy-wiggins, publishedAt: 1347371760, tags: [test] }
INSERT INTO story__story_tags (story_id, tag_id) VALUES (?, ?) ([130,9])
INSERT INTO story__story_tags (story_id, tag_id) VALUES (?, ?) ([130,9])

As you can see from the 3rd to last line, my entity has been indexed into elasticsearch and it contains only 1 tag: "test". Following this, you can see the 2 duplicate queries attempting to relate tag #9 to story #130. Does anyone know why this could be happening?

Is it possible to somehow make these insert queries use the INSERT IGNORE syntax as that would at least bypass the problem.

Thanks!

Well I've managed to find a way of 'fixing' this problem using a smelly hack. I'm not likely to accept this as the correct answer unless there's no other way, so please do respond if you know of a nice way of fixing this.

Because the problem only arises when creating my Story entity and tags at the same time, I've extended the create method of the base Admin class like so: (prepare to hold your nose.....now!)

/**
 * TODO achieve this in a non-smelly way
 * 
 * @param Story $object
 * @return mixed|void
 */
public function create($object)
{
    // Create fails when story has tags, so remove them...
    $tags = $object->getTags();
    $object->setTags(new \Doctrine\Common\Collections\ArrayCollection());
    parent::create($object);

    // ...then add them again and update.
    $object->setTags($tags);
    $this->getModelManager()->update($object);
}

Surely there must be a tidier solution?

Put this is __construct():

$this->Tags = new \Doctrine\Common\Collections\ArrayCollection() ;

You have to do that for all one-2-many and many-2-many relations.

This solution works fine:

public function __construct()
{
    $this->tags = new ArrayCollection();
}

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

Duplicate key doesnt added next time