Anna Filina

Doctrine not saving ManyToMany

March 26th, 2015

Say you have a ManyToMany relationship, like Post <—> Tag.

// AcmeBundle/Entity/Post.php
/**
 * @ORM\ManyToMany(targetEntity="Tag", inversedBy="posts", cascade={"persist","remove"})
 */
 protected $tags;

// AcmeBundle/Entity/Tag.php
/**
 * @ORM\ManyToMany(targetEntity="Post", mappedBy="tags", cascade={"persist","remove"})
 */
 protected $posts;

When you add tags to a Post and the save the Post, everything is great. But when you add posts to a Tag and then save the Tag, the posts are not saved. Why? Because Doctrine persists changes only on the owning side of a relation. The owning side is the one that has the inversedBy.

How to fix this? Simple. If you're accessing the Entity directly (without a Symfony form), then there is only 1 step.

1. On the inverse side (Tag), you will need to edit the following methods: addPost and removePost.

public function addPost(Post $post)
{
    $this->posts[] = $post;
    $post->addTag($this);
    return $this;
}

public function removePost(Post $post)
{
    $this->posts->removeElement($post);
    $post->removeTag($this);
}

This will keep the owning side in sync, so that persisting either the Post or the Tag will save the association. At this point, you can go ahead and clean up the cascade in the Tag entity, as it's no longer necessary.

@ORM\ManyToMany(targetEntity="Post", mappedBy="tags")

2. If you're using a Symfony form with a collection field to associate the posts, you need to add an extra option in your form builder:

->add('posts', 'collection', array(
    'by_reference' => false,
    //...
))

This is only needed in the Tag form (the inverse side of the relationship). If you omit this option, it will default to true and will set posts by reference. It will go fetch the ArrayCollection of posts and manipulate it directly, therefore not calling addPost/removePost that you need to keep things in sync. Setting this option to false will force the form to call these methods.

Happy coding!

Comments

Jesse April 17th, 2015 http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html
Melvin Loos July 24th, 2015 OMG thanks a lot! The moment I read owning sides I immediately knew what I was doing wrong. I put the owning side on the wrong entity, didn't notice it and therefore had been rereading my code like forever. DOH! Must be time for weekend.... Thanks again!
Anna July 24th, 2015 Glad to be helpful. Have a good and relaxing weekend :)
Jaime September 20th, 2015 Thanks you a lot! Great post!
Agustín Houlgrave December 17th, 2015 Thanks! Just what i needed!
mboy January 27th, 2016 Hi

what happens if Tag belong to 2 different Posts (Post1, Post2) and i delete one of them (Post1)?
wouldn't cascade=remove cause the Tag to be deleted as well and therefore remove the connection between Tag and Post2?
mboy January 27th, 2016 as per this answer http://stackoverflow.com/questions/24612664/understanding-doctrine-cascade-operations using cascade=remove on ManyToMany is wrong
Viktor February 28th, 2016 Thanks a lot. Doctrine doen't genrated (I assume Your code is mine:))
$post->addTag($this);
in addPost method. Therefore i had a message that tag_id is null. And after i added this line it started working!

Thanks a lot!
Steve May 31st, 2016 I can't believe this is an issue. I've spent 45 mins looking for a solution and this is the only one I found that worked. Well done Anna.

Doctrine / Symfony don't even seem to share this little snippet.
Mikael Brosset July 6th, 2016 I love you for this article
Roland November 14th, 2016 Thank you Anna, your article saved a lot of time for me today !
Gabriel November 17th, 2016 Muchas gracias!!!
Muhammad Bilal January 11th, 2017 Thank you very much.you saved my lot of time .
Rolf February 17th, 2017 Thanks a lot Anna, you made my day !!!
Dimitris December 19th, 2017 Thank you so much Anna, such a beautiful and graceful solution!
mojtaba dehdari August 6th, 2018 Thank you so much. That was very useful.
mario May 1st, 2019 This saved my day. Thanks.
Maxime May 20th, 2019 Thanks a lot =D


This page is protected by reCAPTCHA and the Google
Privacy Policy and Terms of Service apply.

Phone: +1 514-918-7866 | E-mail: me@afilina.com