From acaf08d4b751f1fc9772de5c0e0c5d8371722f17 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 5 Jun 2011 09:59:16 +0200 Subject: [PATCH] DDC-1193 - Fix bug with cascade remove and proxy classes. --- .../ORM/Persisters/BasicEntityPersister.php | 4 +- lib/Doctrine/ORM/UnitOfWork.php | 20 +++- .../ORM/Functional/Ticket/DDC1193Test.php | 93 +++++++++++++++++++ 3 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1193Test.php diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 5ce7080c2..a14528ecd 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -563,7 +563,7 @@ class BasicEntityPersister * @todo Check identity map? loadById method? Try to guess whether $criteria is the id? */ public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0) - { + { $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode); list($params, $types) = $this->expandParameters($criteria); $stmt = $this->_conn->executeQuery($sql, $params, $types); @@ -577,7 +577,7 @@ class BasicEntityPersister } else { $hydrator = $this->_em->newHydrator(Query::HYDRATE_SIMPLEOBJECT); } - $entities = $hydrator->hydrateAll($stmt, $this->_rsm, $hints); + $entities = $hydrator->hydrateAll($stmt, $this->_rsm, $hints); return $entities ? $entities[0] : null; } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index aa5309a7c..99152a652 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1290,6 +1290,10 @@ class UnitOfWork implements PropertyChangedListener } $visited[$oid] = $entity; // mark visited + + // Cascade first, because scheduleForDelete() removes the entity from the identity map, which + // can cause problems when a lazy proxy has to be initialized for the cascade operation. + $this->cascadeRemove($entity, $visited); $class = $this->em->getClassMetadata(get_class($entity)); $entityState = $this->getEntityState($entity); @@ -1313,7 +1317,6 @@ class UnitOfWork implements PropertyChangedListener throw new UnexpectedValueException("Unexpected entity state: $entityState."); } - $this->cascadeRemove($entity, $visited); } /** @@ -1674,6 +1677,11 @@ class UnitOfWork implements PropertyChangedListener if ( ! $assoc['isCascadePersist']) { continue; } + + if ($entity instanceof Proxy && !$entity->__isInitialized__) { + $entity->__load(); + } + $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); if (($relatedEntities instanceof Collection || is_array($relatedEntities))) { if ($relatedEntities instanceof PersistentCollection) { @@ -1702,7 +1710,11 @@ class UnitOfWork implements PropertyChangedListener if ( ! $assoc['isCascadeRemove']) { continue; } - //TODO: If $entity instanceof Proxy => Initialize ? + + if ($entity instanceof Proxy) { + $entity->__load(); + } + $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); if ($relatedEntities instanceof Collection || is_array($relatedEntities)) { // If its a PersistentCollection initialization is intended! No unwrap! @@ -1865,8 +1877,8 @@ class UnitOfWork implements PropertyChangedListener } $id = array($class->identifier[0] => $idHash); } - - if (isset($this->identityMap[$class->rootEntityName][$idHash])) { + + if (isset($this->identityMap[$class->rootEntityName][$idHash])) { $entity = $this->identityMap[$class->rootEntityName][$idHash]; $oid = spl_object_hash($entity); if ($entity instanceof Proxy && ! $entity->__isInitialized__) { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1193Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1193Test.php new file mode 100644 index 000000000..3ddf37e70 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1193Test.php @@ -0,0 +1,93 @@ +_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); + $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1193Company'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1193Person'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1193Account') + )); + } + + /** + * @group DDC-1193 + */ + public function testIssue() + { + $company = new DDC1193Company(); + $person = new DDC1193Person(); + $account = new DDC1193Account(); + + $person->account = $account; + $person->company = $company; + + $company->member = $person; + + $this->_em->persist($company); + + $this->_em->flush(); + + $companyId = $company->id; + $accountId = $account->id; + $this->_em->clear(); + + $company = $this->_em->find(get_class($company), $companyId); + + $this->assertTrue($this->_em->getUnitOfWork()->isInIdentityMap($company), "Company is in identity map."); + $this->assertFalse($company->member->__isInitialized__, "Pre-Condition"); + $this->assertTrue($this->_em->getUnitOfWork()->isInIdentityMap($company->member), "Member is in identity map."); + + $this->_em->remove($company); + $this->_em->flush(); + + $this->assertEquals(count($this->_em->getRepository(get_class($account))->findAll()), 0); + } +} + +/** @Entity */ +class DDC1193Company { + /** + * @Id @Column(type="integer") + * @GeneratedValue + */ + public $id; + + /** @OneToOne(targetEntity="DDC1193Person", cascade={"persist", "remove"}) */ + public $member; + +} + +/** @Entity */ +class DDC1193Person { + /** + * @Id @Column(type="integer") + * @GeneratedValue + */ + public $id; + + /** + * @OneToOne(targetEntity="DDC1193Account", cascade={"persist", "remove"}) + */ + public $account; +} + +/** @Entity */ +class DDC1193Account { + /** + * @Id @Column(type="integer") + * @GeneratedValue + */ + public $id; + +} + +