Doctrine 1
  1. Doctrine 1
  2. DC-228

Doctrine_Record::fromArray() may fail when input contains related component IDs.

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 1.2.0-BETA1, 1.2.0-BETA2
    • Fix Version/s: 1.2.0-RC1
    • Component/s: Record
    • Labels:
      None

      Description

      I try to explain through an example.

      Data model:
      ===========

      User — UserGroup — Group // many-to-many relation

      Example code:
      =============

      $user = new User;
      $user->name = 'user1';
      $user->Group[0]->name = 'group1'; // id => 1
      $user->Group[1]->name = 'group2'; // id => 2
      $user->save();
      
      $group3 = new Group;
      $group3->name = 'group3';
      $group3->save(); // id => 3
      
      // edit user in an HTML form, and select all groups with checkboxes
      // $_POST = array('name' => 'user1', 'Group' => array(1, 2, 3))
      $user->fromArray($_POST);
      $user->save(); // fail, unique constraint violation
      
      

      Reason:
      =======

      Doctrine_Record::fromArray() calls unlink() in line 1948:
      
      $this->unlink($key, array(), false);
      
      unlink() fills _pendingUnlinks variable with User's Group relation IDs in line 2418:
      
      if ( ! $ids) {
          $ids = $allIds;
      }
      foreach ($ids as $id) {
          $this->_pendingUnlinks[$alias][$id] = true;
      }
      

      So _pendingUnlinks[$alias] will be an array.

      Then fromArray() calls link() in line 1949:

      foreach ($value as $id) {
          $this->link($key, $id, false);
      }
      

      link() remove new IDs (1, 2 and 3) from _pendingUnlinks[$alias] in line 2496:

      foreach ($ids as $id) {
          if (isset($this->_pendingUnlinks[$alias][$id])) {
              unset($this->_pendingUnlinks[$alias][$id]);
          }
      }
      

      So _pendingUnlinks[$alias] will be an empty array.

      When we save user, this code is executed in Doctrine_Connection_UnitOfWork::saveGraph() (in line 103):

      foreach ($record->getPendingUnlinks() as $alias => $ids) {
          if ($ids === false) {
              $record->unlinkInDb($alias, array());
          } else if ($ids) {
              $record->unlinkInDb($alias, array_keys($ids));
          }
      }
      

      BUT

      Because _pendingUnlinks[$alias] is an empty array, neither IF branch will be executed, so relations won't be deleted.

      And then Doctrine try to insert new relations, but it will fail because it will violate existed primary keys in UserGroup.

      In an earlier version of Doctrine-1.2, the unlink() method set _pendingUnlinks[$alias] to false if $ids attribute was an empty array, so in saveGraph() all relations were deleted before new relations inserted.

      /Sorry for my poor English/

        Activity

        There are no comments yet on this issue.

          People

          • Assignee:
            Jonathan H. Wage
            Reporter:
            Zoltan Kovago
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: