diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index e324b79b1..e210f18d4 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -44,21 +44,10 @@ class Configuration extends \Doctrine\DBAL\Configuration 'resultCacheImpl' => null, 'queryCacheImpl' => null, 'metadataCacheImpl' => null, - 'metadataDriverImpl' => new AnnotationDriver(), - 'automaticDirtyChecking' => true + 'metadataDriverImpl' => new AnnotationDriver() )); } - public function setAutomaticDirtyChecking($bool) - { - $this->_attributes['automaticDirtyChecking'] = $bool; - } - - public function getAutomaticDirtyChecking() - { - return $this->_attributes['automaticDirtyChecking']; - } - public function setMetadataDriverImpl($driverImpl) { $this->_attributes['metadataDriverImpl'] = $driverImpl; diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index 3eb6b0b4c..6bf105327 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -383,7 +383,7 @@ final class ClassMetadata * * @var integer */ - //private $_changeTrackingPolicy; + private $_changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT; /** * Initializes a new ClassMetadata instance that will hold the object-relational mapping @@ -420,6 +420,51 @@ final class ClassMetadata return $this->_reflectionProperties; } + /** + * + * @return integer + */ + public function getChangeTrackingPolicy() + { + return $this->_changeTrackingPolicy; + } + + /** + * + * @param integer $policy + */ + public function setChangeTrackingPolicy($policy) + { + $this->_changeTrackingPolicy = $policy; + } + + /** + * + * @return boolean + */ + public function isChangeTrackingDeferredExplicit() + { + return $this->_changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_EXPLICIT; + } + + /** + * + * @return boolean + */ + public function isChangeTrackingPolicyDeferredImplicit() + { + return $this->_changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_IMPLICIT; + } + + /** + * + * @return boolean + */ + public function isChangeTrackingNotify() + { + return $this->_changeTrackingPolicy == self::CHANGETRACKING_NOTIFY; + } + /** * INTERNAL: * Adds a reflection property. Usually only used by the ClassMetadataFactory diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index f4c44c4d3..41cbe000c 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -309,27 +309,27 @@ class UnitOfWork implements PropertyChangedListener foreach ($entities as $entity) { $entitySet[get_class($entity)][] = $entity; } - } else if ( ! $this->_em->getConfiguration()->getAutomaticDirtyChecking()) { - $entitySet = $this->_scheduledForDirtyCheck; } else { $entitySet = $this->_identityMap; } foreach ($entitySet as $className => $entities) { $class = $this->_em->getClassMetadata($className); - if ( ! $class->isInheritanceTypeNone() && count($entities) > 0) { - $class = $this->_em->getClassMetadata(get_class($entities[key($entities)])); - } - /* + // Skip class if change tracking happens through notification if ($class->isChangeTrackingNotify()) { continue; } + + // If change tracking is explicit, then only compute changes on explicitly saved entities $entitiesToProcess = $class->isChangeTrackingDeferredExplicit() ? $this->_scheduledForDirtyCheck[$className] : $entities; - */ - foreach ($entities as $entity) { + if ( ! $class->isInheritanceTypeNone() && count($entitiesToProcess) > 0) { + $class = $this->_em->getClassMetadata(get_class($entitiesToProcess[key($entitiesToProcess)])); + } + + foreach ($entitiesToProcess as $entity) { $oid = spl_object_hash($entity); $state = $this->getEntityState($entity); @@ -970,11 +970,8 @@ class UnitOfWork implements PropertyChangedListener $class = $this->_em->getClassMetadata(get_class($entity)); switch ($this->getEntityState($entity)) { case self::STATE_MANAGED: - // nothing to do, except if automatic dirty checking is disabled - /*if ($class->isChangeTrackingDeferredExplicit()) { - $this->scheduleForDirtyCheck($entity); - }*/ - if ( ! $this->_em->getConfiguration()->getAutomaticDirtyChecking()) { + // nothing to do, except if policy is "deferred explicit" + if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); } break; @@ -1399,7 +1396,7 @@ class UnitOfWork implements PropertyChangedListener $oid = spl_object_hash($entity); $class = $this->_em->getClassMetadata(get_class($entity)); - $this->_entityChangeSets[$oid][$propertyName] = array($oldValue => $newValue); + $this->_entityChangeSets[$oid][$propertyName] = array($oldValue, $newValue); if ($class->hasAssociation($propertyName)) { $assoc = $class->getAssociationMapping($name); diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index a2d455c0b..ce9b09d37 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -7,7 +7,6 @@ use Doctrine\Tests\Mocks\EntityManagerMock; use Doctrine\Tests\Mocks\UnitOfWorkMock; use Doctrine\Tests\Mocks\EntityPersisterMock; use Doctrine\Tests\Mocks\IdentityIdGeneratorMock; - use Doctrine\Tests\Models\Forum\ForumUser; use Doctrine\Tests\Models\Forum\ForumAvatar; @@ -124,54 +123,21 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals(0, count($avatarPersister->getDeletes())); } - /*public function testComputeEntityChangeSets() + public function testChangeTrackingNotify() { - // We need an ID generator for ForumAvatar, because we attach a NEW ForumAvatar - // to a (faked) MANAGED instance. During changeset computation this will result - // in the UnitOfWork requesting the Id generator of ForumAvatar. - $avatarIdGeneratorMock = new IdentityIdGeneratorMock($this->_emMock); - $this->_emMock->setIdGenerator('Doctrine\Tests\Models\Forum\ForumAvatar', $avatarIdGeneratorMock); + $entity = new NotifyChangedEntity; + $entity->setData('thedata'); + $this->_unitOfWork->save($entity); - $user1 = new ForumUser(); - $user1->id = 1; - $user1->username = "romanb"; - $user1->avatar = new ForumAvatar(); - // Fake managed state - $this->_unitOfWork->setEntityState($user1, \Doctrine\ORM\UnitOfWork::STATE_MANAGED); - - $user2 = new ForumUser(); - $user2->id = 2; - $user2->username = "jwage"; - // Fake managed state - $this->_unitOfWork->setEntityState($user2, \Doctrine\ORM\UnitOfWork::STATE_MANAGED); + $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity)); - // Fake original entity date - $this->_unitOfWork->setOriginalEntityData($user1, array( - 'id' => 1, 'username' => 'roman' - )); - $this->_unitOfWork->setOriginalEntityData($user2, array( - 'id' => 2, 'username' => 'jon' - )); + $entity->setData('newdata'); - // Go - $this->_unitOfWork->computeChangeSets(array($user1, $user2)); + $this->assertTrue($this->_unitOfWork->isRegisteredDirty($entity)); - // Verify - $user1ChangeSet = $this->_unitOfWork->getEntityChangeSet($user1); - $this->assertTrue(is_array($user1ChangeSet)); - $this->assertEquals(2, count($user1ChangeSet)); - $this->assertTrue(isset($user1ChangeSet['username'])); - $this->assertEquals(array('roman' => 'romanb'), $user1ChangeSet['username']); - $this->assertTrue(isset($user1ChangeSet['avatar'])); - $this->assertSame(array(null => $user1->avatar), $user1ChangeSet['avatar']); - - $user2ChangeSet = $this->_unitOfWork->getEntityChangeSet($user2); - $this->assertTrue(is_array($user2ChangeSet)); - $this->assertEquals(1, count($user2ChangeSet)); - $this->assertTrue(isset($user2ChangeSet['username'])); - $this->assertEquals(array('jon' => 'jwage'), $user2ChangeSet['username']); + $this->assertEquals(array('data' => array('thedata', 'newdata')), $this->_unitOfWork->getEntityChangeSet($entity)); } - */ + /* public function testSavingSingleEntityWithSequenceIdGeneratorSchedulesInsert() { @@ -213,4 +179,50 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase //... } */ +} + +/** + * @DoctrineEntity + */ +class NotifyChangedEntity implements \Doctrine\Common\NotifyPropertyChanged +{ + private $_listeners = array(); + /** + * @DoctrineId + * @DoctrineColumn(type="integer") + * @DoctrineGeneratedValue(strategy="auto") + */ + private $id; + /** + * @DoctrineColumn(type="varchar") + */ + private $data; + + public function getId() { + return $this->id; + } + + public function getData() { + return $this->data; + } + + public function setData($data) { + if ($data != $this->data) { + $this->_onPropertyChanged('data', $this->data, $data); + $this->data = $data; + } + } + + public function addPropertyChangedListener(\Doctrine\Common\PropertyChangedListener $listener) + { + $this->_listeners[] = $listener; + } + + protected function _onPropertyChanged($propName, $oldValue, $newValue) { + if ($this->_listeners) { + foreach ($this->_listeners as $listener) { + $listener->propertyChanged($this, $propName, $oldValue, $newValue); + } + } + } } \ No newline at end of file