From 2395888feb9498a298d8ca8a69722f1b54fe8221 Mon Sep 17 00:00:00 2001 From: romanb Date: Sat, 3 Jan 2009 19:50:13 +0000 Subject: [PATCH] General work. Now using spl_object_hash. --- lib/Doctrine/Common/ClassLoader.php | 26 +- lib/Doctrine/Common/EventManager.php | 4 +- lib/Doctrine/DBAL/Configuration.php | 3 +- lib/Doctrine/DBAL/Driver/PDOConnection.php | 3 - lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php | 4 +- lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php | 4 +- lib/Doctrine/DBAL/Driver/PDOStatement.php | 2 - lib/Doctrine/DBAL/Driver/Statement.php | 14 +- lib/Doctrine/DBAL/DriverManager.php | 19 +- .../DBAL/Platforms/AbstractPlatform.php | 2 +- lib/Doctrine/ORM/Collection.php | 145 +------ lib/Doctrine/ORM/Configuration.php | 4 +- lib/Doctrine/ORM/Entity.php | 2 - lib/Doctrine/ORM/EntityManager.php | 164 +------- lib/Doctrine/ORM/EntityRepository.php | 2 +- lib/Doctrine/ORM/Id/AbstractIdGenerator.php | 28 +- lib/Doctrine/ORM/Id/Assigned.php | 34 +- lib/Doctrine/ORM/Id/IdentityGenerator.php | 14 +- .../ORM/Internal/CommitOrderCalculator.php | 2 +- lib/Doctrine/ORM/Internal/CommitOrderNode.php | 9 +- .../ORM/Internal/Hydration/ArrayDriver.php | 10 +- .../ORM/Internal/Hydration/ObjectDriver.php | 114 +++--- .../Internal/Hydration/StandardHydrator.php | 27 +- lib/Doctrine/ORM/Internal/Null.php | 25 +- .../ORM/Mapping/AssociationMapping.php | 4 +- lib/Doctrine/ORM/Mapping/ClassMetadata.php | 150 ++----- lib/Doctrine/ORM/Mapping/OneToManyMapping.php | 13 +- lib/Doctrine/ORM/Mapping/OneToOneMapping.php | 4 +- .../Persisters/AbstractEntityPersister.php | 387 ++---------------- .../Persisters/StandardEntityPersister.php | 16 +- lib/Doctrine/ORM/UnitOfWork.php | 252 +++++++----- tests/Orm/Hydration/BasicHydrationTest.php | 1 + tests/Orm/UnitOfWorkTest.php | 191 +++++---- .../lib/mocks/Doctrine_EntityManagerMock.php | 27 +- .../mocks/Doctrine_EntityPersisterMock.php | 30 +- .../Doctrine_IdentityIdGeneratorMock.php | 20 + tests/lib/mocks/Doctrine_SequenceMock.php | 2 - tests/lib/mocks/Doctrine_UnitOfWorkMock.php | 14 +- tests/models/forum/ForumUser.php | 12 + 39 files changed, 665 insertions(+), 1119 deletions(-) create mode 100644 tests/lib/mocks/Doctrine_IdentityIdGeneratorMock.php diff --git a/lib/Doctrine/Common/ClassLoader.php b/lib/Doctrine/Common/ClassLoader.php index cf460fc51..329d232b1 100644 --- a/lib/Doctrine/Common/ClassLoader.php +++ b/lib/Doctrine/Common/ClassLoader.php @@ -1,12 +1,18 @@ setBasePath($prefix, $basePath); + * Example: + * $classLoader->setBasePath('Doctrine', '/usr/local/phplibs/doctrine/lib'); + * Then, when trying to load the class Doctrine\ORM\EntityManager, for example + * the classloader will look for /usr/local/phplibs/doctrine/lib/Doctrine/ORM/EntityManager.php + * * 3) DO NOT setCheckFileExists(true). Doing so is expensive in terms of performance. + * 4) Use an opcode-cache (i.e. APC) (STRONGLY RECOMMENDED). * * @since 2.0 * @author romanb @@ -16,7 +22,7 @@ class Doctrine_Common_ClassLoader private $_namespaceSeparator = '_'; private $_fileExtension = '.php'; private $_checkFileExists = false; - private $_basePath; + private $_basePaths = array(); public function __construct() { @@ -38,19 +44,20 @@ class Doctrine_Common_ClassLoader } /** - * Sets a static base path that is prepended to the path derived from the class itself. + * Sets a static base path for classes with a certain prefix that is prepended + * to the path derived from the class itself. * * @param string $basePath */ - public function setBasePath($basePath) + public function setBasePath($classPrefix, $basePath) { - $this->_basePath = $basePath; + $this->_basePaths[$classPrefix] = $basePath; } /** * Loads the given class or interface. * - * @param string $classname The name of the class to load. + * @param string $classname The name of the class to load. * @return boolean TRUE if the class has been successfully loaded, FALSE otherwise. */ public function loadClass($className) @@ -59,9 +66,10 @@ class Doctrine_Common_ClassLoader return false; } + $prefix = substr($className, 0, strpos($className, $this->_namespaceSeparator)); $class = ''; - if ($this->_basePath) { - $class .= $this->_basePath . DIRECTORY_SEPARATOR; + if (isset($this->_basePaths[$prefix])) { + $class .= $this->_basePaths[$prefix] . DIRECTORY_SEPARATOR; } $class .= str_replace($this->_namespaceSeparator, DIRECTORY_SEPARATOR, $className) . $this->_fileExtension; diff --git a/lib/Doctrine/Common/EventManager.php b/lib/Doctrine/Common/EventManager.php index c148f5bc0..3afb80cde 100644 --- a/lib/Doctrine/Common/EventManager.php +++ b/lib/Doctrine/Common/EventManager.php @@ -19,7 +19,7 @@ * . */ -#namespace Doctrine::Common; +#namespace Doctrine\Common; /** * The EventManager is the central point of Doctrine's event listener system. @@ -101,7 +101,7 @@ class Doctrine_Common_EventManager * Adds an EventSubscriber. The subscriber is asked for all the events he is * interested in and added as a listener for these events. * - * @param Doctrine::Common::Event::EventSubscriber $subscriber The subscriber. + * @param Doctrine\Common\EventSubscriber $subscriber The subscriber. */ public function addEventSubscriber(Doctrine_Common_EventSubscriber $subscriber) { diff --git a/lib/Doctrine/DBAL/Configuration.php b/lib/Doctrine/DBAL/Configuration.php index acbb45579..cf714b05f 100644 --- a/lib/Doctrine/DBAL/Configuration.php +++ b/lib/Doctrine/DBAL/Configuration.php @@ -22,8 +22,7 @@ #namespace Doctrine\DBAL; /** - * The Configuration is the container for all configuration options of Doctrine. - * It combines all configuration options from DBAL & ORM. + * Configuration container for the Doctrine DBAL. * * INTERNAL: When adding a new configuration option just write a getter/setter * pair and add the option to the _attributes array with a proper default value. diff --git a/lib/Doctrine/DBAL/Driver/PDOConnection.php b/lib/Doctrine/DBAL/Driver/PDOConnection.php index e7f3e32a2..9a8ef3870 100644 --- a/lib/Doctrine/DBAL/Driver/PDOConnection.php +++ b/lib/Doctrine/DBAL/Driver/PDOConnection.php @@ -1,5 +1,4 @@ setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); } } - -?> \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php b/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php index 710db6555..f2ddd2005 100644 --- a/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php +++ b/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php @@ -1,8 +1,8 @@ \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Driver/Statement.php b/lib/Doctrine/DBAL/Driver/Statement.php index 4ec28f54e..38e07318f 100644 --- a/lib/Doctrine/DBAL/Driver/Statement.php +++ b/lib/Doctrine/DBAL/Driver/Statement.php @@ -18,16 +18,16 @@ * and is licensed under the LGPL. For more information, see * . */ - -#namespace Doctrine::DBAL::Driver; + +#namespace Doctrine\DBAL\Driver; /** - * Statement interface. - * Drivers must implement this interface. - * - * This resembles the PDOStatement interface. + * Statement interface. + * Drivers must implement this interface. * - * @author Konsta Vesterinen + * This resembles the PDOStatement interface. + * + * @author Konsta Vesterinen * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org diff --git a/lib/Doctrine/DBAL/DriverManager.php b/lib/Doctrine/DBAL/DriverManager.php index e5fb0080f..35f75f7ba 100644 --- a/lib/Doctrine/DBAL/DriverManager.php +++ b/lib/Doctrine/DBAL/DriverManager.php @@ -19,10 +19,10 @@ * . */ -#namespace Doctrine::DBAL; +#namespace Doctrine\DBAL; /** - * Factory for creating Doctrine::DBAL::Connection instances. + * Factory for creating Doctrine\DBAL\Connection instances. * * @author Roman Borschel * @since 2.0 @@ -43,12 +43,13 @@ final class Doctrine_DBAL_DriverManager 'pdo_firebird' => 'Doctrine_DBAL_Driver_PDOFirebird_Driver', 'pdo_informix' => 'Doctrine_DBAL_Driver_PDOInformix_Driver', ); - + + /** Private constructor. This class cannot be instantiated. */ private function __construct() {} /** * Creates a connection object based on the specified parameters. - * This method returns a Doctrine::DBAL::Connection which wraps the underlying + * This method returns a Doctrine\DBAL\Connection which wraps the underlying * driver connection. * * $params must contain at least one of the following. @@ -79,16 +80,16 @@ final class Doctrine_DBAL_DriverManager * * pdo: * You can pass an existing PDO instance through this parameter. The PDO - * instance will be wrapped in a Doctrine::DBAL::Connection. + * instance will be wrapped in a Doctrine\DBAL\Connection. * * wrapperClass: * You may specify a custom wrapper class through the 'wrapperClass' - * parameter but this class MUST inherit from Doctrine::DBAL::Connection. + * parameter but this class MUST inherit from Doctrine\DBAL\Connection. * * @param array $params The parameters. - * @param Doctrine::Common::Configuration The configuration to use. - * @param Doctrine::Common::EventManager The event manager to use. - * @return Doctrine::DBAL::Connection + * @param Doctrine\DBAL\Configuration The configuration to use. + * @param Doctrine\Common\EventManager The event manager to use. + * @return Doctrine\DBAL\Connection */ public static function getConnection(array $params, Doctrine_DBAL_Configuration $config = null, diff --git a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php index e1c705f07..ae9fa1638 100644 --- a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php @@ -19,7 +19,7 @@ * . */ -#namespace Doctrine::DBAL::Platforms; +#namespace Doctrine\DBAL\Platforms; /** * Base class for all DatabasePlatforms. The DatabasePlatforms are the central diff --git a/lib/Doctrine/ORM/Collection.php b/lib/Doctrine/ORM/Collection.php index 2cd6314a3..beb458219 100644 --- a/lib/Doctrine/ORM/Collection.php +++ b/lib/Doctrine/ORM/Collection.php @@ -19,10 +19,10 @@ * . */ -#namespace Doctrine::ORM; +#namespace Doctrine\ORM; /** - * A persistent collection. + * A persistent collection wrapper. * * A collection object is strongly typed in the sense that it can only contain * entities of a specific type or one of it's subtypes. A collection object is @@ -73,7 +73,7 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa protected $_snapshot = array(); /** - * This entity that owns this collection. + * The entity that owns this collection. * * @var Doctrine\ORM\Entity */ @@ -468,7 +468,7 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa public function add($value, $key = null) { if ( ! $value instanceof $this->_entityBaseType) { - throw new Doctrine_Record_Exception('Value variable in collection is not an instance of Doctrine_Entity.'); + throw new Doctrine_Exception('Invalid instance.'); } // TODO: Really prohibit duplicates? @@ -511,121 +511,6 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa //TODO: Register collection as dirty with the UoW if necessary //$this->_changed(); } - - /** - * INTERNAL: - * loadRelated - * - * @param mixed $name - * @return boolean - * @todo New implementation & maybe move elsewhere. - */ - /*public function loadRelated($name = null) - { - $list = array(); - $query = new Doctrine_Query($this->_mapper->getConnection()); - - if ( ! isset($name)) { - foreach ($this->_data as $record) { - // FIXME: composite key support - $ids = $record->identifier(); - $value = count($ids) > 0 ? array_pop($ids) : null; - if ($value !== null) { - $list[] = $value; - } - } - $query->from($this->_mapper->getComponentName() - . '(' . implode(", ",$this->_mapper->getTable()->getPrimaryKeys()) . ')'); - $query->where($this->_mapper->getComponentName() - . '.id IN (' . substr(str_repeat("?, ", count($list)),0,-2) . ')'); - - return $query; - } - - $rel = $this->_mapper->getTable()->getRelation($name); - - if ($rel instanceof Doctrine_Relation_LocalKey || $rel instanceof Doctrine_Relation_ForeignKey) { - foreach ($this->_data as $record) { - $list[] = $record[$rel->getLocal()]; - } - } else { - foreach ($this->_data as $record) { - $ids = $record->identifier(); - $value = count($ids) > 0 ? array_pop($ids) : null; - if ($value !== null) { - $list[] = $value; - } - } - } - - $dql = $rel->getRelationDql(count($list), 'collection'); - $coll = $query->query($dql, $list); - - $this->populateRelated($name, $coll); - }*/ - - /** - * INTERNAL: - * populateRelated - * - * @param string $name - * @param Doctrine_Collection $coll - * @return void - * @todo New implementation & maybe move elsewhere. - */ - /*protected function populateRelated($name, Doctrine_Collection $coll) - { - $rel = $this->_mapper->getTable()->getRelation($name); - $table = $rel->getTable(); - $foreign = $rel->getForeign(); - $local = $rel->getLocal(); - - if ($rel instanceof Doctrine_Relation_LocalKey) { - foreach ($this->_data as $key => $record) { - foreach ($coll as $k => $related) { - if ($related[$foreign] == $record[$local]) { - $this->_data[$key]->_setRelated($name, $related); - } - } - } - } else if ($rel instanceof Doctrine_Relation_ForeignKey) { - foreach ($this->_data as $key => $record) { - if ( ! $record->exists()) { - continue; - } - $sub = new Doctrine_Collection($rel->getForeignComponentName()); - - foreach ($coll as $k => $related) { - if ($related[$foreign] == $record[$local]) { - $sub->add($related); - $coll->remove($k); - } - } - - $this->_data[$key]->_setRelated($name, $sub); - } - } else if ($rel instanceof Doctrine_Relation_Association) { - // @TODO composite key support - $identifier = (array)$this->_mapper->getClassMetadata()->getIdentifier(); - $asf = $rel->getAssociationFactory(); - $name = $table->getComponentName(); - - foreach ($this->_data as $key => $record) { - if ( ! $record->exists()) { - continue; - } - $sub = new Doctrine_Collection($rel->getForeignComponentName()); - foreach ($coll as $k => $related) { - $idField = $identifier[0]; - if ($related->get($local) == $record[$idField]) { - $sub->add($related->get($name)); - } - } - $this->_data[$key]->_setRelated($name, $sub); - - } - } - }*/ /** * INTERNAL: @@ -661,7 +546,8 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa } /** - * INTERNAL: Returns the data of the last snapshot. + * INTERNAL: + * Returns the data of the last snapshot. * * @return array returns the data in last snapshot */ @@ -671,7 +557,8 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa } /** - * INTERNAL: Processes the difference of the last snapshot and the current data. + * INTERNAL: + * Processes the difference of the last snapshot and the current data. * * an example: * Snapshot with the objects 1, 2 and 4 @@ -719,7 +606,7 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa } /** - * Populate a Doctrine_Collection from an array of data. + * Populate a Collection from an array of data. * * @param string $array * @return void @@ -733,7 +620,7 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa } /** - * Synchronizes a Doctrine_Collection with data from an array. + * Synchronizes a Collection with data from an array. * * it expects an array representation of a Doctrine_Collection similar to the return * value of the toArray() method. It will create Dectrine_Records that don't exist @@ -760,7 +647,8 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa } /** - * INTERNAL: getDeleteDiff + * INTERNAL: + * getDeleteDiff * * @return array */ @@ -786,17 +674,17 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa */ protected function _compareRecords($a, $b) { - if ($a->getOid() == $b->getOid()) { + if ($a === $b) { return 0; } - return ($a->getOid() > $b->getOid()) ? 1 : -1; + return 1; } /** * * @param $deep */ - public function free($deep = false) + /*public function free($deep = false) { foreach ($this->getData() as $key => $record) { if ( ! ($record instanceof Doctrine_Null)) { @@ -810,8 +698,7 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa $this->_owner->free($deep); $this->_owner = null; } - } - + }*/ /** * getIterator diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index def2a0efd..1030ab697 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -24,7 +24,7 @@ #use Doctrine\DBAL\Configuration; /** - * The Configuration is the container for all configuration options of Doctrine. + * Configuration container for all configuration options of Doctrine. * It combines all configuration options from DBAL & ORM. * * INTERNAL: When adding a new configuration option just write a getter/setter @@ -72,7 +72,7 @@ class Doctrine_ORM_Configuration extends Doctrine_DBAL_Configuration return $this->_attributes['metadataCacheImpl']; } - public function setMetadataCacheImpl(Doctrine_Cache_Interface $cacheImpl) + public function setMetadataCacheImpl($cacheImpl) { $this->_attributes['metadataCacheImpl'] = $cacheImpl; } diff --git a/lib/Doctrine/ORM/Entity.php b/lib/Doctrine/ORM/Entity.php index 96bf9475b..44de89fb3 100644 --- a/lib/Doctrine/ORM/Entity.php +++ b/lib/Doctrine/ORM/Entity.php @@ -21,8 +21,6 @@ #namespace Doctrine\ORM; -#use \Serializable; - /** * Entity marker interface. * diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 6a0079bc0..7b1e56d22 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -86,14 +86,14 @@ class Doctrine_ORM_EntityManager /** * The database connection used by the EntityManager. * - * @var Doctrine_Connection + * @var Connection */ private $_conn; /** * The metadata factory, used to retrieve the metadata of entity classes. * - * @var Doctrine::ORM::Mapping::ClassMetadataFactory + * @var Doctrine\ORM\Mapping\ClassMetadataFactory */ private $_metadataFactory; @@ -131,19 +131,8 @@ class Doctrine_ORM_EntityManager * @var EventManager */ private $_eventManager; - - /** - * Container that is used temporarily during hydration. - * - * @var array - */ - private $_tmpEntityData = array(); - private $_idGenerators = array(); - private $_closed = false; - - private $_originalEntityData = array(); /** * Creates a new EntityManager that operates on the given database connection. @@ -220,6 +209,7 @@ class Doctrine_ORM_EntityManager * Returns the metadata for a class. * * @return Doctrine_Metadata + * @internal Performance-sensitive method. */ public function getClassMetadata($className) { @@ -262,8 +252,7 @@ class Doctrine_ORM_EntityManager * Creates a new Query object. * * @param string The DQL string. - * @return Doctrine::ORM::Query - * @todo package:orm + * @return Doctrine\ORM\Query */ public function createQuery($dql = "") { @@ -271,7 +260,6 @@ class Doctrine_ORM_EntityManager if ( ! empty($dql)) { $query->setDql($dql); } - return $query; } @@ -281,16 +269,16 @@ class Doctrine_ORM_EntityManager * This is usually not of interest for users, mainly for internal use. * * @param string $entityName The name of the Entity. - * @return Doctrine::ORM::Internal::EntityPersister + * @return Doctrine\ORM\Persister\AbstractEntityPersister */ public function getEntityPersister($entityName) { if ( ! isset($this->_persisters[$entityName])) { $class = $this->getClassMetadata($entityName); - if ($class->getInheritanceType() == Doctrine::INHERITANCE_TYPE_JOINED) { + if ($class->getInheritanceType() == Doctrine_ORM_Mapping_ClassMetadata::INHERITANCE_TYPE_JOINED) { $persister = new Doctrine_EntityPersister_JoinedSubclass($this, $class); } else { - $persister = new Doctrine_EntityPersister_Standard($this, $class); + $persister = new Doctrine_ORM_Persisters_StandardEntityPersister($this, $class); } $this->_persisters[$entityName] = $persister; } @@ -444,7 +432,7 @@ class Doctrine_ORM_EntityManager /** * Removes the given entity from the persistent store. * - * @param Doctrine::ORM::Entity $entity + * @param Doctrine\ORM\Entity $entity * @return void */ public function delete(Doctrine_ORM_Entity $entity) @@ -506,93 +494,6 @@ class Doctrine_ORM_EntityManager return $repository; } - /** - * Creates an entity. Used for reconstitution as well as initial creation. - * - * @param string $className The name of the entity class. - * @param array $data The data for the entity. - * @return Doctrine\ORM\Entity - */ - public function createEntity($className, array $data, Doctrine_Query $query = null) - { - $this->_errorIfNotActiveOrClosed(); - - $this->_tmpEntityData = $data; - $className = $this->_inferCorrectClassName($data, $className); - $classMetadata = $this->getClassMetadata($className); - if ( ! empty($data)) { - $identifierFieldNames = $classMetadata->getIdentifier(); - $isNew = false; - foreach ($identifierFieldNames as $fieldName) { - if ( ! isset($data[$fieldName])) { - // id field not found return new entity - $isNew = true; - break; - } - $id[] = $data[$fieldName]; - } - - if ($isNew) { - $entity = new $className; - } else { - $idHash = $this->_unitOfWork->getIdentifierHash($id); - $entity = $this->_unitOfWork->tryGetByIdHash($idHash, $classMetadata->getRootClassName()); - if ($entity) { - $this->_mergeData($entity, $data/*, $classMetadata, $query->getHint('doctrine.refresh')*/); - return $entity; - } else { - $entity = new $className; - $this->_unitOfWork->addToIdentityMap($entity); - } - } - } else { - $entity = new $className; - } - - //$this->_originalEntityData[$entity->getOid()] = $data; - - return $entity; - } - - /** - * Merges the given data into the given entity, optionally overriding - * local changes. - * - * @param Doctrine\ORM\Entity $entity - * @param array $data - * @param boolean $overrideLocalChanges - * @return void - */ - private function _mergeData(Doctrine_ORM_Entity $entity, /*$class,*/ array $data, $overrideLocalChanges = false) { - if ($overrideLocalChanges) { - foreach ($data as $field => $value) { - $entity->_internalSetField($field, $value); - } - } else { - foreach ($data as $field => $value) { - $currentValue = $entity->get($field); - if ( ! isset($currentValue) || $entity->_internalGetField($field) === null) { - $entity->_internalSetField($field, $value); - } - } - } - - // NEW - /*if ($overrideLocalChanges) { - foreach ($data as $field => $value) { - $class->getReflectionProperty($field)->setValue($entity, $value); - } - } else { - foreach ($data as $field => $value) { - $currentValue = $class->getReflectionProperty($field)->getValue($entity); - if ( ! isset($this->_originalEntityData[$entity->getOid()]) || - $currentValue == $this->_originalEntityData[$entity->getOid()]) { - $class->getReflectionProperty($field)->setValue($entity, $value); - } - } - }*/ - } - /** * Checks if the instance is managed by the EntityManager. * @@ -606,51 +507,10 @@ class Doctrine_ORM_EntityManager ! $this->_unitOfWork->isRegisteredRemoved($entity); } - /** - * INTERNAL: For internal hydration purposes only. - * - * Gets the temporarily stored entity data. - * - * @return array - */ - public function _getTmpEntityData() - { - $data = $this->_tmpEntityData; - $this->_tmpEntityData = array(); - return $data; - } - - /** - * Check the dataset for a discriminator column to determine the correct - * class to instantiate. If no discriminator column is found, the given - * classname will be returned. - * - * @param array $data - * @param string $className - * @return string The name of the class to instantiate. - */ - private function _inferCorrectClassName(array $data, $className) - { - $class = $this->getClassMetadata($className); - - $discCol = $class->getInheritanceOption('discriminatorColumn'); - if ( ! $discCol) { - return $className; - } - - $discMap = $class->getInheritanceOption('discriminatorMap'); - - if (isset($data[$discCol], $discMap[$data[$discCol]])) { - return $discMap[$data[$discCol]]; - } else { - return $className; - } - } - /** * Gets the EventManager used by the EntityManager. * - * @return Doctrine::Common::EventManager + * @return Doctrine\Common\EventManager */ public function getEventManager() { @@ -660,7 +520,7 @@ class Doctrine_ORM_EntityManager /** * Gets the Configuration used by the EntityManager. * - * @return Doctrine::Common::Configuration + * @return Doctrine\ORM\Configuration */ public function getConfiguration() { @@ -682,7 +542,7 @@ class Doctrine_ORM_EntityManager /** * Gets the UnitOfWork used by the EntityManager to coordinate operations. * - * @return Doctrine::ORM::UnitOfWork + * @return Doctrine\ORM\UnitOfWork */ public function getUnitOfWork() { @@ -747,7 +607,7 @@ class Doctrine_ORM_EntityManager /** * Static lookup to get the currently active EntityManager. * - * @return Doctrine::ORM::EntityManager + * @return Doctrine\ORM\EntityManager */ public static function getActiveEntityManager() { diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 8faa7a3e0..13c45c180 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -19,7 +19,7 @@ * . */ -#namespace Doctrine::ORM; +#namespace Doctrine\ORM; /** * A repository provides the illusion of an in-memory Entity store. diff --git a/lib/Doctrine/ORM/Id/AbstractIdGenerator.php b/lib/Doctrine/ORM/Id/AbstractIdGenerator.php index fea0f8089..4708f341d 100644 --- a/lib/Doctrine/ORM/Id/AbstractIdGenerator.php +++ b/lib/Doctrine/ORM/Id/AbstractIdGenerator.php @@ -1,6 +1,6 @@ _em = $em; } - + + /** + * Generates an identifier for an entity. + * + * @param Doctrine\ORM\Entity $entity + * @return mixed + */ abstract public function generate($entity); + + /** + * Gets whether this generator is a post-insert generator which means that + * {@link generate()} must be called after the entity has been inserted + * into the database. + * By default, this method returns FALSE. Generators that have this requirement + * must override this method and return TRUE. + * + * @return boolean + */ + public function isPostInsertGenerator() { + return false; + } } -?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/Id/Assigned.php b/lib/Doctrine/ORM/Id/Assigned.php index 5f9ce9067..285e4baa2 100644 --- a/lib/Doctrine/ORM/Id/Assigned.php +++ b/lib/Doctrine/ORM/Id/Assigned.php @@ -4,23 +4,43 @@ * Special generator for application-assigned identifiers (doesnt really generate anything). * * @since 2.0 + * @author Roman Borschel */ class Doctrine_ORM_Id_Assigned extends Doctrine_ORM_Id_AbstractIdGenerator { /** - * Enter description here... + * Returns the identifier assigned to the given entity. * - * @param Doctrine_ORM_Entity $entity - * @return unknown + * @param Doctrine\ORM\Entity $entity + * @return mixed * @override */ public function generate($entity) { - if ( ! $entity->_identifier()) { - throw new Doctrine_Exception("Entity '$entity' is missing an assigned Id"); + $class = $this->_em->getClassMetadata(get_class($entity)); + if ($class->isIdentifierComposite()) { + $identifier = array(); + $idFields = $class->getIdentifierFieldNames(); + foreach ($idFields as $idField) { + $identifier[] = + $value = $class->getReflectionProperty($idField)->getValue($entity); + if (isset($value)) { + $identifier[] = $value; + } + } + } else { + $value = $class->getReflectionProperty($class->getSingleIdentifierFieldName()) + ->getValue($entity); + if (isset($value)) { + $identifier = array($value); + } } - return $entity->_identifier(); + + if ( ! $identifier) { + throw new Doctrine_Exception("Entity '$entity' is missing an assigned ID."); + } + + return $identifier; } } -?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/Id/IdentityGenerator.php b/lib/Doctrine/ORM/Id/IdentityGenerator.php index 52b343f85..83f5b7a1f 100644 --- a/lib/Doctrine/ORM/Id/IdentityGenerator.php +++ b/lib/Doctrine/ORM/Id/IdentityGenerator.php @@ -10,14 +10,16 @@ class Doctrine_ORM_Id_IdentityGenerator extends Doctrine_ORM_Id_AbstractIdGenera * @override */ public function generate($entity) - { - return self::POST_INSERT_INDICATOR; - } - - public function getPostInsertId() { return $this->_em->getConnection()->lastInsertId(); } + + /** + * @return boolean + * @override + */ + public function isPostInsertGenerator() { + return true; + } } -?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php b/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php index 1aa134a20..b3dbbf0de 100644 --- a/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php +++ b/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php @@ -19,7 +19,7 @@ * . */ -#namespace Doctrine::ORM::Internal; +#namespace Doctrine\ORM\Internal; /** * The CommitOrderCalculator is used by the UnitOfWork to sort out the diff --git a/lib/Doctrine/ORM/Internal/CommitOrderNode.php b/lib/Doctrine/ORM/Internal/CommitOrderNode.php index 7a9575127..e7888345c 100644 --- a/lib/Doctrine/ORM/Internal/CommitOrderNode.php +++ b/lib/Doctrine/ORM/Internal/CommitOrderNode.php @@ -19,11 +19,11 @@ * . */ -#namespace Doctrine::ORM::Internal; +#namespace Doctrine\ORM\Internal; /** * A CommitOrderNode is a temporary wrapper around ClassMetadata instances - * that is used to sort the order of commits. + * that is used to sort the order of commits in a UnitOfWork. * * @since 2.0 * @author Roman Borschel @@ -53,7 +53,7 @@ class Doctrine_ORM_Internal_CommitOrderNode * Creates a new node. * * @param mixed $wrappedObj The object to wrap. - * @param Doctrine::ORM::Internal::CommitOrderCalculator $calc The calculator. + * @param Doctrine\ORM\Internal\CommitOrderCalculator $calc The calculator. */ public function __construct($wrappedObj, Doctrine_ORM_Internal_CommitOrderCalculator $calc) { @@ -155,7 +155,7 @@ class Doctrine_ORM_Internal_CommitOrderNode /** * Adds a directed dependency (an edge on the graph). "$this -before-> $other". * - * @param Doctrine::ORM::Internal::CommitOrderNode $node + * @param Doctrine\ORM\Internal\CommitOrderNode $node */ public function before(Doctrine_ORM_Internal_CommitOrderNode $node) { @@ -163,4 +163,3 @@ class Doctrine_ORM_Internal_CommitOrderNode } } -?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayDriver.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayDriver.php index afbfd1df1..662aac349 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayDriver.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayDriver.php @@ -19,7 +19,7 @@ * . */ -#namespace Doctrine::ORM::Internal::Hydration; +#namespace Doctrine\ORM\Internal\Hydration; /** * Defines an array hydration strategy. @@ -32,8 +32,7 @@ * @author Roman Borschel */ class Doctrine_ORM_Internal_Hydration_ArrayDriver -{ - +{ /** * */ @@ -66,8 +65,7 @@ class Doctrine_ORM_Internal_Hydration_ArrayDriver } } - public function addRelatedIndexedElement(array &$entity1, $property, array &$entity2, - $indexField) + public function addRelatedIndexedElement(array &$entity1, $property, array &$entity2, $indexField) { $entity1[$property][$entity2[$indexField]] = $entity2; } @@ -134,7 +132,7 @@ class Doctrine_ORM_Internal_Hydration_ArrayDriver * last seen instance of each Entity type. This is used for graph construction. * * @param array $resultPointers The result pointers. - * @param array|Collection $coll The element. + * @param array $coll The element. * @param boolean|integer $index Index of the element in the collection. * @param string $dqlAlias * @param boolean $oneToOne Whether it is a single-valued association or not. diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectDriver.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectDriver.php index 3b1d1836f..6165b23a0 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectDriver.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectDriver.php @@ -16,20 +16,21 @@ * * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see - * . + * . */ #namespace Doctrine\ORM\Internal\Hydration; /** - * Hydration strategy used for creating graphs of entities. + * Defines the object hydration strategy. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.phpdoctrine.org + * @link www.doctrine-project.org * @since 1.0 * @version $Revision$ * @author Konsta Vesterinen * @author Roman Borschel + * @internal All the methods in this class are performance-sentitive. */ class Doctrine_ORM_Internal_Hydration_ObjectDriver { @@ -38,15 +39,18 @@ class Doctrine_ORM_Internal_Hydration_ObjectDriver /** Memory for initialized relations */ private $_initializedRelations = array(); /** Null object */ - private $_nullObject; + //private $_nullObject; /** The EntityManager */ private $_em; + private $_uow; private $_metadataMap = array(); + private $_entityData = array(); public function __construct(Doctrine_ORM_EntityManager $em) { - $this->_nullObject = Doctrine_ORM_Internal_Null::$INSTANCE; + //$this->_nullObject = Doctrine_ORM_Internal_Null::$INSTANCE; $this->_em = $em; + $this->_uow = $this->_em->getUnitOfWork(); } public function getElementCollection($component) @@ -59,7 +63,7 @@ class Doctrine_ORM_Internal_Hydration_ObjectDriver public function getLastKey($coll) { // check needed because of mixed results. - // is_object instead of is_array because is_array is slow. + // is_object instead of is_array because is_array is slow on large arrays. if (is_object($coll)) { $coll->end(); return $coll->key(); @@ -71,10 +75,8 @@ class Doctrine_ORM_Internal_Hydration_ObjectDriver public function initRelatedCollection($entity, $name) { - //$class = get_class($entity); - $oid = spl_object_id($entity); + $oid = spl_object_hash($entity); $classMetadata = $this->_metadataMap[$oid]; - //$classMetadata = $this->_em->getClassMetadata(get_class($entity)); if ( ! isset($this->_initializedRelations[$oid][$name])) { $relation = $classMetadata->getAssociationMapping($name); $relatedClass = $this->_em->getClassMetadata($relation->getTargetEntityName()); @@ -83,6 +85,7 @@ class Doctrine_ORM_Internal_Hydration_ObjectDriver $coll->_setHydrationFlag(true); $classMetadata->getReflectionProperty($name)->setValue($entity, $coll); $this->_initializedRelations[$oid][$name] = true; + $this->_uow->setOriginalEntityProperty($oid, $name, $coll); } } @@ -93,91 +96,102 @@ class Doctrine_ORM_Internal_Hydration_ObjectDriver public function getNullPointer() { - return $this->_nullObject; + //TODO: Return VirtualProxy if lazy association + return null; } public function getElement(array $data, $className) { $entity = $this->_em->getUnitOfWork()->createEntity($className, $data); - - $this->_metadataMap[spl_object_id($entity)] = $this->_em->getClassMetadata($className); - + $oid = spl_object_hash($entity); + $this->_metadataMap[$oid] = $this->_em->getClassMetadata($className); return $entity; } - + + /** + * Adds an element to an indexed collection-valued property. + * + * @param $entity1 + * @param $property + * @param $entity2 + * @param $indexField + */ public function addRelatedIndexedElement($entity1, $property, $entity2, $indexField) { - $classMetadata1 = $this->_metadataMap[spl_object_id($entity1)]; - $classMetadata2 = $this->_metadataMap[spl_object_id($entity2)]; - //$classMetadata1 = $this->_em->getClassMetadata(get_class($entity1)); - //$classMetadata2 = $this->_em->getClassMetadata(get_class($entity2)); + $classMetadata1 = $this->_metadataMap[spl_object_hash($entity1)]; + $classMetadata2 = $this->_metadataMap[spl_object_hash($entity2)]; $indexValue = $classMetadata2->getReflectionProperty($indexField)->getValue($entity2); $classMetadata1->getReflectionProperty($property)->getValue($entity1)->add($entity2, $indexValue); } - + + /** + * Adds an element to a collection-valued property. + * + * @param $entity1 + * @param $property + * @param $entity2 + */ public function addRelatedElement($entity1, $property, $entity2) { - $classMetadata1 = $this->_metadataMap[spl_object_id($entity1)]; - //$classMetadata1 = $this->_em->getClassMetadata(get_class($entity1)); - $classMetadata1->getReflectionProperty($property) - ->getValue($entity1)->add($entity2); + $classMetadata1 = $this->_metadataMap[spl_object_hash($entity1)]; + $classMetadata1->getReflectionProperty($property)->getValue($entity1)->add($entity2); } - + + /** + * Sets a related element. + * + * @param $entity1 + * @param $property + * @param $entity2 + */ public function setRelatedElement($entity1, $property, $entity2) { - $classMetadata1 = $this->_metadataMap[spl_object_id($entity1)]; - //$classMetadata1 = $this->_em->getClassMetadata(get_class($entity1)); - $classMetadata1->getReflectionProperty($property) - ->setValue($entity1, $entity2); + $oid = spl_object_hash($entity1); + $classMetadata1 = $this->_metadataMap[$oid]; + $classMetadata1->getReflectionProperty($property)->setValue($entity1, $entity2); + $this->_uow->setOriginalEntityProperty($oid, $property, $entity2); $relation = $classMetadata1->getAssociationMapping($property); if ($relation->isOneToOne()) { $targetClass = $this->_em->getClassMetadata($relation->getTargetEntityName()); if ($relation->isOwningSide()) { // If there is an inverse mapping on the target class its bidirectional if ($targetClass->hasInverseAssociationMapping($property)) { - $refProp = $targetClass->getReflectionProperty( - $targetClass->getInverseAssociationMapping($fieldName) - ->getSourceFieldName()); - $refProp->setValue($entity2, $entity1); + $oid2 = spl_object_hash($entity2); + $sourceProp = $targetClass->getInverseAssociationMapping($fieldName)->getSourceFieldName(); + $targetClass->getReflectionProperty($sourceProp)->setValue($entity2, $entity1); + $this->_entityData[$oid2][$sourceProp] = $entity1; } } else { // for sure bidirectional, as there is no inverse side in unidirectional - $targetClass->getReflectionProperty($relation->getMappedByFieldName()) - ->setValue($entity2, $entity1); + $mappedByProp = $relation->getMappedByFieldName(); + $targetClass->getReflectionProperty($mappedByProp)->setValue($entity2, $entity1); + $this->_entityData[spl_object_hash($entity2)][$mappedByProp] = $entity1; } } } public function isIndexKeyInUse($entity, $assocField, $indexField) { - return $this->_metadataMap[spl_object_id($entity)]->getReflectionProperty($assocField) + return $this->_metadataMap[spl_object_hash($entity)]->getReflectionProperty($assocField) ->getValue($entity)->containsKey($indexField); - /*return $this->_em->getClassMetadata(get_class($entity))->getReflectionProperty($assocField) - ->getValue($entity)->containsKey($indexField);*/ } public function isFieldSet($entity, $field) { - return $this->_metadataMap[spl_object_id($entity)]->getReflectionProperty($field) + return $this->_metadataMap[spl_object_hash($entity)]->getReflectionProperty($field) ->getValue($entity) !== null; - /*return $this->_em->getClassMetadata(get_class($entity))->getReflectionProperty($field) - ->getValue($entity) !== null;*/ } public function getFieldValue($entity, $field) { - return $this->_metadataMap[spl_object_id($entity)]->getReflectionProperty($field) + return $this->_metadataMap[spl_object_hash($entity)]->getReflectionProperty($field) ->getValue($entity); - /*return $this->_em->getClassMetadata(get_class($entity))->getReflectionProperty($field) - ->getValue($entity);*/ } public function getReferenceValue($entity, $field) { - return $this->_metadataMap[spl_object_id($entity)]->getReflectionProperty($field) + return $this->_metadataMap[spl_object_hash($entity)]->getReflectionProperty($field) ->getValue($entity); - /*return $this->_em->getClassMetadata(get_class($entity))->getReflectionProperty($field) - ->getValue($entity);*/ } public function addElementToIndexedCollection($coll, $entity, $keyField) @@ -195,14 +209,15 @@ class Doctrine_ORM_Internal_Hydration_ObjectDriver * last seen instance of each Entity type. This is used for graph construction. * * @param array $resultPointers The result pointers. - * @param array|Collection $coll The element. + * @param Collection $coll The element. * @param boolean|integer $index Index of the element in the collection. * @param string $dqlAlias * @param boolean $oneToOne Whether it is a single-valued association or not. */ public function updateResultPointer(&$resultPointers, &$coll, $index, $dqlAlias, $oneToOne) { - if ($coll === $this->_nullObject) { + if ($coll === /*$this->_nullObject*/null) { + echo "HERE!"; unset($resultPointers[$dqlAlias]); // Ticket #1228 return; } @@ -230,10 +245,13 @@ class Doctrine_ORM_Internal_Hydration_ObjectDriver foreach ($this->_collections as $coll) { $coll->_takeSnapshot(); $coll->_setHydrationFlag(false); + $this->_uow->addManagedCollection($coll); } + // clean up $this->_collections = array(); $this->_initializedRelations = array(); $this->_metadataMap = array(); + $this->_entityData = array(); } } diff --git a/lib/Doctrine/ORM/Internal/Hydration/StandardHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/StandardHydrator.php index 87d945f55..e6de9a9d3 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/StandardHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/StandardHydrator.php @@ -22,7 +22,7 @@ #namespace Doctrine\ORM\Internal\Hydration; /** - * The hydrator has the tedious to process result sets returned by the database + * The hydrator has the tedious task to process result sets returned by the database * and turn them into useable structures. * * Runtime complexity: The following gives the overall number of iterations @@ -63,10 +63,6 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte * * This is method defines the core of Doctrine's object population algorithm. * - * @todo: Detailed documentation. Refactor (too long & nesting level). - * - * @param mixed $stmt - * @param array $tableAliases Array that maps table aliases (SQL alias => DQL alias) * @param array $aliasMap Array that maps DQL aliases to their components * (DQL alias => array( * 'metadata' => Table object, @@ -134,8 +130,6 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte // Initialize foreach ($this->_queryComponents as $dqlAlias => $component) { - // disable lazy-loading of related elements during hydration - //$component['metadata']->setAttribute('loadReferences', false); $identifierMap[$dqlAlias] = array(); $resultPointers[$dqlAlias] = array(); $idTemplate[$dqlAlias] = ''; @@ -152,7 +146,8 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte $result = $this->_gatherScalarRowData($result[0], $cache); return array_shift($result); } - + + $resultCounter = 0; // Process result set while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { // Evaluate HYDRATE_SCALAR @@ -178,12 +173,14 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte $result[] = array( $driver->getFieldValue($element, $field) => $element ); + ++$resultCounter; } else { $driver->addElementToIndexedCollection($result, $element, $field); } } else { if ($parserResult->isMixedQuery()) { $result[] = array($element); + ++$resultCounter; } else { $driver->addElementToCollection($result, $element); } @@ -226,7 +223,7 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte continue; } - // check the type of the relation (many or single-valued) + // Check the type of the relation (many or single-valued) if ( ! $relation->isOneToOne()) { // x-to-many relation $oneToOne = false; @@ -270,7 +267,6 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte $coll =& $baseElement[$relationAlias]; } else { $coll = $driver->getReferenceValue($baseElement, $relationAlias); - //$baseElement->_internalGetReference($relationAlias); } if ($coll !== null) { @@ -279,11 +275,9 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte } // Append scalar values to mixed result sets - //TODO: we dont need to count every time here, instead count with the loop if (isset($scalars)) { - $rowNumber = count($result) - 1; foreach ($scalars as $name => $value) { - $result[$rowNumber][$name] = $value; + $result[$resultCounter - 1][$name] = $value; } } } @@ -291,11 +285,6 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte $stmt->closeCursor(); $driver->flush(); - /*// re-enable lazy loading - foreach ($this->_queryComponents as $dqlAlias => $data) { - $data['metadata']->setAttribute('loadReferences', true); - }*/ - $e = microtime(true); echo 'Hydration took: ' . ($e - $s) . PHP_EOL; @@ -514,7 +503,6 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte } /** - * prepareValue * this method performs special data preparation depending on * the type of the given column * @@ -536,6 +524,7 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte * for the field can be skipped. Used i.e. during hydration to * improve performance on large and/or complex results. * @return mixed prepared value + * @todo Remove. Should be handled by the Type classes. No need for this switch stuff. */ public function prepareValue(Doctrine_ClassMetadata $class, $fieldName, $value, $typeHint = null) { diff --git a/lib/Doctrine/ORM/Internal/Null.php b/lib/Doctrine/ORM/Internal/Null.php index bb54986ef..c9799d2ee 100644 --- a/lib/Doctrine/ORM/Internal/Null.php +++ b/lib/Doctrine/ORM/Internal/Null.php @@ -18,20 +18,20 @@ * and is licensed under the LGPL. For more information, see * . */ - -#namespace Doctrine::ORM::Internal; + +#namespace Doctrine\ORM\Internal; /** - * Null class representing a null value that has been fetched from - * the database or a fetched, empty association. This is for internal use only. - * User code should never deal with this null object. - * - * Semantics are as follows: - * - * Regular PHP null : Value is undefined. When a field with that value is accessed - * and lazy loading is used the database is queried. - * - * Null object: Null valued of a field or empty association that has already been loaded. + * Null class representing a null value that has been fetched from + * the database or a fetched, empty association. This is for internal use only. + * User code should never deal with this null object. + * + * Semantics are as follows: + * + * Regular PHP null : Value is undefined. When a field with that value is accessed + * and lazy loading is used the database is queried. + * + * Null object: Null valued of a field or empty association that has already been loaded. * On access, the database is not queried. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL @@ -39,6 +39,7 @@ * @since 1.0 * @version $Revision: 4723 $ * @author Konsta Vesterinen + * @todo No longer needed? */ // static initializer Doctrine_ORM_Internal_Null::$INSTANCE = new Doctrine_ORM_Internal_Null(); diff --git a/lib/Doctrine/ORM/Mapping/AssociationMapping.php b/lib/Doctrine/ORM/Mapping/AssociationMapping.php index 0b6431e6b..27859e54f 100644 --- a/lib/Doctrine/ORM/Mapping/AssociationMapping.php +++ b/lib/Doctrine/ORM/Mapping/AssociationMapping.php @@ -28,7 +28,7 @@ * @since 2.0 * @todo Rename to AssociationMapping. */ -class Doctrine_ORM_Mapping_AssociationMapping implements Serializable +abstract class Doctrine_ORM_Mapping_AssociationMapping implements Serializable { const FETCH_MANUAL = 1; const FETCH_LAZY = 2; @@ -443,6 +443,8 @@ class Doctrine_ORM_Mapping_AssociationMapping implements Serializable { return false; } + + abstract public function lazyLoadFor($entity); /* Serializable implementation */ diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index 2170e3938..f910b9f9b 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -31,10 +31,8 @@ * * @author Roman Borschel * @since 2.0 - * @todo Rename to ClassDescriptor. */ -class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata - implements Doctrine_Common_Configurable, Serializable +class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata implements Serializable { /* The inheritance mapping types */ /** @@ -136,7 +134,7 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata /** * The field names of all fields that are part of the identifier/primary key - * of the described entity class. + * of the mapped entity class. * * @var array */ @@ -331,26 +329,49 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata $prop->setAccessible(true); $this->_reflectionProperties[$prop->getName()] = $prop; } - //$this->_isVirtualPropertyObject = is_subclass_of($entityName, 'Doctrine\Common\VirtualPropertyObject'); } + /** + * Gets the ReflectionClass instance of the mapped class. + * + * @return ReflectionClass + */ public function getReflectionClass() { return $this->_reflectionClass; } + /** + * Gets the ReflectionPropertys of the mapped class. + * + * @return array An array of ReflectionProperty instances. + */ public function getReflectionProperties() { return $this->_reflectionProperties; } + /** + * Gets a ReflectionProperty for a specific field of the mapped class. + * + * @param string $name + * @return ReflectionProperty + */ public function getReflectionProperty($name) { return $this->_reflectionProperties[$name]; } + public function getSingleIdReflectionProperty() + { + if ($this->_isIdentifierComposite) { + throw new Doctrine_Exception("getSingleIdReflectionProperty called on entity with composite key."); + } + return $this->_reflectionProperties[$this->_identifier[0]]; + } + /** - * getComponentName + * Gets the name of the mapped class. * * @return string */ @@ -360,11 +381,11 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata } /** - * Gets the name of the root class of the entity hierarchy. If the entity described - * by the ClassMetadata is not participating in a hierarchy, this is the same as the + * Gets the name of the root class of the mapped entity hierarchy. If the entity described + * by this ClassMetadata instance is not participating in a hierarchy, this is the same as the * name returned by {@link getClassName()}. * - * @return string + * @return string The name of the root class of the entity hierarchy. */ public function getRootClassName() { @@ -387,7 +408,7 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata } /** - * Check if the class has a composite identifier. + * Checks if the class has a composite identifier. * * @param string $fieldName The field name * @return boolean TRUE if the identifier is composite, FALSE otherwise. @@ -449,12 +470,10 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata if ( ! array_key_exists($name, $this->_tableOptions)) { throw new Doctrine_ClassMetadata_Exception("Unknown table option: '$name'."); } - return $this->_tableOptions[$name]; } /** - * getTableOptions * returns all table options. * * @return array all options and their values @@ -469,7 +488,7 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata * If the column name for the field cannot be found, the given field name * is returned. * - * @param string $alias The field name. + * @param string $fieldName The field name. * @return string The column name. */ public function getColumnName($fieldName) @@ -479,7 +498,7 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata } /** - * Gets the mapping of a (regular) fields that holds some data but not a + * Gets the mapping of a (regular) field that holds some data but not a * reference to another object. * * @param string $fieldName The field name. @@ -490,7 +509,6 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata if ( ! isset($this->_fieldMappings[$fieldName])) { throw Doctrine_MappingException::mappingNotFound($fieldName); } - return $this->_fieldMappings[$fieldName]; } @@ -499,14 +517,13 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata * * @param string $fieldName The field name that represents the association in * the object model. - * @return Doctrine::ORM::Mapping::AssociationMapping The mapping. + * @return Doctrine\ORM\Mapping\AssociationMapping The mapping. */ public function getAssociationMapping($fieldName) { if ( ! isset($this->_associationMappings[$fieldName])) { throw new Doctrine_Exception("Mapping not found: $fieldName"); } - return $this->_associationMappings[$fieldName]; } @@ -514,7 +531,7 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata * Gets the inverse association mapping for the given fieldname. * * @param string $mappedByFieldName - * @return Doctrine::ORM::Mapping::AssociationMapping The mapping. + * @return Doctrine\ORM\Mapping\AssociationMapping The mapping. */ public function getInverseAssociationMapping($mappedByFieldName) { @@ -608,7 +625,7 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata } /** - * Validates & completes the field mapping. Default values are applied here. + * Validates & completes the field mapping. * * @param array $mapping The field mapping to validated & complete. * @return array The validated and completed field mapping. @@ -689,59 +706,6 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata //... } - private $_entityIdentifiers = array(); - /** - * Gets the identifier of an entity. - * - * @param object $entity - * @return array Map of identifier field names to values. - */ - public function getEntityIdentifier($entity) - { - $oid = spl_object_id($entity); - if ( ! isset($this->_entityIdentifiers[$oid])) { - if ( ! $this->isIdentifierComposite()) { - $idField = $this->_identifier[0]; - $idValue = $this->_reflectionProperties[$idField]->getValue($entity); - if (isset($idValue)) { - //return array($idField => $idValue); - $this->_entityIdentifiers[$oid] = array($idField => $idValue); - } else { - return false; - } - //$this->_entityIdentifiers[$oid] = false; - } else { - $id = array(); - foreach ($this->getIdentifierFieldNames() as $idFieldName) { - $idValue = $this->_reflectionProperties[$idFieldName]->getValue($entity); - if (isset($idValue)) { - $id[$idFieldName] = $idValue; - } - } - //return $id; - $this->_entityIdentifiers[$oid] = $id; - } - } - return $this->_entityIdentifiers[$oid]; - } - - /** - * - * - * @param $entity - * @param $identifier - */ - public function setEntityIdentifier($entity, $identifier) - { - if (is_array($identifier)) { - foreach ($identifier as $fieldName => $value) { - $this->_reflectionProperties[$fieldName]->setValue($entity, $value); - } - } else { - $this->_reflectionProperties[$this->_identifier[0]]->setValue($entity, $identifier); - } - } - /** * Gets the identifier (primary key) field names of the class. * @@ -852,7 +816,7 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata } /** - * Sets the type of Id generator to use for this class. + * Sets the type of Id generator to use for the mapped class. */ public function setIdGeneratorType($generatorType) { @@ -860,9 +824,9 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata } /** - * Checks whether the class uses an Id generator. + * Checks whether the mapped class uses an Id generator. * - * @return boolean TRUE if the class uses an Id generator, FALSE otherwise. + * @return boolean TRUE if the mapped class uses an Id generator, FALSE otherwise. */ public function usesIdGenerator() { @@ -1010,7 +974,7 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata } /** - * Gets the inheritance mapping type used by the class. + * Gets the inheritance mapping type used by the mapped class. * * @return string */ @@ -1020,7 +984,7 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata } /** - * Sets the subclasses of the class. + * Sets the subclasses of the mapped class. * All entity classes that participate in a hierarchy and have subclasses * need to declare them this way. * @@ -1136,10 +1100,6 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata */ public function getInheritanceOption($name) { - /*if ( ! array_key_exists($name, $this->_inheritanceOptions)) { - throw new Doctrine_ClassMetadata_Exception("Unknown inheritance option: '$name'."); - }*/ - return $this->_inheritanceOptions[$name]; } @@ -1179,7 +1139,6 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata throw Doctrine_MappingException::invalidInheritanceOption($name); } } - $this->_inheritanceOptions[$name] = $value; } @@ -1350,7 +1309,6 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata public function serialize() { //$contents = get_object_vars($this); - /* @TODO How to handle $this->_em and $this->_parser ? */ //return serialize($contents); return ""; } @@ -1506,7 +1464,7 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata */ public function setCustomRepositoryClass($repositoryClassName) { - if ( ! is_subclass_of($repositoryClassName, 'Doctrine_EntityRepository')) { + if ( ! is_subclass_of($repositoryClassName, 'Doctrine\ORM\EntityRepository')) { throw new Doctrine_ClassMetadata_Exception("The custom repository must be a subclass" . " of Doctrine_EntityRepository."); } @@ -1640,37 +1598,15 @@ class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata { return $columnName === $this->_inheritanceOptions['discriminatorColumn']; } - - public function hasAttribute($name) - { - return isset($this->_attributes[$name]); - } - - public function getAttribute($name) - { - if ($this->hasAttribute($name)) { - return $this->_attributes[$name]; - } - } - - public function setAttribute($name, $value) - { - if ($this->hasAttribute($name)) { - $this->_attributes[$name] = $value; - } - } public function hasAssociation($fieldName) { return isset($this->_associationMappings[$fieldName]); } - /** - * - */ public function __toString() { - return spl_object_hash($this); + return __CLASS__ . '@' . spl_object_hash($this); } } diff --git a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php index c1785deba..b835b39c1 100644 --- a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php @@ -19,7 +19,7 @@ * . */ -#namespace Doctrine::ORM::Mapping; +#namespace Doctrine\ORM\Mapping; /** * Represents a one-to-many mapping. @@ -66,7 +66,7 @@ class Doctrine_ORM_Mapping_OneToManyMapping extends Doctrine_ORM_Mapping_Associa } /** - * Validates and completed the mapping. + * Validates and completes the mapping. * * @param array $mapping The mapping to validate and complete. * @return array The validated and completed mapping. @@ -106,6 +106,15 @@ class Doctrine_ORM_Mapping_OneToManyMapping extends Doctrine_ORM_Mapping_Associa return true; } + /** + * + * @param $entity + * @override + */ + public function lazyLoadFor($entity) + { + + } } diff --git a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php index f73f2e2e7..c17a5e5ae 100644 --- a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php @@ -130,10 +130,10 @@ class Doctrine_ORM_Mapping_OneToOneMapping extends Doctrine_ORM_Mapping_Associat /** * Lazy-loads the associated entity for a given entity. * - * @param Doctrine::ORM::Entity $entity + * @param Doctrine\ORM\Entity $entity * @return void */ - public function lazyLoadFor(Doctrine_ORM_Entity $entity) + public function lazyLoadFor($entity) { if ($entity->getClassName() != $this->_sourceClass->getClassName()) { //error? diff --git a/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php b/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php index a5fee9d94..f029832a7 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php @@ -41,7 +41,7 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister /** * Metadata object that descibes the mapping of the mapped entity class. * - * @var Doctrine_ClassMetadata + * @var Doctrine\ORM\Mapping\ClassMetadata */ protected $_classMetadata; @@ -53,26 +53,28 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister protected $_entityName; /** - * The Doctrine_Connection object that the database connection of this mapper. + * The Connection instance. * - * @var Doctrine::DBAL::Connection $conn + * @var Doctrine\DBAL\Connection $conn */ protected $_conn; /** - * The EntityManager. + * The EntityManager instance. * - * @var Doctrine::ORM::EntityManager + * @var Doctrine\ORM\EntityManager */ protected $_em; /** * Null object. */ - private $_nullObject; + //private $_nullObject; /** - * Constructs a new EntityPersister. + * Initializes a new instance of a class derived from AbstractEntityPersister + * that uses the given EntityManager and persists instances of the class described + * by the given class metadata descriptor. */ public function __construct(Doctrine_ORM_EntityManager $em, Doctrine_ORM_Mapping_ClassMetadata $classMetadata) { @@ -80,31 +82,36 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister $this->_entityName = $classMetadata->getClassName(); $this->_conn = $em->getConnection(); $this->_classMetadata = $classMetadata; - $this->_nullObject = Doctrine_ORM_Internal_Null::$INSTANCE; + //$this->_nullObject = Doctrine_ORM_Internal_Null::$INSTANCE; } /** * Inserts an entity. * - * @param Doctrine::ORM::Entity $entity The entity to insert. - * @return void + * @param Doctrine\ORM\Entity $entity The entity to insert. + * @return mixed */ public function insert($entity) { $insertData = array(); $this->_prepareData($entity, $insertData, true); $this->_conn->insert($this->_classMetadata->getTableName(), $insertData); + $idGen = $this->_em->getIdGenerator($this->_classMetadata->getClassName()); + if ($idGen->isPostInsertGenerator()) { + return $idGen->generate($entity); + } + return null; } /** * Updates an entity. * - * @param Doctrine::ORM::Entity $entity The entity to update. + * @param Doctrine\ORM\Entity $entity The entity to update. * @return void */ public function update(Doctrine_ORM_Entity $entity) { - $dataChangeSet = $entity->_getDataChangeSet(); + /*$dataChangeSet = $entity->_getDataChangeSet(); $referenceChangeSet = $entity->_getReferenceChangeSet(); foreach ($referenceChangeSet as $field => $change) { @@ -118,14 +125,14 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister } //... } - + */ //TODO: perform update } /** * Deletes an entity. * - * @param Doctrine::ORM::Entity $entity The entity to delete. + * @param Doctrine\ORM\Entity $entity The entity to delete. * @return void */ public function delete(Doctrine_ORM_Entity $entity) @@ -209,7 +216,6 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister /** * Callback that is invoked during the SQL construction process. - * @todo Move to ClassMetadata? */ public function getCustomJoins() { @@ -218,7 +224,6 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister /** * Callback that is invoked during the SQL construction process. - * @todo Move to ClassMetadata? */ public function getCustomFields() { @@ -226,24 +231,7 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister } /** - * Assumes that the keys of the given field array are field names and converts - * them to column names. - * - * @return array - */ - /*protected function _convertFieldToColumnNames(array $fields, Doctrine_ClassMetadata $class) - { - $converted = array(); - foreach ($fields as $fieldName => $value) { - $converted[$class->getColumnName($fieldName)] = $value; - } - - return $converted; - }*/ - - /** - * Returns an array of modified fields and values with data preparation - * adds column aggregation inheritance and converts Records into primary key values + * Prepares all the entity data for insertion into the database. * * @param array $array * @return void @@ -255,7 +243,7 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister $type = $this->_classMetadata->getTypeOfField($field); $columnName = $this->_classMetadata->getColumnName($field); - if ($newVal === Doctrine_ORM_Internal_Null::$INSTANCE) { + if (is_null($newVal)) { $result[$columnName] = null; } else if (is_object($newVal)) { $assocMapping = $this->_classMetadata->getAssociationMapping($field); @@ -301,336 +289,7 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister $result[$discColumn] = array_search($this->_entityName, $discMap); } } - - - - ############################################################# - - # The following is old code that needs to be removed/ported - - - /** - * deletes all related composites - * this method is always called internally when a record is deleted - * - * @throws PDOException if something went wrong at database level - * @return void - */ - protected function _deleteComposites(Doctrine_ORM_Entity $record) - { - $classMetadata = $this->_classMetadata; - foreach ($classMetadata->getRelations() as $fk) { - if ($fk->isComposite()) { - $obj = $record->get($fk->getAlias()); - if ($obj instanceof Doctrine_ORM_Entity && - $obj->_state() != Doctrine_ORM_Entity::STATE_LOCKED) { - $obj->delete($this->_mapper->getConnection()); - } - } - } - } - - /** - * Returns the connection the mapper is currently using. - * - * @return Doctrine_Connection|null The connection object. - */ - public function getConnection() - { - return $this->_conn; - } - - public function getEntityManager() - { - return $this->_em; - } - - /** - * getComponentName - * - * @return void - * @deprecated Use getMappedClassName() - */ - public function getComponentName() - { - return $this->_domainClassName; - } - - /** - * Saves an entity. - * - * @param Doctrine_Entity $record The entity to save. - * @param Doctrine_Connection $conn The connection to use. Will default to the mapper's - * connection. - */ - public function save(Doctrine_ORM_Entity $record) - { - if ( ! ($record instanceof $this->_domainClassName)) { - throw new Doctrine_Mapper_Exception("Mapper of type " . $this->_domainClassName . " - can't save instances of type" . get_class($record) . "."); - } - - if ($conn === null) { - $conn = $this->_conn; - } - - $state = $record->_state(); - if ($state === Doctrine_ORM_Entity::STATE_LOCKED) { - return false; - } - - $record->_state(Doctrine_ORM_Entity::STATE_LOCKED); - - try { - $conn->beginInternalTransaction(); - $saveLater = $this->_saveRelated($record); - - $record->_state($state); - - if ($record->isValid()) { - $this->_insertOrUpdate($record); - } else { - $conn->getTransaction()->addInvalid($record); - } - - $state = $record->_state(); - $record->_state(Doctrine_ORM_Entity::STATE_LOCKED); - - foreach ($saveLater as $fk) { - $alias = $fk->getAlias(); - if ($record->hasReference($alias)) { - $obj = $record->$alias; - // check that the related object is not an instance of Doctrine_Null - if ( ! ($obj instanceof Doctrine_Null)) { - $obj->save($conn); - } - } - } - - // save the MANY-TO-MANY associations - $this->saveAssociations($record); - // reset state - $record->_state($state); - $conn->commit(); - } catch (Exception $e) { - $conn->rollback(); - throw $e; - } - - return true; - } - - /** - * Inserts or updates an entity, depending on it's state. - * - * @param Doctrine_Entity $record The entity to insert/update. - */ - protected function _insertOrUpdate(Doctrine_ORM_Entity $record) - { - //$record->preSave(); - //$this->notifyEntityListeners($record, 'preSave', Doctrine_Event::RECORD_SAVE); - - switch ($record->_state()) { - case Doctrine_ORM_Entity::STATE_TDIRTY: - $this->_insert($record); - break; - case Doctrine_ORM_Entity::STATE_DIRTY: - case Doctrine_ORM_Entity::STATE_PROXY: - $this->_update($record); - break; - case Doctrine_ORM_Entity::STATE_CLEAN: - case Doctrine_ORM_Entity::STATE_TCLEAN: - // do nothing - break; - } - - //$record->postSave(); - //$this->notifyEntityListeners($record, 'postSave', Doctrine_Event::RECORD_SAVE); - } - - /** - * saves the given record - * - * @param Doctrine_Entity $record - * @return void - */ - public function saveSingleRecord(Doctrine_ORM_Entity $record) - { - $this->_insertOrUpdate($record); - } - - /** - * saves all related records to $record - * - * @throws PDOException if something went wrong at database level - * @param Doctrine_Entity $record - */ - protected function _saveRelated(Doctrine_ORM_Entity $record) - { - $saveLater = array(); - foreach ($record->_getReferences() as $k => $v) { - $rel = $record->getTable()->getRelation($k); - - $local = $rel->getLocal(); - $foreign = $rel->getForeign(); - - if ($rel instanceof Doctrine_Relation_ForeignKey) { - $saveLater[$k] = $rel; - } else if ($rel instanceof Doctrine_Relation_LocalKey) { - // ONE-TO-ONE relationship - $obj = $record->get($rel->getAlias()); - - // Protection against infinite function recursion before attempting to save - if ($obj instanceof Doctrine_ORM_Entity && $obj->isModified()) { - $obj->save(); - - /** Can this be removed? - $id = array_values($obj->identifier()); - - foreach ((array) $rel->getLocal() as $k => $field) { - $record->set($field, $id[$k]); - } - */ - } - } - } - - return $saveLater; - } - - /** - * saveAssociations - * - * this method takes a diff of one-to-many / many-to-many original and - * current collections and applies the changes - * - * for example if original many-to-many related collection has records with - * primary keys 1,2 and 3 and the new collection has records with primary keys - * 3, 4 and 5, this method would first destroy the associations to 1 and 2 and then - * save new associations to 4 and 5 - * - * @throws Doctrine_Connection_Exception if something went wrong at database level - * @param Doctrine_Entity $record - * @return void - */ - public function saveAssociations(Doctrine_ORM_Entity $record) - { - foreach ($record->_getReferences() as $relationName => $relatedObject) { - if ($relatedObject === Doctrine_Null::$INSTANCE) { - continue; - } - $rel = $record->getTable()->getRelation($relationName); - - if ($rel instanceof Doctrine_Relation_Association) { - $relatedObject->save($this->_conn); - $assocTable = $rel->getAssociationTable(); - - foreach ($relatedObject->getDeleteDiff() as $r) { - $query = 'DELETE FROM ' . $assocTable->getTableName() - . ' WHERE ' . $rel->getForeign() . ' = ?' - . ' AND ' . $rel->getLocal() . ' = ?'; - // FIXME: composite key support - $ids1 = $r->identifier(); - $id1 = count($ids1) > 0 ? array_pop($ids1) : null; - $ids2 = $record->identifier(); - $id2 = count($ids2) > 0 ? array_pop($ids2) : null; - $this->_conn->execute($query, array($id1, $id2)); - } - - $assocMapper = $this->_conn->getMapper($assocTable->getComponentName()); - foreach ($relatedObject->getInsertDiff() as $r) { - $assocRecord = $assocMapper->create(); - $assocRecord->set($assocTable->getFieldName($rel->getForeign()), $r); - $assocRecord->set($assocTable->getFieldName($rel->getLocal()), $record); - $assocMapper->save($assocRecord); - } - } - } - } - - /** - * Updates an entity. - * - * @param Doctrine_Entity $record record to be updated - * @return boolean whether or not the update was successful - * @todo Move to Doctrine_Table (which will become Doctrine_Mapper). - */ - protected function _update(Doctrine_ORM_Entity $record) - { - $record->preUpdate(); - $this->notifyEntityListeners($record, 'preUpdate', Doctrine_Event::RECORD_UPDATE); - - $table = $this->_classMetadata; - $this->_doUpdate($record); - - $record->postUpdate(); - $this->notifyEntityListeners($record, 'postUpdate', Doctrine_Event::RECORD_UPDATE); - - return true; - } abstract protected function _doUpdate(Doctrine_ORM_Entity $entity); - - /** - * Inserts an entity. - * - * @param Doctrine_Entity $record record to be inserted - * @return boolean - */ - protected function _insert(Doctrine_ORM_Entity $record) - { - //$record->preInsert(); - //$this->notifyEntityListeners($record, 'preInsert', Doctrine_Event::RECORD_INSERT); - - $this->_doInsert($record); - $this->addRecord($record); - - //$record->postInsert(); - //$this->notifyEntityListeners($record, 'postInsert', Doctrine_Event::RECORD_INSERT); - - return true; - } - abstract protected function _doInsert(Doctrine_ORM_Entity $entity); - - /** - * Deletes given entity and all it's related entities. - * - * Triggered Events: onPreDelete, onDelete. - * - * @return boolean true on success, false on failure - * @throws Doctrine_Mapper_Exception - */ - public function delete_old(Doctrine_ORM_Entity $record) - { - if ( ! $record->exists()) { - return false; - } - - if ( ! ($record instanceof $this->_domainClassName)) { - throw new Doctrine_Mapper_Exception("Mapper of type " . $this->_domainClassName . " - can't save instances of type" . get_class($record) . "."); - } - - if ($conn == null) { - $conn = $this->_conn; - } - - $record->preDelete(); - $this->notifyEntityListeners($record, 'preDelete', Doctrine_Event::RECORD_DELETE); - - $table = $this->_classMetadata; - - $state = $record->_state(); - $record->_state(Doctrine_ORM_Entity::STATE_LOCKED); - - $this->_doDelete($record); - - $record->postDelete(); - $this->notifyEntityListeners($record, 'postDelete', Doctrine_Event::RECORD_DELETE); - - return true; - } - - } diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index 1e9776a22..a296ad1b0 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -30,7 +30,7 @@ * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version $Revision$ - * @link www.phpdoctrine.org + * @link www.doctrine-project.org * @since 2.0 */ class Doctrine_ORM_Persisters_StandardEntityPersister extends Doctrine_ORM_Persisters_AbstractEntityPersister @@ -40,10 +40,8 @@ class Doctrine_ORM_Persisters_StandardEntityPersister extends Doctrine_ORM_Persi */ protected function _doDelete(Doctrine_ORM_Entity $record) { - $conn = $this->_conn; - $metadata = $this->_classMetadata; - try { - $conn->beginInternalTransaction(); + /*try { + $this->_conn->beginInternalTransaction(); $this->_deleteComposites($record); $record->_state(Doctrine_ORM_Entity::STATE_TDIRTY); @@ -57,18 +55,16 @@ class Doctrine_ORM_Persisters_StandardEntityPersister extends Doctrine_ORM_Persi } catch (Exception $e) { $conn->rollback(); throw $e; - } + }*/ } /** - * Inserts a single entity into the database, without any related entities. + * Inserts a single entity into the database. * - * @param Doctrine_Entity $record The entity to insert. + * @param Doctrine\ORM\Entity $entity The entity to insert. */ protected function _doInsert(Doctrine_ORM_Entity $record) { - $conn = $this->_conn; - $fields = $record->getPrepared(); if (empty($fields)) { return false; diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 3b92f52f3..22585bc45 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -32,11 +32,8 @@ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.phpdoctrine.org * @since 2.0 - * @version $Revision: 4947 $ - * @author Konsta Vesterinen + * @version $Revision$ * @author Roman Borschel - * @todo Rename: Doctrine::ORM::UnitOfWork. - * @todo Turn connection exceptions into UnitOfWorkExceptions. */ class Doctrine_ORM_UnitOfWork { @@ -89,6 +86,13 @@ class Doctrine_ORM_UnitOfWork */ protected $_identityMap = array(); + /** + * Map of all identifiers. Keys are object ids. + * + * @var array + */ + private $_entityIdentifiers = array(); + /** * Map of the original entity data of entities fetched from the database. * Keys are object ids. This is used for calculating changesets at commit time. @@ -199,7 +203,7 @@ class Doctrine_ORM_UnitOfWork public function commit() { // Compute changes in managed entities - $this->_computeDataChangeSet(); + $this->computeDataChangeSet(); if (empty($this->_newEntities) && empty($this->_deletedEntities) && @@ -244,7 +248,7 @@ class Doctrine_ORM_UnitOfWork */ public function getDataChangeSet($entity) { - $oid = spl_object_id($entity); + $oid = spl_object_hash($entity); if (isset($this->_dataChangeSets[$oid])) { return $this->_dataChangeSets[$oid]; } @@ -261,7 +265,7 @@ class Doctrine_ORM_UnitOfWork * map are computed. * @return void */ - private function _computeDataChangeSet(array $entities = null) + public function computeDataChangeSet(array $entities = null) { $entitySet = array(); if ( ! is_null($entities)) { @@ -279,7 +283,7 @@ class Doctrine_ORM_UnitOfWork foreach ($entitySet as $className => $entities) { $class = $this->_em->getClassMetadata($className); foreach ($entities as $entity) { - $oid = spl_object_id($entity); + $oid = spl_object_hash($entity); if ($this->getEntityState($entity) == self::STATE_MANAGED) { if ( ! $class->isInheritanceTypeNone()) { $class = $this->_em->getClassMetadata(get_class($entity)); @@ -317,7 +321,7 @@ class Doctrine_ORM_UnitOfWork /** * Executes all entity insertions for entities of the specified type. * - * @param Doctrine::ORM::Mapping::ClassMetadata $class + * @param Doctrine\ORM\Mapping\ClassMetadata $class */ private function _executeInserts($class) { @@ -329,11 +333,14 @@ class Doctrine_ORM_UnitOfWork $persister = $this->_em->getEntityPersister($className); foreach ($this->_newEntities as $entity) { if (get_class($entity) == $className) { - $persister->insert($entity); - if ($class->isIdGeneratorIdentity()) { - $id = $this->_em->getIdGenerator($class->getIdGeneratorType()); - $class->setEntityIdentifier($entity, $id); - $this->_entityStates[spl_object_id($oid)] = self::STATE_MANAGED; + $returnVal = $persister->insert($entity); + if ( ! is_null($returnVal)) { + $oid = spl_object_hash($entity); + $class->getReflectionProperty($class->getSingleIdentifierFieldName()) + ->setValue($entity, $returnVal); + $this->_entityIdentifiers[$oid] = array($returnVal); + $this->_entityStates[$oid] = self::STATE_MANAGED; + $this->addToIdentityMap($entity); } } } @@ -342,7 +349,7 @@ class Doctrine_ORM_UnitOfWork /** * Executes all entity updates for entities of the specified type. * - * @param Doctrine::ORM::Mapping::ClassMetadata $class + * @param Doctrine\ORM\Mapping\ClassMetadata $class */ private function _executeUpdates($class) { @@ -358,7 +365,7 @@ class Doctrine_ORM_UnitOfWork /** * Executes all entity deletions for entities of the specified type. * - * @param Doctrine::ORM::Mapping::ClassMetadata $class + * @param Doctrine\ORM\Mapping\ClassMetadata $class */ private function _executeDeletions($class) { @@ -441,11 +448,8 @@ class Doctrine_ORM_UnitOfWork */ public function registerNew($entity) { - $oid = spl_object_id($entity); + $oid = spl_object_hash($entity); - /*if ( ! $entity->_identifier()) { - throw new Doctrine_Connection_Exception("Entity without identity cant be registered as new."); - }*/ if (isset($this->_dirtyEntities[$oid])) { throw new Doctrine_Connection_Exception("Dirty object can't be registered as new."); } @@ -457,7 +461,7 @@ class Doctrine_ORM_UnitOfWork } $this->_newEntities[$oid] = $entity; - if ($this->_em->getClassMetadata(get_class($entity))->getEntityIdentifier($entity)) { + if (isset($this->_entityIdentifiers[$oid])) { $this->addToIdentityMap($entity); } } @@ -465,36 +469,25 @@ class Doctrine_ORM_UnitOfWork /** * Checks whether an entity is registered as new on the unit of work. * - * @param Doctrine_ORM_Entity $entity + * @param Doctrine\ORM\Entity $entity * @return boolean * @todo Rename to isScheduledForInsert(). */ public function isRegisteredNew($entity) { - return isset($this->_newEntities[spl_object_id($entity)]); - } - - /** - * Registers a clean entity. - * The entity is simply put into the identity map. - * - * @param object $entity - */ - public function registerClean($entity) - { - $this->addToIdentityMap($entity); + return isset($this->_newEntities[spl_object_hash($entity)]); } /** * Registers a dirty entity. * - * @param Doctrine::ORM::Entity $entity + * @param Doctrine\ORM\Entity $entity * @todo Rename to scheduleForUpdate(). */ public function registerDirty($entity) { - $oid = spl_object_id($entity); - if ( ! $entity->_identifier()) { + $oid = spl_object_hash($entity); + if ( ! isset($this->_entityIdentifiers[$oid])) { throw new Doctrine_Exception("Entity without identity " . "can't be registered as dirty."); } @@ -518,7 +511,7 @@ class Doctrine_ORM_UnitOfWork */ public function isRegisteredDirty($entity) { - return isset($this->_dirtyEntities[spl_object_id($entity)]); + return isset($this->_dirtyEntities[spl_object_hash($entity)]); } /** @@ -528,12 +521,13 @@ class Doctrine_ORM_UnitOfWork */ public function registerDeleted($entity) { - $oid = spl_object_id($entity); + $oid = spl_object_hash($entity); if ( ! $this->isInIdentityMap($entity)) { return; } $this->removeFromIdentityMap($entity); + $className = get_class($entity); if (isset($this->_newEntities[$oid])) { unset($this->_newEntities[$oid]); @@ -552,13 +546,13 @@ class Doctrine_ORM_UnitOfWork * Checks whether an entity is registered as removed/deleted with the unit * of work. * - * @param Doctrine::ORM::Entity $entity + * @param Doctrine\ORM\Entity $entity * @return boolean * @todo Rename to isScheduledForDelete(). */ public function isRegisteredRemoved($entity) { - return isset($this->_deletedEntities[spl_object_id($entity)]); + return isset($this->_deletedEntities[spl_object_hash($entity)]); } /** @@ -570,25 +564,26 @@ class Doctrine_ORM_UnitOfWork */ public function detach($entity) { - if ($this->isInIdentityMap($entity)) { - $this->removeFromIdentityMap($entity); - } + $oid = spl_object_hash($entity); + $this->removeFromIdentityMap($entity); + unset($this->_newEntities[$oid], $this->_dirtyEntities[$oid], + $this->_deletedEntities[$oid], $this->_entityIdentifiers[$oid], + $this->_entityStates[$oid]); } /** * Enter description here... * - * @param Doctrine_ORM_Entity $entity + * @param Doctrine\ORM\Entity $entity * @return unknown * @todo Rename to isScheduled() */ public function isEntityRegistered($entity) { - $oid = spl_object_id($entity); + $oid = spl_object_hash($entity); return isset($this->_newEntities[$oid]) || isset($this->_dirtyEntities[$oid]) || - isset($this->_deletedEntities[$oid]) || - $this->isInIdentityMap($entity); + isset($this->_deletedEntities[$oid]); } /** @@ -606,10 +601,16 @@ class Doctrine_ORM_UnitOfWork $numDetached = 0; if ($entityName !== null && isset($this->_identityMap[$entityName])) { $numDetached = count($this->_identityMap[$entityName]); + foreach ($this->_identityMap[$entityName] as $entity) { + $this->detach($entity); + } $this->_identityMap[$entityName] = array(); } else { $numDetached = count($this->_identityMap); $this->_identityMap = array(); + $this->_newEntities = array(); + $this->_dirtyEntities = array(); + $this->_deletedEntities = array(); } return $numDetached; @@ -627,9 +628,9 @@ class Doctrine_ORM_UnitOfWork public function addToIdentityMap($entity) { $classMetadata = $this->_em->getClassMetadata(get_class($entity)); - $idHash = $this->getIdentifierHash($classMetadata->getEntityIdentifier($entity)); + $idHash = $this->getIdentifierHash($this->_entityIdentifiers[spl_object_hash($entity)]); if ($idHash === '') { - throw new Doctrine_Exception("Entity with oid '" . spl_object_id($entity) + throw new Doctrine_Exception("Entity with oid '" . spl_object_hash($entity) . "' has no identity and therefore can't be added to the identity map."); } $className = $classMetadata->getRootClassName(); @@ -648,23 +649,22 @@ class Doctrine_ORM_UnitOfWork */ public function getEntityState($entity) { - $oid = spl_object_id($entity); - return isset($this->_entityStates[$oid]) ? $this->_entityStates[$oid] : - self::STATE_NEW; + $oid = spl_object_hash($entity); + return isset($this->_entityStates[$oid]) ? $this->_entityStates[$oid] : self::STATE_NEW; } /** * Removes an entity from the identity map. * - * @param Doctrine_ORM_Entity $entity - * @return unknown + * @param Doctrine\ORM\Entity $entity + * @return boolean */ public function removeFromIdentityMap($entity) { $classMetadata = $this->_em->getClassMetadata(get_class($entity)); - $idHash = $this->getIdentifierHash($classMetadata->getEntityIdentifier($entity)); + $idHash = $this->getIdentifierHash($this->_entityIdentifiers[spl_object_hash($entity)]); if ($idHash === '') { - throw new Doctrine_Exception("Entity with oid '" . spl_object_id($entity) + throw new Doctrine_Exception("Entity with oid '" . spl_object_hash($entity) . "' has no identity and therefore can't be removed from the identity map."); } $className = $classMetadata->getRootClassName(); @@ -681,7 +681,7 @@ class Doctrine_ORM_UnitOfWork * * @param string $idHash * @param string $rootClassName - * @return Doctrine::ORM::Entity + * @return Doctrine\ORM\Entity */ public function getByIdHash($idHash, $rootClassName) { @@ -729,8 +729,12 @@ class Doctrine_ORM_UnitOfWork */ public function isInIdentityMap($entity) { + $oid = spl_object_hash($entity); + if ( ! isset($this->_entityIdentifiers[$oid])) { + return false; + } $classMetadata = $this->_em->getClassMetadata(get_class($entity)); - $idHash = $this->getIdentifierHash($classMetadata->getEntityIdentifier($entity)); + $idHash = $this->getIdentifierHash($this->_entityIdentifiers[$oid]); if ($idHash === '') { return false; } @@ -765,15 +769,15 @@ class Doctrine_ORM_UnitOfWork $this->_doSave($entity, $visited, $insertNow); if ( ! empty($insertNow)) { // We have no choice. This means that there are new entities - // with an IDENTITY column key generation. - $this->_computeDataChangeSet($insertNow); + // with an IDENTITY column key generation strategy. + $this->computeDataChangeSet($insertNow); $commitOrder = $this->_getCommitOrder($insertNow); foreach ($commitOrder as $class) { $this->_executeInserts($class); } // remove them from _newEntities $this->_newEntities = array_diff_key($this->_newEntities, $insertNow); - $this->_dataChangeSets = array(); + $this->_dataChangeSets = array_diff_key($this->_dataChangeSets, $insertNow); } } @@ -787,7 +791,7 @@ class Doctrine_ORM_UnitOfWork */ private function _doSave($entity, array &$visited, array &$insertNow) { - $oid = spl_object_id($entity); + $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } @@ -800,12 +804,16 @@ class Doctrine_ORM_UnitOfWork // nothing to do break; case self::STATE_NEW: - $result = $this->_em->getIdGenerator($class->getClassName())->generate($entity); - if ($result == Doctrine_ORM_Id_AbstractIdGenerator::POST_INSERT_INDICATOR) { + $idGen = $this->_em->getIdGenerator($class->getClassName()); + if ($idGen->isPostInsertGenerator()) { $insertNow[$oid] = $entity; } else { - $class->setEntityIdentifier($entity, $result); + $idValue = $idGen->generate($entity); + $this->_entityIdentifiers[$oid] = array($idValue); $this->_entityStates[$oid] = self::STATE_MANAGED; + if ( ! $idGen instanceof Doctrine_ORM_Id_Assigned) { + $class->getSingleIdReflectionProperty()->setValue($entity, $idValue); + } } $this->registerNew($entity); break; @@ -844,19 +852,18 @@ class Doctrine_ORM_UnitOfWork /** * Enter description here... * - * @param Doctrine_ORM_Entity $entity + * @param Doctrine\ORM\Entity $entity * @param array $visited */ private function _doDelete($entity, array &$visited) { - $oid = spl_object_id($entity); + $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited - //$class = $entity->getClass(); switch ($this->getEntityState($entity)) { case self::STATE_NEW: case self::STATE_DELETED: @@ -951,47 +958,43 @@ class Doctrine_ORM_UnitOfWork } /** - * Creates an entity. Used for reconstitution as well as initial creation. + * Creates an entity. Used for reconstitution of entities during hydration. * * @param string $className The name of the entity class. * @param array $data The data for the entity. * @return Doctrine\ORM\Entity + * @internal Performance-sensitive method. */ - public function createEntity($className, array $data, Doctrine_Query $query = null) + public function createEntity($className, array $data, $query = null) { $className = $this->_inferCorrectClassName($data, $className); $classMetadata = $this->_em->getClassMetadata($className); - if ( ! empty($data)) { + + $id = array(); + if ($classMetadata->isIdentifierComposite()) { $identifierFieldNames = $classMetadata->getIdentifier(); - $isNew = false; foreach ($identifierFieldNames as $fieldName) { - if ( ! isset($data[$fieldName])) { - // id field not found return new entity - $isNew = true; - break; - } $id[] = $data[$fieldName]; } - - if ($isNew) { - $entity = new $className; - } else { - $idHash = $this->getIdentifierHash($id); - $entity = $this->tryGetByIdHash($idHash, $classMetadata->getRootClassName()); - if ($entity) { - $this->_mergeData($entity, $data, $classMetadata/*, $query->getHint('doctrine.refresh')*/); - return $entity; - } else { - $entity = new $className; - $this->_mergeData($entity, $data, $classMetadata, true); - $this->addToIdentityMap($entity); - } - } + $idHash = $this->getIdentifierHash($id); + } else { + $id = array($data[$classMetadata->getSingleIdentifierFieldName()]); + $idHash = $id[0]; + } + $entity = $this->tryGetByIdHash($idHash, $classMetadata->getRootClassName()); + if ($entity) { + $oid = spl_object_hash($entity); + $this->_mergeData($entity, $data, $classMetadata/*, $query->getHint('doctrine.refresh')*/); + return $entity; } else { $entity = new $className; + $oid = spl_object_hash($entity); + $this->_mergeData($entity, $data, $classMetadata, true); + $this->_entityIdentifiers[$oid] = $id; + $this->addToIdentityMap($entity); } - $this->_originalEntityData[spl_object_id($entity)] = $data; + $this->_originalEntityData[$oid] = $data; return $entity; } @@ -1011,11 +1014,11 @@ class Doctrine_ORM_UnitOfWork $class->getReflectionProperty($field)->setValue($entity, $value); } } else { - $oid = spl_object_id($entity); + $oid = spl_object_hash($entity); foreach ($data as $field => $value) { $currentValue = $class->getReflectionProperty($field)->getValue($entity); - if ( ! isset($this->_originalEntityData[$oid]) || - $currentValue == $this->_originalEntityData[$oid]) { + if ( ! isset($this->_originalEntityData[$oid][$field]) || + $currentValue == $this->_originalEntityData[$oid][$field]) { $class->getReflectionProperty($field)->setValue($entity, $value); } } @@ -1058,6 +1061,61 @@ class Doctrine_ORM_UnitOfWork { return $this->_identityMap; } + + /** + * Gets the original data of an entity. The original data is the data that was + * present at the time the entity was reconstituted from the database. + * + * @param object $entity + * @return array + */ + public function getOriginalEntityData($entity) + { + $oid = spl_object_hash($entity); + if (isset($this->_originalEntityData[$oid])) { + return $this->_originalEntityData[$oid]; + } + return array(); + } + + /** + * INTERNAL: + * For hydration purposes only. + * + * Sets a property of the original data array of an entity. + * + * @param string $oid + * @param string $property + * @param mixed $value + */ + public function setOriginalEntityProperty($oid, $property, $value) + { + $this->_originalEntityData[$oid][$property] = $value; + } + + /** + * INTERNAL: + * For hydration purposes only. + * + * Adds a managed collection to the UnitOfWork. + * + * @param Doctrine\ORM\Collection $coll + */ + public function addManagedCollection(Doctrine_ORM_Collection $coll) + { + + } + + /** + * Gets the identifier of an entity. + * + * @param object $entity + * @return array The identifier values. + */ + public function getEntityIdentifier($entity) + { + return $this->_entityIdentifiers[spl_object_hash($entity)]; + } } diff --git a/tests/Orm/Hydration/BasicHydrationTest.php b/tests/Orm/Hydration/BasicHydrationTest.php index aebb777f5..a80e91add 100644 --- a/tests/Orm/Hydration/BasicHydrationTest.php +++ b/tests/Orm/Hydration/BasicHydrationTest.php @@ -871,6 +871,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase $this->assertEquals(3, count($result[0]->boards)); $this->assertTrue(isset($result[1]->boards)); $this->assertEquals(1, count($result[1]->boards)); + } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_SCALAR) { //... } diff --git a/tests/Orm/UnitOfWorkTest.php b/tests/Orm/UnitOfWorkTest.php index 9f9a3f635..5c4dc01ae 100644 --- a/tests/Orm/UnitOfWorkTest.php +++ b/tests/Orm/UnitOfWorkTest.php @@ -2,7 +2,8 @@ require_once 'lib/DoctrineTestInit.php'; require_once 'lib/mocks/Doctrine_EntityManagerMock.php'; require_once 'lib/mocks/Doctrine_ConnectionMock.php'; -require_once 'lib/mocks/Doctrine_ClassMetadataMock.php'; +require_once 'lib/mocks/Doctrine_UnitOfWorkMock.php'; +require_once 'lib/mocks/Doctrine_IdentityIdGeneratorMock.php'; /** * UnitOfWork tests. @@ -12,16 +13,11 @@ require_once 'lib/mocks/Doctrine_ClassMetadataMock.php'; class Orm_UnitOfWorkTest extends Doctrine_OrmTestCase { private $_unitOfWork; - private $_user; // Mocks // Provides a sequence mock to the UnitOfWork private $_connectionMock; - // The sequence mock - private $_idGeneratorMock; - // The persister mock used by the UnitOfWork - private $_persisterMock; // The EntityManager mock that provides the mock persister private $_emMock; @@ -30,106 +26,141 @@ class Orm_UnitOfWorkTest extends Doctrine_OrmTestCase $this->_connectionMock = new Doctrine_ConnectionMock(array()); $this->_emMock = Doctrine_EntityManagerMock::create($this->_connectionMock, "uowMockEm"); - $this->_idGeneratorMock = new Doctrine_SequenceMock($this->_emMock); - $this->_emMock->setIdGenerator('ForumUser', $this->_idGeneratorMock); - $this->_emMock->setIdGenerator('ForumAvatar', $this->_idGeneratorMock); - - $this->_persisterMock = new Doctrine_EntityPersisterMock( - $this->_emMock, $this->_emMock->getClassMetadata("ForumUser")); - $this->_emMock->setEntityPersister($this->_persisterMock); - $this->_emMock->activate(); // SUT - $this->_unitOfWork = $this->_emMock->getUnitOfWork(); - - $this->_user = new ForumUser(); - $this->_user->id = 1; - $this->_user->username = 'romanb'; + $this->_unitOfWork = new Doctrine_UnitOfWorkMock($this->_emMock); + $this->_emMock->setUnitOfWork($this->_unitOfWork); } protected function tearDown() { - //$this->_user->free(); } - /* Basic registration tests */ - - public function testRegisterNew() - { - // registerNew() is normally called in save()/persist() - $this->_unitOfWork->registerNew($this->_user); - $this->assertTrue($this->_unitOfWork->isRegisteredNew($this->_user)); - $this->assertTrue($this->_unitOfWork->isInIdentityMap($this->_user)); - $this->assertFalse($this->_unitOfWork->isRegisteredDirty($this->_user)); - $this->assertFalse($this->_unitOfWork->isRegisteredRemoved($this->_user)); - } - - /*public function testRegisterNewPerf() { - $s = microtime(true); - - for ($i=1; $i<40000; $i++) { - $user = new ForumUser(); - $user->id = $i; - $this->_unitOfWork->registerNew($user); - } - $e = microtime(true); - - echo $e - $s . " seconds" . PHP_EOL; - }*/ - public function testRegisterRemovedOnNewEntityIsIgnored() { - $this->assertFalse($this->_unitOfWork->isRegisteredRemoved($this->_user)); - $this->_unitOfWork->registerDeleted($this->_user); - $this->assertFalse($this->_unitOfWork->isRegisteredRemoved($this->_user)); + $user = new ForumUser(); + $user->username = 'romanb'; + $this->assertFalse($this->_unitOfWork->isRegisteredRemoved($user)); + $this->_unitOfWork->registerDeleted($user); + $this->assertFalse($this->_unitOfWork->isRegisteredRemoved($user)); } /* Operational tests */ public function testSavingSingleEntityWithIdentityColumnForcesInsert() - { - $this->_unitOfWork->save($this->_user); - - $this->assertEquals(1, count($this->_persisterMock->getInserts())); // insert forced - $this->assertEquals(0, count($this->_persisterMock->getUpdates())); - $this->assertEquals(0, count($this->_persisterMock->getDeletes())); - - $this->assertTrue($this->_unitOfWork->isInIdentityMap($this->_user)); - + { + // Setup fake persister and id generator for identity generation + $userPersister = new Doctrine_EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("ForumUser")); + $this->_emMock->setEntityPersister('ForumUser', $userPersister); + $idGeneratorMock = new Doctrine_IdentityIdGeneratorMock($this->_emMock); + $this->_emMock->setIdGenerator('ForumUser', $idGeneratorMock); + $userPersister->setMockIdGeneratorType(Doctrine_ORM_Mapping_ClassMetadata::GENERATOR_TYPE_IDENTITY); + + // Test + $user = new ForumUser(); + $user->username = 'romanb'; + $this->_unitOfWork->save($user); + + // Check + $this->assertEquals(1, count($userPersister->getInserts())); // insert forced + $this->assertEquals(0, count($userPersister->getUpdates())); + $this->assertEquals(0, count($userPersister->getDeletes())); + $this->assertTrue($this->_unitOfWork->isInIdentityMap($user)); // should no longer be scheduled for insert - $this->assertFalse($this->_unitOfWork->isRegisteredNew($this->_user)); + $this->assertFalse($this->_unitOfWork->isRegisteredNew($user)); // should have an id - $this->assertTrue(is_numeric($this->_user->id)); + $this->assertTrue(is_numeric($user->id)); // Now lets check whether a subsequent commit() does anything - - $this->_persisterMock->reset(); - + $userPersister->reset(); + + // Test $this->_unitOfWork->commit(); // shouldnt do anything - // verify that nothing happened - $this->assertEquals(0, count($this->_persisterMock->getInserts())); - $this->assertEquals(0, count($this->_persisterMock->getUpdates())); - $this->assertEquals(0, count($this->_persisterMock->getDeletes())); + // Check. Verify that nothing happened. + $this->assertEquals(0, count($userPersister->getInserts())); + $this->assertEquals(0, count($userPersister->getUpdates())); + $this->assertEquals(0, count($userPersister->getDeletes())); } - - public function testCommitOrder() + + /** + * Tests a scenario where a save() operation is cascaded from a ForumUser + * to its associated ForumAvatar, both entities using IDENTITY id generation. + */ + public function testCascadedIdentityColumnInsert() { + // Setup fake persister and id generator for identity generation + //ForumUser + $userPersister = new Doctrine_EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("ForumUser")); + $this->_emMock->setEntityPersister('ForumUser', $userPersister); + $userIdGeneratorMock = new Doctrine_IdentityIdGeneratorMock($this->_emMock); + $this->_emMock->setIdGenerator('ForumUser', $userIdGeneratorMock); + $userPersister->setMockIdGeneratorType(Doctrine_ORM_Mapping_ClassMetadata::GENERATOR_TYPE_IDENTITY); + // ForumAvatar + $avatarPersister = new Doctrine_EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("ForumAvatar")); + $this->_emMock->setEntityPersister('ForumAvatar', $avatarPersister); + $avatarIdGeneratorMock = new Doctrine_IdentityIdGeneratorMock($this->_emMock); + $this->_emMock->setIdGenerator('ForumAvatar', $avatarIdGeneratorMock); + $avatarPersister->setMockIdGeneratorType(Doctrine_ORM_Mapping_ClassMetadata::GENERATOR_TYPE_IDENTITY); + + // Test + $user = new ForumUser(); + $user->username = 'romanb'; $avatar = new ForumAvatar(); - $this->_user->avatar = $avatar; - $this->_unitOfWork->save($this->_user); // save cascaded to avatar - - $this->assertEquals(2, count($this->_persisterMock->getInserts())); // insert forced - $this->assertEquals(0, count($this->_persisterMock->getUpdates())); - $this->assertEquals(0, count($this->_persisterMock->getDeletes())); - // verify order of inserts()s - $inserts = $this->_persisterMock->getInserts(); - $this->assertSame($avatar, $inserts[0]); - $this->assertSame($this->_user, $inserts[1]); - - //... + $user->avatar = $avatar; + $this->_unitOfWork->save($user); // save cascaded to avatar + + $this->assertTrue(is_numeric($user->id)); + $this->assertTrue(is_numeric($avatar->id)); + + $this->assertEquals(1, count($userPersister->getInserts())); // insert forced + $this->assertEquals(0, count($userPersister->getUpdates())); + $this->assertEquals(0, count($userPersister->getDeletes())); + + $this->assertEquals(1, count($avatarPersister->getInserts())); // insert forced + $this->assertEquals(0, count($avatarPersister->getUpdates())); + $this->assertEquals(0, count($avatarPersister->getDeletes())); } + + public function testComputeDataChangeSet() + { + $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"; + $this->_unitOfWork->setEntityState($user2, Doctrine_ORM_UnitOfWork::STATE_MANAGED); + + $this->_unitOfWork->setOriginalEntityData($user1, array( + 'id' => 1, 'username' => 'roman' + )); + $this->_unitOfWork->setOriginalEntityData($user2, array( + 'id' => 2, 'username' => 'jon' + )); + + $this->_unitOfWork->computeDataChangeSet(array($user1, $user2)); + + $user1ChangeSet = $this->_unitOfWork->getDataChangeSet($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->getDataChangeSet($user2); + $this->assertTrue(is_array($user2ChangeSet)); + $this->assertEquals(1, count($user2ChangeSet)); + $this->assertTrue(isset($user2ChangeSet['username'])); + $this->assertEquals(array('jon' => 'jwage'), $user2ChangeSet['username']); + } + /* public function testSavingSingleEntityWithSequenceIdGeneratorSchedulesInsert() { diff --git a/tests/lib/mocks/Doctrine_EntityManagerMock.php b/tests/lib/mocks/Doctrine_EntityManagerMock.php index 9a6ffd7b2..934284fa5 100644 --- a/tests/lib/mocks/Doctrine_EntityManagerMock.php +++ b/tests/lib/mocks/Doctrine_EntityManagerMock.php @@ -2,6 +2,9 @@ require_once 'lib/mocks/Doctrine_EntityPersisterMock.php'; +/** + * Special EntityManager mock used for testing purposes. + */ class Doctrine_EntityManagerMock extends Doctrine_ORM_EntityManager { private $_persisterMock; @@ -13,8 +16,8 @@ class Doctrine_EntityManagerMock extends Doctrine_ORM_EntityManager */ public function getEntityPersister($entityName) { - return isset($this->_persisterMock) ? $this->_persisterMock : - parent::getEntityPersister($entityName); + return isset($this->_persisterMock[$entityName]) ? + $this->_persisterMock[$entityName] : parent::getEntityPersister($entityName); } /** @@ -27,24 +30,36 @@ class Doctrine_EntityManagerMock extends Doctrine_ORM_EntityManager /* Mock API */ + /** + * Sets a (mock) UnitOfWork that will be returned when getUnitOfWork() is called. + * + * @param $uow + */ public function setUnitOfWork($uow) { $this->_uowMock = $uow; } - public function setEntityPersister($persister) + /** + * Sets a (mock) persister for an entity class that will be returned when + * getEntityPersister() is invoked for that class. + * + * @param $entityName + * @param $persister + */ + public function setEntityPersister($entityName, $persister) { - $this->_persisterMock = $persister; + $this->_persisterMock[$entityName] = $persister; } /** - * Mock factory method. + * Mock factory method to create an EntityManager. * * @param unknown_type $conn * @param unknown_type $name * @param Doctrine_Configuration $config * @param Doctrine_EventManager $eventManager - * @return unknown + * @return Doctrine\ORM\EntityManager */ public static function create($conn, $name, Doctrine_ORM_Configuration $config = null, Doctrine_Common_EventManager $eventManager = null) diff --git a/tests/lib/mocks/Doctrine_EntityPersisterMock.php b/tests/lib/mocks/Doctrine_EntityPersisterMock.php index f69579c89..65d02dd71 100644 --- a/tests/lib/mocks/Doctrine_EntityPersisterMock.php +++ b/tests/lib/mocks/Doctrine_EntityPersisterMock.php @@ -1,22 +1,32 @@ $entity + * @return + * @override + */ public function insert($entity) { - $class = $this->_em->getClassMetadata(get_class($entity)); - if ($class->isIdGeneratorIdentity()) { - $class->setEntityIdentifier($entity, $this->_identityColumnValueCounter++); - $this->_em->getUnitOfWork()->addToIdentityMap($entity); - } - $this->_inserts[] = $entity; + if ( ! is_null($this->_mockIdGeneratorType) && $this->_mockIdGeneratorType == Doctrine_ORM_Mapping_ClassMetadata::GENERATOR_TYPE_IDENTITY + || $this->_classMetadata->isIdGeneratorIdentity()) { + return $this->_identityColumnValueCounter++; + } + return null; + } + + public function setMockIdGeneratorType($genType) { + $this->_mockIdGeneratorType = $genType; } public function update(Doctrine_ORM_Entity $entity) @@ -51,7 +61,5 @@ class Doctrine_EntityPersisterMock extends Doctrine_ORM_Persisters_StandardEntit $this->_updates = array(); $this->_deletes = array(); } - } -?> \ No newline at end of file diff --git a/tests/lib/mocks/Doctrine_IdentityIdGeneratorMock.php b/tests/lib/mocks/Doctrine_IdentityIdGeneratorMock.php new file mode 100644 index 000000000..82b09856c --- /dev/null +++ b/tests/lib/mocks/Doctrine_IdentityIdGeneratorMock.php @@ -0,0 +1,20 @@ +_mockPostInsertId = $id; + } +} +?> diff --git a/tests/lib/mocks/Doctrine_SequenceMock.php b/tests/lib/mocks/Doctrine_SequenceMock.php index 307fd3ff2..07ddcbe42 100644 --- a/tests/lib/mocks/Doctrine_SequenceMock.php +++ b/tests/lib/mocks/Doctrine_SequenceMock.php @@ -15,8 +15,6 @@ class Doctrine_SequenceMock extends Doctrine_ORM_Id_SequenceGenerator return $this->_sequenceNumber++; } - - /** * @override */ diff --git a/tests/lib/mocks/Doctrine_UnitOfWorkMock.php b/tests/lib/mocks/Doctrine_UnitOfWorkMock.php index 5ee84abee..400a2dd17 100644 --- a/tests/lib/mocks/Doctrine_UnitOfWorkMock.php +++ b/tests/lib/mocks/Doctrine_UnitOfWorkMock.php @@ -17,7 +17,7 @@ class Doctrine_UnitOfWorkMock extends Doctrine_ORM_UnitOfWork { * @override */ public function getDataChangeSet($entity) { - $oid = spl_object_id($entity); + $oid = spl_object_hash($entity); return isset($this->_mockDataChangeSets[$oid]) ? $this->_mockDataChangeSets[$oid] : parent::getDataChangeSet($entity); } @@ -25,7 +25,17 @@ class Doctrine_UnitOfWorkMock extends Doctrine_ORM_UnitOfWork { /* MOCK API */ public function setDataChangeSet($entity, array $mockChangeSet) { - $this->_mockDataChangeSets[spl_object_id($entity)] = $mockChangeSet; + $this->_mockDataChangeSets[spl_object_hash($entity)] = $mockChangeSet; + } + + public function setEntityState($entity, $state) + { + $this->_entityStates[spl_object_hash($entity)] = $state; + } + + public function setOriginalEntityData($entity, array $originalData) + { + $this->_originalEntityData[spl_object_hash($entity)] = $originalData; } } diff --git a/tests/models/forum/ForumUser.php b/tests/models/forum/ForumUser.php index 8f779b687..15185228e 100644 --- a/tests/models/forum/ForumUser.php +++ b/tests/models/forum/ForumUser.php @@ -13,6 +13,18 @@ class ForumUser public static function initMetadata($mapping) { + /*$mapping->setClassMetadata(array( + 'doctrine.inheritanceType' => 'joined', + 'doctrine.discriminatorColumn' => 'dtype', + 'doctrine.discriminatorMap' => array('user' => 'ForumUser', 'admin' => 'ForumAdministrator'), + 'doctrine.subclasses' => array('ForumAdministrator') + )); + $mapping->setFieldMetadata('id', array( + 'doctrine.type' => 'integer', + 'doctrine.id' => true, + 'doctrine.idGenerator' => 'auto' + ));*/ + // inheritance mapping $mapping->setInheritanceType('joined', array( 'discriminatorColumn' => 'dtype',