From b8090c99a3bbc1b75f0cfe83fc129de206bceb49 Mon Sep 17 00:00:00 2001 From: romanb Date: Sat, 18 Jul 2009 11:41:37 +0000 Subject: [PATCH] [2.0] Moved locking tests into Functional namespace. Fixed several missing flush() calls in some functional association tests. Reordered DELETE statements for ecommerce model tests in OrmFunctionalTestCase in order to maintain referential integrity. Fixed issue with bi-directional self-referencing one-one associations. Some other small improvements and cosmetics. Small hydration performance improvement through inlining method call in UnitOfWork::createEntity(). --- lib/Doctrine/ORM/Event/LifecycleEventArgs.php | 24 +++ lib/Doctrine/ORM/Events.php | 2 +- .../ORM/Internal/Hydration/ObjectHydrator.php | 4 +- lib/Doctrine/ORM/Mapping/ClassMetadata.php | 117 ++++++-------- .../ORM/Mapping/ClassMetadataFactory.php | 13 +- .../ORM/Mapping/Driver/AnnotationDriver.php | 4 +- .../Mapping/Driver/DoctrineAnnotations.php | 13 +- lib/Doctrine/ORM/Mapping/OneToOneMapping.php | 6 +- .../Persisters/StandardEntityPersister.php | 76 ++------- lib/Doctrine/ORM/UnitOfWork.php | 148 +++++++++++++++--- .../Tests/Models/Company/CompanyPerson.php | 2 +- .../Models/ECommerce/ECommerceCustomer.php | 1 + tests/Doctrine/Tests/ORM/AllTests.php | 10 -- .../Tests/ORM/Functional/AllTests.php | 2 + .../ORM/Functional/BasicFunctionalTest.php | 1 + .../Functional/ClassTableInheritanceTest.php | 2 +- .../Tests/ORM/Functional/Locking/AllTests.php | 30 ++++ .../Locking/OptimisticTest.php | 20 ++- .../OneToManyBidirectionalAssociationTest.php | 3 + .../OneToOneBidirectionalAssociationTest.php | 2 + ...OneToOneSelfReferentialAssociationTest.php | 3 +- .../OneToOneUnidirectionalAssociationTest.php | 1 + tests/Doctrine/Tests/ORM/Locking/AllTests.php | 30 ---- .../Doctrine/Tests/OrmFunctionalTestCase.php | 12 +- 24 files changed, 296 insertions(+), 230 deletions(-) create mode 100644 lib/Doctrine/ORM/Event/LifecycleEventArgs.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/Locking/AllTests.php rename tests/Doctrine/Tests/ORM/{ => Functional}/Locking/OptimisticTest.php (90%) delete mode 100644 tests/Doctrine/Tests/ORM/Locking/AllTests.php diff --git a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php new file mode 100644 index 000000000..37f5d0645 --- /dev/null +++ b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php @@ -0,0 +1,24 @@ +_entity = $entity; + } + + public function getEntity() + { + return $this->_entity; + } + + public function getEntityManager() + { + return $this->_em; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Events.php b/lib/Doctrine/ORM/Events.php index 0f2a732e4..7d0ca462d 100644 --- a/lib/Doctrine/ORM/Events.php +++ b/lib/Doctrine/ORM/Events.php @@ -39,6 +39,6 @@ final class Events const postInsert = 'postInsert'; const preUpdate = 'preUpdate'; const postUpdate = 'postUpdate'; - const load = 'load'; + const postLoad = 'postLoad'; const loadClassMetadata = 'loadClassMetadata'; } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index c781ad57d..3f304ad4a 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -283,8 +283,8 @@ class ObjectHydrator extends AbstractHydrator if (isset($targetClass->inverseMappings[$property])) { $sourceProp = $targetClass->inverseMappings[$property]->sourceFieldName; $targetClass->reflFields[$sourceProp]->setValue($entity2, $entity1); - } else if ($class === $targetClass) { - // Special case: self-referencing one-one on the same class + } else if ($class === $targetClass && $relation->mappedByFieldName) { + // Special case: bi-directional self-referencing one-one on the same class $targetClass->reflFields[$property]->setValue($entity2, $entity1); } } else { diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index c95045e05..7902397ce 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -293,14 +293,6 @@ final class ClassMetadata * @var array */ public $primaryTable; - - /** - * The cached lifecycle listeners. There is only one instance of each - * listener class at any time. - * - * @var array - */ - public $lifecycleListenerInstances = array(); /** * The registered lifecycle callbacks for entities of this class. @@ -309,13 +301,6 @@ final class ClassMetadata */ public $lifecycleCallbacks = array(); - /** - * The registered lifecycle listeners for entities of this class. - * - * @var array - */ - public $lifecycleListeners = array(); - /** * The association mappings. All mappings, inverse and owning side. * @@ -398,14 +383,14 @@ final class ClassMetadata public $inheritedAssociationFields = array(); /** - * A flag for whether or not the model is to be versioned with optimistic locking + * A flag for whether or not instances of this class are to be versioned with optimistic locking. * * @var boolean $isVersioned */ public $isVersioned; /** - * The name of the field which stores the version information + * The name of the field which is used for versioning in optimistic locking (if any). * * @var mixed $versionField */ @@ -415,7 +400,7 @@ final class ClassMetadata * Initializes a new ClassMetadata instance that will hold the object-relational mapping * metadata of the class with the given name. * - * @param string $entityName Name of the entity class the new instance is used for. + * @param string $entityName The name of the entity class the new instance is used for. */ public function __construct($entityName) { @@ -1531,15 +1516,20 @@ final class ClassMetadata */ public function invokeLifecycleCallbacks($lifecycleEvent, $entity) { - foreach ($this->getLifecycleCallbacks($lifecycleEvent) as $callback) { + foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) { $entity->$callback(); } - foreach ($this->getLifecycleListeners($lifecycleEvent) as $className => $callback) { - if ( ! isset($this->lifecycleListenerInstances[$className])) { - $this->lifecycleListenerInstances[$className] = new $className; - } - $this->lifecycleListenerInstances[$className]->$callback($entity); - } + } + + /** + * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event. + * + * @param string $lifecycleEvent + * @return boolean + */ + public function hasLifecycleCallbacks($lifecycleEvent) + { + return isset($this->lifecycleCallbacks[$lifecycleEvent]); } /** @@ -1550,37 +1540,7 @@ final class ClassMetadata */ public function getLifecycleCallbacks($event) { - return isset($this->lifecycleCallbacks[$event]) ? - $this->lifecycleCallbacks[$event] : array(); - } - - /** - * Gets the registered lifecycle listeners for an event. - * - * @param string $event - * @return array - */ - public function getLifecycleListeners($event) - { - return isset($this->lifecycleListeners[$event]) ? - $this->lifecycleListeners[$event] : array(); - } - - /** - * Adds a lifecycle listener for entities of this class. - * - * Note: If the same listener class is registered more than once, the old - * one will be overridden. - * - * @param string $listenerClass - * @param array $callbacks - */ - public function addLifecycleListener($listenerClass, array $callbacks) - { - $this->lifecycleListeners[$event][$listenerClass] = array(); - foreach ($callbacks as $method => $event) { - $this->lifecycleListeners[$event][$listenerClass][] = $method; - } + return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : array(); } /** @@ -1594,12 +1554,7 @@ final class ClassMetadata */ public function addLifecycleCallback($callback, $event) { - if ( ! isset($this->lifecycleCallbacks[$event])) { - $this->lifecycleCallbacks[$event] = array(); - } - if ( ! in_array($callback, $this->lifecycleCallbacks[$event])) { - $this->lifecycleCallbacks[$event][$callback] = $callback; - } + $this->lifecycleCallbacks[$event][] = $callback; } /** @@ -1771,20 +1726,44 @@ final class ClassMetadata { $this->sequenceGeneratorDefinition = $definition; } - - public function isVersioned($bool = null) + + /** + * Checks whether this class is versioned for optimistic locking. + * + * @return boolean TRUE if this class is versioned for optimistic locking, FALSE otherwise. + */ + public function isVersioned() { - if ( ! is_null($bool)) { - $this->isVersioned = $bool; - } return $this->isVersioned; } - + + /** + * Sets whether this class is to be versioned for optimistic locking. + * + * @param boolean $bool + */ + public function setVersioned($bool) + { + $this->isVersioned = $bool; + } + + /** + * Gets the name of the field that is used for versioning if this class is versioned + * for optimistic locking. + * + * @return string + */ public function getVersionField() { return $this->versionField; } - + + /** + * Sets the name of the field that is to be used for versioning if this class is + * versioned for optimistic locking. + * + * @param string $versionField + */ public function setVersionField($versionField) { $this->versionField = $versionField; diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index ac3ea681c..82b29c623 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -26,7 +26,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\ORM\Events; /** - * The metadata factory is used to create ClassMetadata objects that contain all the + * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the * metadata mapping informations of a class which describes how a class should be mapped * to a relational database. * @@ -35,7 +35,7 @@ use Doctrine\ORM\Events; * @version $Revision$ * @link www.doctrine-project.org * @since 2.0 - * @todo Support for mapped superclasses (@DoctrineMappedSuperclass) + * @todo Support for mapped superclasses (@MappedSuperclass) */ class ClassMetadataFactory { @@ -114,7 +114,8 @@ class ClassMetadataFactory /** * Sets the metadata descriptor for a specific class. - * This is only useful in very special cases, like when generating proxy classes. + * + * NOTE: This is only useful in very special cases, like when generating proxy classes. * * @param string $className * @param ClassMetadata $class @@ -162,7 +163,7 @@ class ClassMetadataFactory $this->_addInheritedFields($class, $parent); $this->_addInheritedRelations($class, $parent); $class->setIdentifier($parent->identifier); - $class->isVersioned($parent->isVersioned); + $class->setVersioned($parent->isVersioned); $class->setVersionField($parent->versionField); } @@ -261,8 +262,8 @@ class ClassMetadataFactory */ private function _generateStaticSql($class) { - if ($versioned = $class->isVersioned()) { - $versionField = $class->getVersionField(); + if ($versioned = $class->isVersioned) { + $versionField = $class->versionField; } // Generate INSERT SQL diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index bed4aff56..474b237a5 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -157,7 +157,7 @@ class AnnotationDriver implements Driver $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAnnot->strategy)); } if ($versionAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Version')) { - $metadata->isVersioned(true); + $metadata->setVersioned(true); $metadata->setVersionField($mapping['fieldName']); if ( ! isset($mapping['default'])) { @@ -177,7 +177,7 @@ class AnnotationDriver implements Driver 'initialValue' => $seqGeneratorAnnot->initialValue )); } else if ($tblGeneratorAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\TableGenerator')) { - throw new DoctrineException("DoctrineTableGenerator not yet implemented."); + throw DoctrineException::tableIdGeneratorNotImplemented(); } } else if ($oneToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToOne')) { $mapping['targetEntity'] = $oneToOneAnnot->targetEntity; diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index 288a06ff4..8dd747631 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -101,4 +101,15 @@ final class SequenceGenerator extends \Doctrine\Common\Annotations\Annotation { public $initialValue = 1; } final class ChangeTrackingPolicy extends \Doctrine\Common\Annotations\Annotation {} -final class DoctrineX extends \Doctrine\Common\Annotations\Annotation {} \ No newline at end of file + +/* Annotations for lifecycle callbacks */ +final class PreSave extends \Doctrine\Common\Annotations\Annotation {} +final class PostSave extends \Doctrine\Common\Annotations\Annotation {} +final class PreUpdate extends \Doctrine\Common\Annotations\Annotation {} +final class PostUpdate extends \Doctrine\Common\Annotations\Annotation {} +final class PreDelete extends \Doctrine\Common\Annotations\Annotation {} +final class PostDelete extends \Doctrine\Common\Annotations\Annotation {} +final class PostLoad extends \Doctrine\Common\Annotations\Annotation {} + +/* Generic annotation for Doctrine extensions */ +final class DoctrineX extends \Doctrine\Common\Annotations\Annotation {} diff --git a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php index 3bafa158f..35773888a 100644 --- a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php @@ -87,7 +87,11 @@ class OneToOneMapping extends AssociationMapping { parent::_validateAndCompleteMapping($mapping); - if ($this->isOwningSide()) { + if (isset($mapping['joinColumns']) && $mapping['joinColumns']) { + $this->isOwningSide = true; + } + + if ($this->isOwningSide) { if ( ! isset($mapping['joinColumns'])) { throw MappingException::invalidMapping($this->sourceFieldName); } diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index 8a2376024..974100689 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -132,20 +132,13 @@ class StandardEntityPersister $primaryTableName = $this->_class->primaryTable['name']; $sqlLogger = $this->_conn->getConfiguration()->getSqlLogger(); - $hasPreInsertListeners = $this->_evm->hasListeners(Events::preInsert); - $hasPostInsertListeners = $this->_evm->hasListeners(Events::postInsert); foreach ($this->_queuedInserts as $entity) { $insertData = array(); $this->_prepareData($entity, $insertData, true); - if ($hasPreInsertListeners) { - $this->_preInsert($entity); - } - $paramIndex = 1; if ($sqlLogger) { - //TODO: Log type $params = array(); foreach ($insertData[$primaryTableName] as $value) { $params[$paramIndex] = $value; @@ -170,10 +163,6 @@ class StandardEntityPersister if ($isVersioned) { $this->_assignDefaultVersionValue($this->_class, $entity, $id); } - - if ($hasPostInsertListeners) { - $this->_postInsert($entity); - } } $stmt->closeCursor(); @@ -191,7 +180,7 @@ class StandardEntityPersister */ protected function _assignDefaultVersionValue($class, $entity, $id) { - $versionField = $this->_class->getVersionField(); + $versionField = $this->_class->versionField; $identifier = $this->_class->getIdentifierColumnNames(); $versionFieldColumnName = $this->_class->getColumnName($versionField); @@ -246,8 +235,8 @@ class StandardEntityPersister $set[] = $this->_conn->quoteIdentifier($columnName) . ' = ?'; } - if ($isVersioned = $this->_class->isVersioned()) { - $versionField = $this->_class->getVersionField(); + if ($isVersioned = $this->_class->isVersioned) { + $versionField = $this->_class->versionField; $identifier = $this->_class->getIdentifier(); $versionFieldColumnName = $this->_class->getColumnName($versionField); $where[$versionFieldColumnName] = $entity->version; @@ -339,8 +328,8 @@ class StandardEntityPersister $platform = $this->_conn->getDatabasePlatform(); $uow = $this->_em->getUnitOfWork(); - if ($versioned = $this->_class->isVersioned()) { - $versionField = $this->_class->getVersionField(); + if ($versioned = $this->_class->isVersioned) { + $versionField = $this->_class->versionField; } foreach ($uow->getEntityChangeSet($entity) as $field => $change) { @@ -359,9 +348,8 @@ class StandardEntityPersister continue; } - // Special case: One-one self-referencing of the same class with IDENTITY type key generation. - if ($this->_class->isIdGeneratorIdentity() && $newVal !== null && - $assocMapping->sourceEntityName == $assocMapping->targetEntityName) { + // Special case: One-one self-referencing of the same class. + if ($newVal !== null && $assocMapping->sourceEntityName == $assocMapping->targetEntityName) { $oid = spl_object_hash($newVal); $isScheduledForInsert = $uow->isRegisteredNew($newVal); if (isset($this->_queuedInserts[$oid]) || $isScheduledForInsert) { @@ -448,6 +436,8 @@ class StandardEntityPersister } if ( ! $this->_em->getConfiguration()->getAllowPartialObjects()) { + // Partial objects not allowed, so make sure we put in proxies and + // empty collections respectively. foreach ($this->_class->associationMappings as $field => $assoc) { if ($assoc->isOneToOne()) { if ($assoc->isLazilyFetched) { @@ -455,7 +445,7 @@ class StandardEntityPersister $proxy = $this->_em->getProxyGenerator()->getAssociationProxy($entity, $assoc); $this->_class->reflFields[$field]->setValue($entity, $proxy); } else { - //TODO: Eager fetch? + //TODO: Eager fetch } } else { // Inject collection @@ -492,50 +482,4 @@ class StandardEntityPersister return 'SELECT ' . $columnList . ' FROM ' . $this->_class->getTableName() . ' WHERE ' . $conditionSql; } - - /** - * Dispatches the preInsert event for the given entity. - * - * @param object $entity - */ - final protected function _preInsert($entity) - { - $eventArgs = new \Doctrine\ORM\Event\PreInsertUpdateEventArgs( - $entity, $this->_em->getUnitOfWork()->getEntityChangeSet($entity) - ); - $this->_evm->dispatchEvent(Events::preInsert, $eventArgs); - } - - /** - * Dispatches the postInsert event for the given entity. - * - * @param object $entity - */ - final protected function _postInsert($entity) - { - $this->_evm->dispatchEvent(Events::postInsert); - } - - /** - * Dispatches the preUpdate event for the given entity. - * - * @param object $entity - */ - final protected function _preUpdate($entity) - { - $eventArgs = new \Doctrine\ORM\Event\PreInsertUpdateEventArgs( - $entity, $this->_em->getUnitOfWork()->getEntityChangeSet($entity) - ); - $this->_evm->dispatchEvent(Events::preUpdate, $eventArgs); - } - - /** - * Dispatches the postUpdate event for the given entity. - * - * @param object $entity - */ - final protected function _postUpdate($entity) - { - $this->_evm->dispatchEvent(Events::postUpdate); - } } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 30772dc76..63e2746ef 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -24,6 +24,7 @@ namespace Doctrine\ORM; use Doctrine\Common\Collections\Collection; use Doctrine\Common\DoctrineException; use Doctrine\Common\PropertyChangedListener; +use Doctrine\ORM\Events; use Doctrine\ORM\Internal\CommitOrderCalculator; use Doctrine\ORM\Internal\CommitOrderNode; use Doctrine\ORM\PersistentCollection; @@ -135,7 +136,12 @@ class UnitOfWork implements PropertyChangedListener * @var array */ private $_entityUpdates = array(); - + + /** + * Any extra updates that have been scheduled by persisters. + * + * @var array + */ private $_extraUpdates = array(); /** @@ -210,6 +216,13 @@ class UnitOfWork implements PropertyChangedListener * @var boolean */ private $_useCExtension = false; + + /** + * The EventManager. + * + * @var EventManager + */ + private $_evm; /** * Initializes a new UnitOfWork instance, bound to the given EntityManager. @@ -219,7 +232,7 @@ class UnitOfWork implements PropertyChangedListener public function __construct(EntityManager $em) { $this->_em = $em; - //TODO: any benefit with lazy init? + $this->_evm = $em->getEventManager(); $this->_commitOrderCalculator = new CommitOrderCalculator(); $this->_useCExtension = $this->_em->getConfiguration()->getUseCExtension(); } @@ -248,13 +261,19 @@ class UnitOfWork implements PropertyChangedListener $conn = $this->_em->getConnection(); try { $conn->beginTransaction(); + + if ($this->_entityInsertions) { + foreach ($commitOrder as $class) { + $this->_executeInserts($class); + } + } + + if ($this->_entityUpdates) { + foreach ($commitOrder as $class) { + $this->_executeUpdates($class); + } + } - foreach ($commitOrder as $class) { - $this->_executeInserts($class); - } - foreach ($commitOrder as $class) { - $this->_executeUpdates($class); - } // Extra updates that were requested by persisters. if ($this->_extraUpdates) { $this->_executeExtraUpdates(); @@ -273,8 +292,10 @@ class UnitOfWork implements PropertyChangedListener //TODO: collection recreations (insertions of complete collections) // Entity deletions come last and need to be in reverse commit order - for ($count = count($commitOrder), $i = $count - 1; $i >= 0; --$i) { - $this->_executeDeletions($commitOrder[$i]); + if ($this->_entityDeletions) { + for ($count = count($commitOrder), $i = $count - 1; $i >= 0; --$i) { + $this->_executeDeletions($commitOrder[$i]); + } } $conn->commit(); @@ -441,8 +462,8 @@ class UnitOfWork implements PropertyChangedListener } if ( ! isset($this->_originalEntityData[$oid])) { - // Entity is either NEW or MANAGED but not yet fully persisted - // (only has an id). These result in an INSERT. + // Entity is either NEW or MANAGED but not yet fully persisted (only has an id). + // These result in an INSERT. $this->_originalEntityData[$oid] = $actualData; $this->_entityChangeSets[$oid] = array_map( function($e) { return array(null, $e); }, $actualData @@ -549,12 +570,60 @@ class UnitOfWork implements PropertyChangedListener $this->_originalEntityData[$oid] = $data; } else if ($state == self::STATE_DELETED) { throw DoctrineException::updateMe("Deleted entity in collection detected during flush." - . " Make sure you properly remove deleted entities from collections."); + . " Make sure you properly remove deleted entities from collections."); } // MANAGED associated entities are already taken into account // during changeset calculation anyway, since they are in the identity map. } } + + /** + * EXPERIMENTAL: + * Computes the changeset of an individual entity, independently of the + * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit(). + * + * @param $class + * @param $entity + */ + public function computeSingleEntityChangeSet($class, $entity) + { + $oid = spl_object_hash($entity); + + if ( ! $class->isInheritanceTypeNone()) { + $class = $this->_em->getClassMetadata(get_class($entity)); + } + + $actualData = array(); + foreach ($class->reflFields as $name => $refProp) { + if ( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) { + $actualData[$name] = $refProp->getValue($entity); + } + } + + if ( ! isset($this->_originalEntityData[$oid])) { + $this->_originalEntityData[$oid] = $actualData; + $this->_entityChangeSets[$oid] = array_map( + function($e) { return array(null, $e); }, $actualData + ); + } else { + $originalData = $this->_originalEntityData[$oid]; + $changeSet = array(); + + foreach ($actualData as $propName => $actualValue) { + $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; + if (is_object($orgValue) && $orgValue !== $actualValue) { + $changeSet[$propName] = array($orgValue, $actualValue); + } else if ($orgValue != $actualValue || ($orgValue === null ^ $actualValue === null)) { + $changeSet[$propName] = array($orgValue, $actualValue); + } + } + + if ($changeSet) { + $this->_entityChangeSets[$oid] = $changeSet; + $this->_originalEntityData[$oid] = $actualData; + } + } + } /** * Executes all entity insertions for entities of the specified type. @@ -571,7 +640,9 @@ class UnitOfWork implements PropertyChangedListener unset($this->_entityInsertions[$oid]); } } + $postInsertIds = $persister->executeInserts(); + if ($postInsertIds) { foreach ($postInsertIds as $id => $entity) { // Persister returned a post-insert ID @@ -597,8 +668,12 @@ class UnitOfWork implements PropertyChangedListener $persister = $this->getEntityPersister($className); foreach ($this->_entityUpdates as $oid => $entity) { if (get_class($entity) == $className) { + //TODO: Fire preUpdate + $persister->update($entity); unset($this->_entityUpdates[$oid]); + + //TODO: Fire postUpdate } } } @@ -616,6 +691,8 @@ class UnitOfWork implements PropertyChangedListener if (get_class($entity) == $className) { $persister->delete($entity); unset($this->_entityDeletions[$oid]); + + //TODO: Fire postDelete } } } @@ -737,7 +814,14 @@ class UnitOfWork implements PropertyChangedListener $this->_entityUpdates[$oid] = $entity; } } - + + /** + * Schedules an extra update that will be executed immediately after the + * regular entity updates. + * + * @param $entity + * @param $changeset + */ public function scheduleExtraUpdate($entity, array $changeset) { $this->_extraUpdates[spl_object_hash($entity)] = array($entity, $changeset); @@ -748,7 +832,7 @@ class UnitOfWork implements PropertyChangedListener * Note: Is not very useful currently as dirty entities are only registered * at commit time. * - * @param Doctrine_Entity $entity + * @param object $entity * @return boolean * @todo Rename to isScheduledForUpdate(). */ @@ -789,7 +873,7 @@ class UnitOfWork implements PropertyChangedListener * Checks whether an entity is registered as removed/deleted with the unit * of work. * - * @param Doctrine\ORM\Entity $entity + * @param object $entity * @return boolean * @todo Rename to isScheduledForDelete(). */ @@ -813,6 +897,12 @@ class UnitOfWork implements PropertyChangedListener $this->_entityStates[$oid]); } + /** + * + * + * @param $entity + * @return unknown_type + */ public function isEntityRegistered($entity) { $oid = spl_object_hash($entity); @@ -985,7 +1075,7 @@ class UnitOfWork implements PropertyChangedListener $this->_executeExtraUpdates(); $this->_extraUpdates = array(); } - // remove them from _entityInsertions and _entityChangeSets + // Remove them from _entityInsertions and _entityChangeSets $this->_entityInsertions = array_diff_key($this->_entityInsertions, $insertNow); $this->_entityChangeSets = array_diff_key($this->_entityChangeSets, $insertNow); } @@ -1019,7 +1109,8 @@ class UnitOfWork implements PropertyChangedListener } break; case self::STATE_NEW: - //TODO: Better defer insert for post-insert ID generators also? + //TODO: Fire preSave lifecycle event + $idGen = $class->idGenerator; if ($idGen->isPostInsertGenerator()) { $insertNow[$oid] = $entity; @@ -1081,6 +1172,8 @@ class UnitOfWork implements PropertyChangedListener } $visited[$oid] = $entity; // mark visited + + //TODO: Fire preDelete switch ($this->getEntityState($entity)) { case self::STATE_NEW: @@ -1330,14 +1423,17 @@ class UnitOfWork implements PropertyChangedListener $entity = $this->_identityMap[$class->rootEntityName][$idHash]; $oid = spl_object_hash($entity); $overrideLocalChanges = false; - //$overrideLocalChanges = $query->getHint('doctrine.refresh'); + //$overrideLocalChanges = isset($hints['doctrine.refresh']) && $hints['doctrine.refresh'] === true; } else { $entity = new $className; $oid = spl_object_hash($entity); $this->_entityIdentifiers[$oid] = $id; $this->_entityStates[$oid] = self::STATE_MANAGED; $this->_originalEntityData[$oid] = $data; - $this->addToIdentityMap($entity); + $this->_identityMap[$class->rootEntityName][$idHash] = $entity; + if ($entity instanceof \Doctrine\Common\NotifyPropertyChanged) { + $entity->addPropertyChangedListener($this); + } $overrideLocalChanges = true; } @@ -1366,6 +1462,14 @@ class UnitOfWork implements PropertyChangedListener } } } + + /*if (isset($class->lifecycleCallbacks[Events::postLoad])) { + $class->invokeLifecycleCallbacks(Events::postLoad, $entity); + } + if ($this->_evm->hasListeners(Events::postLoad)) { + $this->_evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em)); + } + */ return $entity; } @@ -1563,7 +1667,7 @@ class UnitOfWork implements PropertyChangedListener */ public function propertyChanged($entity, $propertyName, $oldValue, $newValue) { - if ($this->getEntityState($entity) == self::STATE_MANAGED) { + //if ($this->getEntityState($entity) == self::STATE_MANAGED) { $oid = spl_object_hash($entity); $class = $this->_em->getClassMetadata(get_class($entity)); @@ -1582,6 +1686,6 @@ class UnitOfWork implements PropertyChangedListener } else { $this->_entityUpdates[$oid] = $entity; } - } + //} } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/Company/CompanyPerson.php b/tests/Doctrine/Tests/Models/Company/CompanyPerson.php index abfa95da9..06b5a1dd6 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyPerson.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyPerson.php @@ -27,7 +27,7 @@ class CompanyPerson */ private $name; /** - * @OneToOne(targetEntity="CompanyPerson") + * @OneToOne(targetEntity="CompanyPerson", mappedBy="spouse") * @JoinColumn(name="spouse_id", referencedColumnName="id") */ private $spouse; diff --git a/tests/Doctrine/Tests/Models/ECommerce/ECommerceCustomer.php b/tests/Doctrine/Tests/Models/ECommerce/ECommerceCustomer.php index a35b5acb8..20b43e63e 100644 --- a/tests/Doctrine/Tests/Models/ECommerce/ECommerceCustomer.php +++ b/tests/Doctrine/Tests/Models/ECommerce/ECommerceCustomer.php @@ -33,6 +33,7 @@ class ECommerceCustomer * Example of a one-one self referential association. A mentor can follow * only one customer at the time, while a customer can choose only one * mentor. Not properly appropriate but it works. + * * @OneToOne(targetEntity="ECommerceCustomer", cascade={"save"}) * @JoinColumn(name="mentor_id", referencedColumnName="id") */ diff --git a/tests/Doctrine/Tests/ORM/AllTests.php b/tests/Doctrine/Tests/ORM/AllTests.php index 7384e6fa2..ac5238690 100644 --- a/tests/Doctrine/Tests/ORM/AllTests.php +++ b/tests/Doctrine/Tests/ORM/AllTests.php @@ -2,15 +2,6 @@ namespace Doctrine\Tests\ORM; -use Doctrine\Tests\ORM\Associations; -use Doctrine\Tests\ORM\Cache; -use Doctrine\Tests\ORM\Entity; -use Doctrine\Tests\ORM\Hydration; -use Doctrine\Tests\ORM\Mapping; -use Doctrine\Tests\ORM\Query; -use Doctrine\Tests\ORM\Ticket; -use Doctrine\Tests\ORM\Functional; - if (!defined('PHPUnit_MAIN_METHOD')) { define('PHPUnit_MAIN_METHOD', 'Orm_AllTests::main'); } @@ -42,7 +33,6 @@ class AllTests $suite->addTest(Mapping\AllTests::suite()); $suite->addTest(Functional\AllTests::suite()); $suite->addTest(Id\AllTests::suite()); - $suite->addTest(Locking\AllTests::suite()); return $suite; } diff --git a/tests/Doctrine/Tests/ORM/Functional/AllTests.php b/tests/Doctrine/Tests/ORM/Functional/AllTests.php index f5d378bb9..22c8c7364 100644 --- a/tests/Doctrine/Tests/ORM/Functional/AllTests.php +++ b/tests/Doctrine/Tests/ORM/Functional/AllTests.php @@ -35,6 +35,8 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\ORM\Functional\OneToManySelfReferentialAssociationTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\ManyToManySelfReferentialAssociationTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\ReferenceProxyTest'); + + $suite->addTest(Locking\AllTests::suite()); return $suite; } diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index f0eb3499a..6ef7d4ffc 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -26,6 +26,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $user->username = 'romanb'; $user->status = 'developer'; $this->_em->save($user); + $this->assertTrue(is_numeric($user->id)); $this->assertTrue($this->_em->contains($user)); diff --git a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php index 37f10ae3b..f10de44eb 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php @@ -21,7 +21,7 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase } public function testCRUD() - { + { $person = new CompanyPerson; $person->setName('Roman S. Borschel'); diff --git a/tests/Doctrine/Tests/ORM/Functional/Locking/AllTests.php b/tests/Doctrine/Tests/ORM/Functional/Locking/AllTests.php new file mode 100644 index 000000000..9b021ced8 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Locking/AllTests.php @@ -0,0 +1,30 @@ +addTestSuite('Doctrine\Tests\ORM\Functional\Locking\OptimisticTest'); + + return $suite; + } +} + +if (PHPUnit_MAIN_METHOD == 'Orm_Functional_Locking_AllTests::main') { + AllTests::main(); +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Locking/OptimisticTest.php b/tests/Doctrine/Tests/ORM/Functional/Locking/OptimisticTest.php similarity index 90% rename from tests/Doctrine/Tests/ORM/Locking/OptimisticTest.php rename to tests/Doctrine/Tests/ORM/Functional/Locking/OptimisticTest.php index b401ebab7..5d82b111c 100644 --- a/tests/Doctrine/Tests/ORM/Locking/OptimisticTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/Locking/OptimisticTest.php @@ -1,8 +1,6 @@ _schemaTool->createSchema(array( - $this->_em->getClassMetadata('Doctrine\Tests\ORM\Locking\OptimisticJoinedParent'), - $this->_em->getClassMetadata('Doctrine\Tests\ORM\Locking\OptimisticJoinedChild'), - $this->_em->getClassMetadata('Doctrine\Tests\ORM\Locking\OptimisticStandard') + $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Locking\OptimisticJoinedParent'), + $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Locking\OptimisticJoinedChild'), + $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Locking\OptimisticStandard') )); } catch (\Exception $e) { // Swallow all exceptions. We do not test the schema tool here. @@ -50,7 +48,7 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase */ public function testJoinedChildFailureThrowsException() { - $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Locking\OptimisticJoinedChild t WHERE t.name = :name'); + $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Functional\Locking\OptimisticJoinedChild t WHERE t.name = :name'); $q->setParameter('name', 'child'); $test = $q->getSingleResult(); @@ -79,7 +77,7 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase */ public function testJoinedParentFailureThrowsException() { - $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Locking\OptimisticJoinedParent t WHERE t.name = :name'); + $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Functional\Locking\OptimisticJoinedParent t WHERE t.name = :name'); $q->setParameter('name', 'parent'); $test = $q->getSingleResult(); @@ -108,7 +106,7 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase */ public function testStandardFailureThrowsException() { - $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Locking\OptimisticStandard t WHERE t.name = :name'); + $q = $this->_em->createQuery('SELECT t FROM Doctrine\Tests\ORM\Functional\Locking\OptimisticStandard t WHERE t.name = :name'); $q->setParameter('name', 'test'); $test = $q->getSingleResult(); @@ -129,7 +127,7 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase * @DiscriminatorValue("parent") * @InheritanceType("JOINED") * @DiscriminatorColumn(name="discr", type="string") - * @SubClasses({"Doctrine\Tests\ORM\Locking\OptimisticJoinedChild"}) + * @SubClasses({"Doctrine\Tests\ORM\Functional\Locking\OptimisticJoinedChild"}) */ class OptimisticJoinedParent { diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToManyBidirectionalAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToManyBidirectionalAssociationTest.php index 7018bb4eb..8866d3230 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToManyBidirectionalAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToManyBidirectionalAssociationTest.php @@ -32,6 +32,7 @@ class OneToManyBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctiona $this->product->addFeature($this->firstFeature); $this->product->addFeature($this->secondFeature); $this->_em->save($this->product); + $this->_em->flush(); $this->assertFeatureForeignKeyIs($this->product->getId(), $this->firstFeature); $this->assertFeatureForeignKeyIs($this->product->getId(), $this->secondFeature); @@ -40,6 +41,7 @@ class OneToManyBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctiona public function testSavesAnEmptyCollection() { $this->_em->save($this->product); + $this->_em->flush(); $this->assertEquals(0, count($this->product->getFeatures())); } @@ -47,6 +49,7 @@ class OneToManyBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctiona public function testDoesNotSaveAnInverseSideSet() { $this->product->brokenAddFeature($this->firstFeature); $this->_em->save($this->product); + $this->_em->flush(); $this->assertFeatureForeignKeyIs(null, $this->firstFeature); } diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php index dcd4785ea..30f9589fe 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php @@ -28,6 +28,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional public function testSavesAOneToOneAssociationWithCascadeSaveSet() { $this->customer->setCart($this->cart); $this->_em->save($this->customer); + $this->_em->flush(); $this->assertCartForeignKeyIs($this->customer->getId()); } @@ -35,6 +36,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional public function testDoesNotSaveAnInverseSideSet() { $this->customer->brokenSetCart($this->cart); $this->_em->save($this->customer); + $this->_em->flush(); $this->assertCartForeignKeyIs(null); } diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneSelfReferentialAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneSelfReferentialAssociationTest.php index 0c588d8a1..c2a533238 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneSelfReferentialAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneSelfReferentialAssociationTest.php @@ -31,6 +31,7 @@ class OneToOneSelfReferentialAssociationTest extends \Doctrine\Tests\OrmFunction public function testSavesAOneToOneAssociationWithCascadeSaveSet() { $this->customer->setMentor($this->mentor); $this->_em->save($this->customer); + $this->_em->flush(); $this->assertForeignKeyIs($this->mentor->getId()); } @@ -59,7 +60,7 @@ class OneToOneSelfReferentialAssociationTest extends \Doctrine\Tests\OrmFunction $this->_em->flush(); $this->_em->clear(); - $query = $this->_em->createQuery('select c, m from Doctrine\Tests\Models\ECommerce\ECommerceCustomer c left join c.mentor m'); + $query = $this->_em->createQuery('select c, m from Doctrine\Tests\Models\ECommerce\ECommerceCustomer c left join c.mentor m order by c.id asc'); $result = $query->getResultList(); $customer = $result[0]; diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneUnidirectionalAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneUnidirectionalAssociationTest.php index 07c1914d8..eb32819f7 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneUnidirectionalAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneUnidirectionalAssociationTest.php @@ -29,6 +29,7 @@ class OneToOneUnidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctiona public function testSavesAOneToOneAssociationWithCascadeSaveSet() { $this->product->setShipping($this->shipping); $this->_em->save($this->product); + $this->_em->flush(); $this->assertForeignKeyIs($this->shipping->getId()); } diff --git a/tests/Doctrine/Tests/ORM/Locking/AllTests.php b/tests/Doctrine/Tests/ORM/Locking/AllTests.php deleted file mode 100644 index 6a497e4dd..000000000 --- a/tests/Doctrine/Tests/ORM/Locking/AllTests.php +++ /dev/null @@ -1,30 +0,0 @@ -addTestSuite('Doctrine\Tests\ORM\Locking\OptimisticTest'); - - return $suite; - } -} - -if (PHPUnit_MAIN_METHOD == 'Orm_Locking_AllTests::main') { - AllTests::main(); -} \ No newline at end of file diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 810261116..ab9530605 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -77,15 +77,15 @@ class OrmFunctionalTestCase extends OrmTestCase $conn->exec('DELETE FROM cms_users'); } if (isset($this->_usedModelSets['ecommerce'])) { - $conn->exec('DELETE FROM ecommerce_carts'); - $conn->exec('DELETE FROM ecommerce_customers'); - $conn->exec('DELETE FROM ecommerce_products'); $conn->exec('DELETE FROM ecommerce_carts_products'); - $conn->exec('DELETE FROM ecommerce_shippings'); - $conn->exec('DELETE FROM ecommerce_features'); - $conn->exec('DELETE FROM ecommerce_categories'); $conn->exec('DELETE FROM ecommerce_products_categories'); $conn->exec('DELETE FROM ecommerce_products_related'); + $conn->exec('DELETE FROM ecommerce_carts'); + $conn->exec('DELETE FROM ecommerce_customers'); + $conn->exec('DELETE FROM ecommerce_features'); + $conn->exec('DELETE FROM ecommerce_products'); + $conn->exec('DELETE FROM ecommerce_shippings'); + $conn->exec('DELETE FROM ecommerce_categories'); } if (isset($this->_usedModelSets['company'])) { $conn->exec('DELETE FROM company_persons_friends');