Doctrine 2 - ORM
  1. Doctrine 2 - ORM
  2. DDC-978

Many-To-Many relations are removed after Flush()

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Critical Critical
    • Resolution: Fixed
    • Affects Version/s: 2.0
    • Fix Version/s: 2.0.1, 2.1
    • Component/s: ORM
    • Security Level: All
    • Labels:
      None

      Description

      Let's say we have three entities: User, Channel, and Log

      Every time a User is created or updated, Channels are assigned to a Many-To-Many collection.
      This action gets logged in the Log entity.

      I have reproduced the bug in the Doctrine sandbox (see attachment). It's a fully working example.
      This is a summary:

      This works
      // Fetch two channels in an array
      $channels = .......
      
      // create a new user and assign channels
      $user = new User();
      $user->setName('FooBar');
      $user->setChannels($channels);
      
      //save the user
      $em->persist($user);
      $em->flush();
      
      // log it
      $log = new Log();
      $log->setMessage('User created with two channels');
      $log->setItem($user);
      
      $em->persist($log);
      $em->flush();
      
      This also works
      // Fetch two channels in an array
      $channels = .......
      
      // fetch an existing user, *change something to the user*, and assign channels
      $user = .............
      $user->setName('Gaz');
      $user->setChannels($channels);
      
      //save the user
      $em->persist($user);
      $em->flush();
      
      // log it
      $log = new Log();
      $log->setMessage('User created with two channels');
      $log->setItem($user);
      
      $em->persist($log);
      $em->flush();
      
      This does not work (channels are removed)
      // Fetch two channels in an array
      $channels = .......
      
      // fetch an existing user and only assign channels, change nothing else
      $user = .............
      $user->setChannels($channels);
      
      //save the user
      $em->persist($user);
      $em->flush();
      
      // log it
      $log = new Log();
      $log->setMessage('User created with two channels');
      $log->setItem($user);
      
      $em->persist($log);
      $em->flush();
      

      This last piece of code generates following SQL code (summarized):

      DELETE FROM user_channels WHERE userId = 1
      INSERT INTO user_channels (userId, channelId) VALUES (1, 2);
      DELETE FROM user_channels WHERE userId = 1
      

      If you remove the $em->flush() after $em->persist($user); in the last example, the code works (channels are persisted).

      I did not have this problem with Doctrine2 Alpha4.

        Issue Links

          Activity

          Hide
          Benjamin Eberlei added a comment -

          What do you do in "setChannels"? Do you completly overwrite the previous content of the association variable "User::$channels" ?

          Show
          Benjamin Eberlei added a comment - What do you do in "setChannels"? Do you completly overwrite the previous content of the association variable "User::$channels" ?
          Hide
          Thomas G. added a comment - - edited

          I have reproduced the bug in a fully working example. See attachment. Just read instructions in comment and execute each step seperately.

          But yes, I overwrite the previous content of the association variable "User::$channels" with a Doctrine ArrayCollection.

          User Entity
          /** @Entity @Table(name="users") */
          class User
          {
              /**
               * @Id @Column(type="integer")
               * @GeneratedValue(strategy="AUTO")
               */
              private $id;
              /** @Column(type="string", length=50) */
              private $name;
          
              /**
               * @ManyToMany(targetEntity="Channel")
               * @JoinTable(name="users_channels",
               *          joinColumns={@JoinColumn(name="userId",
                          referencedColumnName="id")},
               *          inverseJoinColumns={@JoinColumn(name="channelId",
               *          referencedColumnName="id")}
               *      )
               * @var ArrayCollection
               */
              protected $channels;
          
              public function __construct(){
                  $this->channels = new \Doctrine\Common\Collections\ArrayCollection();
              }
          
              public function getId()
              {
                  return $this->id;
              }
          
              public function getName()
              {
                  return $this->name;
              }
          
              public function setName($name)
              {
                  $this->name = $name;
              }
          
              public function getChannels() {
                  return $this->channels;
              }
          
              public function setChannels($channels) {
                  $channels =  new \Doctrine\Common\Collections\ArrayCollection($channels);
                 
                  $this->channels = $channels;
              }
          }
          
          Show
          Thomas G. added a comment - - edited I have reproduced the bug in a fully working example. See attachment. Just read instructions in comment and execute each step seperately. But yes, I overwrite the previous content of the association variable "User::$channels" with a Doctrine ArrayCollection. User Entity /** @Entity @Table(name= "users" ) */ class User { /** * @Id @Column(type= "integer" ) * @GeneratedValue(strategy= "AUTO" ) */ private $id; /** @Column(type= "string" , length=50) */ private $name; /** * @ManyToMany(targetEntity= "Channel" ) * @JoinTable(name= "users_channels" , * joinColumns={@JoinColumn(name= "userId" , referencedColumnName= "id" )}, * inverseJoinColumns={@JoinColumn(name= "channelId" , * referencedColumnName= "id" )} * ) * @ var ArrayCollection */ protected $channels; public function __construct(){ $ this ->channels = new \Doctrine\Common\Collections\ArrayCollection(); } public function getId() { return $ this ->id; } public function getName() { return $ this ->name; } public function setName($name) { $ this ->name = $name; } public function getChannels() { return $ this ->channels; } public function setChannels($channels) { $channels = new \Doctrine\Common\Collections\ArrayCollection($channels); $ this ->channels = $channels; } }
          Hide
          Benjamin Eberlei added a comment -

          Fixed.

          Show
          Benjamin Eberlei added a comment - Fixed.

            People

            • Assignee:
              Benjamin Eberlei
              Reporter:
              Thomas G.
            • Votes:
              2 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: