Search code examples
phpsymfonydoctrine-ormsymfony-sonatasonata-admin

Symfony2 & SonataAdmin: ManyToMany field being inserted twice upon create (causing duplicate primary key error) but works on update


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!


Solution

  • 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?