diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index e91786cd8..e8bb8ce47 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -264,7 +264,7 @@ class UnitOfWork implements PropertyChangedListener $this->_orphanRemovals)) { return; // Nothing to do. } - + if ($this->_orphanRemovals) { foreach ($this->_orphanRemovals as $orphan) { $this->remove($orphan); @@ -371,70 +371,7 @@ class UnitOfWork implements PropertyChangedListener } /** - * Method can be used to compute the change-set of any entity. - * - * @Internal - * - * @Todo inline _computeEntityChanges to here? - * - * @param ClassMetadata $class - * @param object $entity - * @return void - */ - public function computeChangeSet($class, $entity) - { - $this->_computeEntityChanges($class, $entity); - // Look for changes in associations of the entity - foreach ($class->associationMappings as $assoc) { - $val = $class->reflFields[$assoc->sourceFieldName]->getValue($entity); - if ($val !== null) { - $this->_computeAssociationChanges($assoc, $val); - } - } - } - - /** - * Computes all the changes that have been done to entities and collections - * since the last commit and stores these changes in the _entityChangeSet map - * temporarily for access by the persisters, until the UoW commit is finished. - */ - public function computeChangeSets() - { - // Compute changes for INSERTed entities first. This must always happen. - foreach ($this->_entityInsertions as $entity) { - $class = $this->_em->getClassMetadata(get_class($entity)); - $this->computeChangeSet($class, $entity); - } - - // Compute changes for other MANAGED entities. Change tracking policies take effect here. - foreach ($this->_identityMap as $className => $entities) { - $class = $this->_em->getClassMetadata($className); - - // Skip class if change tracking happens through notification - if ($class->isChangeTrackingNotify() /* || $class->isReadOnly*/) { - continue; - } - - // If change tracking is explicit, then only compute changes on explicitly saved entities - $entitiesToProcess = $class->isChangeTrackingDeferredExplicit() ? - $this->_scheduledForDirtyCheck[$className] : $entities; - - foreach ($entitiesToProcess as $entity) { - // Ignore uninitialized proxy objects - if (/* $entity is readOnly || */ $entity instanceof Proxy && ! $entity->__isInitialized__) { - continue; - } - // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. - $oid = spl_object_hash($entity); - if ( ! isset($this->_entityInsertions[$oid]) && isset($this->_entityStates[$oid])) { - $this->computeChangeSet($class, $entity); - } - } - } - } - - /** - * Computes the changes done to a single entity. + * Computes the changes that happened to a single entity. * * Modifies/populates the following properties: * @@ -461,13 +398,13 @@ class UnitOfWork implements PropertyChangedListener * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to compute the changes. */ - private function _computeEntityChanges($class, $entity) + public function computeChangeSet(Mapping\ClassMetadata $class, $entity) { - $oid = spl_object_hash($entity); - if ( ! $class->isInheritanceTypeNone()) { $class = $this->_em->getClassMetadata(get_class($entity)); } + + $oid = spl_object_hash($entity); $actualData = array(); foreach ($class->reflFields as $name => $refProp) { @@ -550,6 +487,54 @@ class UnitOfWork implements PropertyChangedListener } } } + + // Look for changes in associations of the entity + foreach ($class->associationMappings as $assoc) { + $val = $class->reflFields[$assoc->sourceFieldName]->getValue($entity); + if ($val !== null) { + $this->_computeAssociationChanges($assoc, $val); + } + } + } + + /** + * Computes all the changes that have been done to entities and collections + * since the last commit and stores these changes in the _entityChangeSet map + * temporarily for access by the persisters, until the UoW commit is finished. + */ + public function computeChangeSets() + { + // Compute changes for INSERTed entities first. This must always happen. + foreach ($this->_entityInsertions as $entity) { + $class = $this->_em->getClassMetadata(get_class($entity)); + $this->computeChangeSet($class, $entity); + } + + // Compute changes for other MANAGED entities. Change tracking policies take effect here. + foreach ($this->_identityMap as $className => $entities) { + $class = $this->_em->getClassMetadata($className); + + // Skip class if change tracking happens through notification + if ($class->isChangeTrackingNotify() /* || $class->isReadOnly*/) { + continue; + } + + // If change tracking is explicit, then only compute changes on explicitly saved entities + $entitiesToProcess = $class->isChangeTrackingDeferredExplicit() ? + $this->_scheduledForDirtyCheck[$className] : $entities; + + foreach ($entitiesToProcess as $entity) { + // Ignore uninitialized proxy objects + if (/* $entity is readOnly || */ $entity instanceof Proxy && ! $entity->__isInitialized__) { + continue; + } + // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. + $oid = spl_object_hash($entity); + if ( ! isset($this->_entityInsertions[$oid]) && isset($this->_entityStates[$oid])) { + $this->computeChangeSet($class, $entity); + } + } + } } /** diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC422Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC422Test.php new file mode 100644 index 000000000..e21dbf92f --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC422Test.php @@ -0,0 +1,75 @@ +_em->getConnection()->getConfiguration()->setSqlLogger(new \Doctrine\DBAL\Logging\EchoSqlLogger); + $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC422Guest'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC422Customer'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC422Contact') + )); + } + + /** + * @group DDC-422 + */ + public function testIssue() + { + $customer = new DDC422Customer; + $this->_em->persist($customer); + $this->_em->flush(); + $this->_em->clear(); + + $customer = $this->_em->find(get_class($customer), $customer->id); + + $this->assertTrue($customer->contacts instanceof \Doctrine\ORM\PersistentCollection); + $this->assertFalse($customer->contacts->isInitialized()); + $contact = new DDC422Contact; + $customer->contacts->add($contact); + $this->assertTrue($customer->contacts->isDirty()); + $this->assertFalse($customer->contacts->isInitialized()); + $this->_em->flush(); + + $this->assertEquals(1, $this->_em->getConnection()->fetchColumn("select count(*) from ddc422_customers_contacts")); + } +} + +/** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"guest" = "DDC422Guest", "customer" = "DDC422Customer"}) + */ +class DDC422Guest { + /** @Id @Column(type="integer") @GeneratedValue */ + public $id; +} + +/** @Entity */ +class DDC422Customer extends DDC422Guest { + /** + * @ManyToMany(targetEntity="DDC422Contact", cascade={"persist","remove"}) + * @JoinTable(name="ddc422_customers_contacts", + * joinColumns={@JoinColumn(name="customer_id", referencedColumnName="id", onDelete="cascade" )}, + * inverseJoinColumns={@JoinColumn(name="contact_id", referencedColumnName="id", onDelete="cascade" )} + * ) + */ + public $contacts; + + public function __construct() { + $this->contacts = new \Doctrine\Common\Collections\ArrayCollection; + } +} + +/** @Entity */ +class DDC422Contact { + /** @Id @Column(type="integer") @GeneratedValue */ + public $id; +} +