diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 3a096ba21f2..0db1eb0db35 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -35,6 +35,8 @@ use Doctrine\ORM\Mapping\QuoteStrategy; use Doctrine\ORM\Repository\DefaultRepositoryFactory; use Doctrine\ORM\Repository\RepositoryFactory; +use Doctrine\ORM\Persister\DefaultPersisterFactory; +use Doctrine\ORM\Persister\PersisterFactory; /** * Configuration container for all configuration options of Doctrine. @@ -805,4 +807,30 @@ public function getRepositoryFactory() ? $this->_attributes['repositoryFactory'] : new DefaultRepositoryFactory(); } + + /** + * Set the persister factory. + * + * @since 2.5 + * + * @param \Doctrine\ORM\Persister\PersisterFactory $persisterFactory + */ + public function setPersisterFactory(PersisterFactory $persisterFactory) + { + $this->_attributes['persisterFactory'] = $persisterFactory; + } + + /** + * Get the persister factory. + * + * @since 2.5 + * + * @return \Doctrine\ORM\Persister\PersisterFactory + */ + public function getPersisterFactory() + { + return isset($this->_attributes['persisterFactory']) + ? $this->_attributes['persisterFactory'] + : new DefaultPersisterFactory(); + } } diff --git a/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php b/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php index dc123118f52..e6263d16a53 100644 --- a/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php +++ b/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php @@ -149,6 +149,22 @@ public function getPartialReference($entityName, $identifier) return $this->wrapped->getPartialReference($entityName, $identifier); } + /** + * {@inheritdoc} + */ + public function getEntityPersister($entityName) + { + return $this->wrapped->getEntityPersister($entityName); + } + + /** + * {@inheritdoc} + */ + public function getCollectionPersister($association) + { + return $this->wrapped->getCollectionPersister($association); + } + /** * {@inheritdoc} */ diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index d21d3fb86d3..74b5055f343 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -113,6 +113,13 @@ */ private $repositoryFactory; + /** + * The persister factory used to create persisters. + * + * @var \Doctrine\ORM\Persister\PersisterFactory + */ + private $persisterFactory; + /** * The expression builder instance used to generate query expressions. * @@ -155,6 +162,7 @@ protected function __construct(Connection $conn, Configuration $config, EventMan $this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl()); $this->repositoryFactory = $config->getRepositoryFactory(); + $this->persisterFactory = $config->getPersisterFactory(); $this->unitOfWork = new UnitOfWork($this); $this->proxyFactory = new ProxyFactory( $this, @@ -445,7 +453,7 @@ public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion case LockMode::PESSIMISTIC_READ: case LockMode::PESSIMISTIC_WRITE: - $persister = $unitOfWork->getEntityPersister($class->name); + $persister = $this->getEntityPersister($class->name); $persister->refresh($sortedId, $entity, $lockMode); break; } @@ -453,7 +461,7 @@ public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion return $entity; // Hit! } - $persister = $unitOfWork->getEntityPersister($class->name); + $persister = $this->getEntityPersister($class->name); switch ($lockMode) { case LockMode::NONE: @@ -759,6 +767,32 @@ public function getRepository($entityName) return $this->repositoryFactory->getRepository($this, $entityName); } + /** + * Gets the persister for an entity class. + * + * @param string $entityName The name of the entity. + * + * @return \Doctrine\ORM\Persister\Entity\EntityPersister + */ + public function getEntityPersister($entityName) + { + $classMetadata = $this->getClassMetadata($entityName); + + return $this->persisterFactory->createEntityPersister($this, $classMetadata); + } + + /** + * Gets the persister for a collection. + * + * @param array $association The association mapping. + * + * @return \Doctrine\ORM\Persister\Collection\CollectionPersister + */ + public function getCollectionPersister($association) + { + return $this->persisterFactory->createCollectionPersister($this, $association); + } + /** * Determines whether an entity instance is managed in this EntityManager. * diff --git a/lib/Doctrine/ORM/EntityManagerInterface.php b/lib/Doctrine/ORM/EntityManagerInterface.php index d72f7cd0cf8..b0775376789 100644 --- a/lib/Doctrine/ORM/EntityManagerInterface.php +++ b/lib/Doctrine/ORM/EntityManagerInterface.php @@ -27,7 +27,7 @@ * EntityManager interface * * @since 2.4 - * @author Lars Strojny */ interface EntityManagerInterface extends ObjectManager { @@ -44,6 +44,8 @@ public function createNamedNativeQuery($name); public function createQueryBuilder(); public function getReference($entityName, $id); public function getPartialReference($entityName, $identifier); + public function getEntityPersister($entityName); + public function getCollectionPersister($association); public function close(); public function copy($entity, $deep = false); public function lock($entity, $lockMode, $lockVersion = null); diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 9a968d1ba24..cf0106574b3 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -176,7 +176,7 @@ public function findAll() */ public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) { - $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); + $persister = $this->_em->getEntityPersister($this->_entityName); return $persister->loadAll($criteria, $orderBy, $limit, $offset); } @@ -191,7 +191,7 @@ public function findBy(array $criteria, array $orderBy = null, $limit = null, $o */ public function findOneBy(array $criteria, array $orderBy = null) { - $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); + $persister = $this->_em->getEntityPersister($this->_entityName); return $persister->load($criteria, null, null, array(), 0, 1, $orderBy); } @@ -299,7 +299,7 @@ protected function getClassMetadata() */ public function matching(Criteria $criteria) { - $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); + $persister = $this->_em->getEntityPersister($this->_entityName); return new ArrayCollection($persister->loadCriteria($criteria)); } diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 8d0fef75786..0389b1e35af 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -778,7 +778,7 @@ public function current() public function next() { $this->initialize(); - + return $this->coll->next(); } @@ -875,7 +875,7 @@ public function matching(Criteria $criteria) $criteria->where($expression); - $persister = $this->em->getUnitOfWork()->getEntityPersister($this->association['targetEntity']); + $persister = $this->em->getEntityPersister($this->association['targetEntity']); return new ArrayCollection($persister->loadCriteria($criteria)); } diff --git a/lib/Doctrine/ORM/Persister/Collection/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persister/Collection/AbstractCollectionPersister.php index cdc79ec577f..a070ceca73c 100644 --- a/lib/Doctrine/ORM/Persister/Collection/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persister/Collection/AbstractCollectionPersister.php @@ -26,9 +26,11 @@ * Base class for all collection persisters. * * @since 2.0 + * + * @author Guilherme Blanco * @author Roman Borschel */ -abstract class AbstractCollectionPersister +abstract class AbstractCollectionPersister implements CollectionPersister { /** * @var EntityManager diff --git a/lib/Doctrine/ORM/Persister/Collection/ManyToManyCollectionPersister.php b/lib/Doctrine/ORM/Persister/Collection/ManyToManyCollectionPersister.php index dc42e3fb38b..29dd5d0165d 100644 --- a/lib/Doctrine/ORM/Persister/Collection/ManyToManyCollectionPersister.php +++ b/lib/Doctrine/ORM/Persister/Collection/ManyToManyCollectionPersister.php @@ -265,9 +265,10 @@ public function count(PersistentCollection $coll) */ public function slice(PersistentCollection $coll, $offset, $length = null) { - $mapping = $coll->getMapping(); + $mapping = $coll->getMapping(); + $persister = $this->em->getEntityPersister($mapping['targetEntity']); - return $this->em->getUnitOfWork()->getEntityPersister($mapping['targetEntity'])->getManyToManyCollection($mapping, $coll->getOwner(), $offset, $length); + return $persister->getManyToManyCollection($mapping, $coll->getOwner(), $offset, $length); } /** diff --git a/lib/Doctrine/ORM/Persister/Collection/OneToManyCollectionPersister.php b/lib/Doctrine/ORM/Persister/Collection/OneToManyCollectionPersister.php index 7aaecfdc193..332a6480bb1 100644 --- a/lib/Doctrine/ORM/Persister/Collection/OneToManyCollectionPersister.php +++ b/lib/Doctrine/ORM/Persister/Collection/OneToManyCollectionPersister.php @@ -40,14 +40,25 @@ class OneToManyCollectionPersister extends AbstractCollectionPersister public function get(PersistentCollection $coll, $index) { $mapping = $coll->getMapping(); - $uow = $this->em->getUnitOfWork(); - $persister = $uow->getEntityPersister($mapping['targetEntity']); + $persister = $this->em->getEntityPersister($mapping['targetEntity']); - if (!isset($mapping['indexBy'])) { - throw new \BadMethodCallException("Selecting a collection by index is only supported on indexed collections."); + if ( ! isset($mapping['indexBy'])) { + throw new \BadMethodCallException( + "Selecting a collection by index is only supported on indexed collections." + ); } - return $persister->load(array($mapping['mappedBy'] => $coll->getOwner(), $mapping['indexBy'] => $index), null, null, array(), 0, 1); + return $persister->load( + array( + $mapping['mappedBy'] => $coll->getOwner(), + $mapping['indexBy'] => $index + ), + null, + null, + array(), + 0, + 1 + ); } /** @@ -175,8 +186,7 @@ public function count(PersistentCollection $coll) public function slice(PersistentCollection $coll, $offset, $length = null) { $mapping = $coll->getMapping(); - $uow = $this->em->getUnitOfWork(); - $persister = $uow->getEntityPersister($mapping['targetEntity']); + $persister = $this->em->getEntityPersister($mapping['targetEntity']); return $persister->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length); } @@ -204,12 +214,12 @@ public function contains(PersistentCollection $coll, $element) return false; } - $persister = $uow->getEntityPersister($mapping['targetEntity']); + $persister = $this->em->getEntityPersister($mapping['targetEntity']); // only works with single id identifier entities. Will throw an // exception in Entity Persisters if that is not the case for the // 'mappedBy' field. - $id = current( $uow->getEntityIdentifier($coll->getOwner())); + $id = current($uow->getEntityIdentifier($coll->getOwner())); return $persister->exists($element, array($mapping['mappedBy'] => $id)); } diff --git a/lib/Doctrine/ORM/Persister/DefaultPersisterFactory.php b/lib/Doctrine/ORM/Persister/DefaultPersisterFactory.php new file mode 100644 index 00000000000..36b48cc8878 --- /dev/null +++ b/lib/Doctrine/ORM/Persister/DefaultPersisterFactory.php @@ -0,0 +1,118 @@ +. + */ + +namespace Doctrine\ORM\Persister; + +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\ClassMetadata; + +/** + * This factory is used to create default persisters for entities or collections at runtime. + * + * @author Guilherme Blanco + * + * @since 2.5 + */ +class DefaultPersisterFactory implements PersisterFactory +{ + /** + * The entity persister instances used to persist entity instances. + * + * @var array<\Doctrine\ORM\Persister\Entity\EntityPersister> + */ + private $entityPersisters = array(); + + /** + * The collection persister instances used to persist collections. + * + * @var array<\Doctrine\ORM\Persister\Collection\CollectionPersister> + */ + private $collectionPersisters = array(); + + /** + * {@inheritdoc} + */ + public function createEntityPersister(EntityManagerInterface $entityManager, ClassMetadata $classMetadata) + { + $entityName = $classMetadata->name; + + if (isset($this->entityPersisters[$entityName])) { + return $this->entityPersisters[$entityName]; + } + + switch (true) { + case ($classMetadata->customPersisterClassName): + $persisterClass = $classMetadata->customPersisterClassName; + $persister = new $persisterClass($entityManager, $classMetadata); + break; + + case ($classMetadata->isInheritanceTypeNone()): + $persister = new Entity\BasicEntityPersister($entityManager, $classMetadata); + break; + + case ($classMetadata->isInheritanceTypeSingleTable()): + $persister = new Entity\SingleTableEntityPersister($entityManager, $classMetadata); + break; + + case ($classMetadata->isInheritanceTypeJoined()): + $persister = new Entity\JoinedSubclassEntityPersister($entityManager, $classMetadata); + break; + + default: + $persister = new Entity\UnionSubclassEntityPersister($entityManager, $classMetadata); + } + + $this->entityPersisters[$entityName] = $persister; + + return $persister; + } + + /** + * {@inheritdoc} + */ + public function createCollectionPersister(EntityManagerInterface $entityManager, array $association) + { + $type = $association['persisterClass'] + ? sprintf('%s::%s', $association['sourceEntity'], $association['fieldName']) + : $association['type']; + + if (isset($this->collectionPersisters[$type])) { + return $this->collectionPersisters[$type]; + } + + switch (true) { + case ($association['persisterClass']): + $persisterClass = $association['persisterClass']; + $persister = new $persisterClass($entityManager); + break; + + case ($type === ClassMetadata::ONE_TO_MANY): + $persister = new Collection\OneToManyCollectionPersister($entityManager); + break; + + case ($type === ClassMetadata::MANY_TO_MANY): + $persister = new Collection\ManyToManyCollectionPersister($entityManager); + break; + } + + $this->collectionPersisters[$type] = $persister; + + return $persister; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persister/Entity/BasicEntityPersister.php b/lib/Doctrine/ORM/Persister/Entity/BasicEntityPersister.php index 665dc482e59..19df9f821e7 100644 --- a/lib/Doctrine/ORM/Persister/Entity/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persister/Entity/BasicEntityPersister.php @@ -78,7 +78,7 @@ * @author Fabio B. Silva * @since 2.0 */ -class BasicEntityPersister +class BasicEntityPersister implements EntityPersister { /** * @var array diff --git a/lib/Doctrine/ORM/Persister/Entity/JoinedSubclassEntityPersister.php b/lib/Doctrine/ORM/Persister/Entity/JoinedSubclassEntityPersister.php index 6aac8d5762a..a67fd4dc23c 100644 --- a/lib/Doctrine/ORM/Persister/Entity/JoinedSubclassEntityPersister.php +++ b/lib/Doctrine/ORM/Persister/Entity/JoinedSubclassEntityPersister.php @@ -138,7 +138,7 @@ public function executeInserts() : $this->class; // Prepare statement for the root table - $rootPersister = $this->em->getUnitOfWork()->getEntityPersister($rootClass->name); + $rootPersister = $this->em->getEntityPersister($rootClass->name); $rootTableName = $rootClass->getTableName(); $rootTableStmt = $this->conn->prepare($rootPersister->getInsertSQL()); @@ -150,11 +150,12 @@ public function executeInserts() } foreach ($this->class->parentClasses as $parentClassName) { - $parentClass = $this->em->getClassMetadata($parentClassName); + $parentClass = $this->em->getClassMetadata($parentClassName); $parentTableName = $parentClass->getTableName(); if ($parentClass !== $rootClass) { - $parentPersister = $this->em->getUnitOfWork()->getEntityPersister($parentClassName); + $parentPersister = $this->em->getEntityPersister($parentClassName); + $subTableStmts[$parentTableName] = $this->conn->prepare($parentPersister->getInsertSQL()); } } diff --git a/lib/Doctrine/ORM/Persister/PersisterFactory.php b/lib/Doctrine/ORM/Persister/PersisterFactory.php index 8a68cefe79b..c30d4b2020f 100644 --- a/lib/Doctrine/ORM/Persister/PersisterFactory.php +++ b/lib/Doctrine/ORM/Persister/PersisterFactory.php @@ -20,6 +20,7 @@ namespace Doctrine\ORM\Persister; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\ClassMetadata; /** * Interface for persister factory. @@ -34,17 +35,19 @@ interface PersisterFactory * Gets the entity persister for an given class. * * @param \Doctrine\ORM\EntityManagerInterface $entityManager The EntityManager instance. + * @param \Doctrine\ORM\Mapping\ClassMetadata $classMetadata The ClassMetadata instance. * * @return \Doctrine\ORM\Persister\Entity\EntityPersister */ - public function createEntityPersister(EntityManagerInterface $entityManager); + public function createEntityPersister(EntityManagerInterface $entityManager, ClassMetadata $classMetadata); /** * Gets the entity persister for an given collection association. * * @param \Doctrine\ORM\EntityManagerInterface $entityManager The EntityManager instance. + * @param array $association Association mapping. * * @return \Doctrine\ORM\Persister\Collection\CollectionPersister */ - public function createCollectionPersister(EntityManagerInterface $entityManager); + public function createCollectionPersister(EntityManagerInterface $entityManager, array $association); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 0bff3267143..0fb41edefc4 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -25,8 +25,7 @@ use Doctrine\Common\Util\ClassUtils; use Doctrine\Common\Proxy\Proxy as BaseProxy; use Doctrine\Common\Proxy\ProxyGenerator; -use Doctrine\ORM\ORMInvalidArgumentException; -use Doctrine\ORM\Persister\Entity\BasicEntityPersister; +use Doctrine\ORM\Persister\Entity\EntityPersister; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityNotFoundException; @@ -69,6 +68,7 @@ public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerat $proxyGenerator = new ProxyGenerator($proxyDir, $proxyNs); $proxyGenerator->setPlaceholder('baseProxyInterface', 'Doctrine\ORM\Proxy\Proxy'); + parent::__construct($proxyGenerator, $em->getMetadataFactory(), $autoGenerate); $this->em = $em; @@ -92,7 +92,7 @@ protected function skipClass(ClassMetadata $metadata) protected function createProxyDefinition($className) { $classMetadata = $this->em->getClassMetadata($className); - $entityPersister = $this->uow->getEntityPersister($className); + $entityPersister = $this->em->getEntityPersister($className); return new ProxyDefinition( ClassUtils::generateProxyClassName($className, $this->proxyNs), @@ -106,14 +106,14 @@ protected function createProxyDefinition($className) /** * Creates a closure capable of initializing a proxy * - * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata - * @param \Doctrine\ORM\Persister\Entity\BasicEntityPersister $entityPersister + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata + * @param \Doctrine\ORM\Persister\Entity\EntityPersister $entityPersister * * @return \Closure * * @throws \Doctrine\ORM\EntityNotFoundException */ - private function createInitializer(ClassMetadata $classMetadata, BasicEntityPersister $entityPersister) + private function createInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister) { if ($classMetadata->getReflectionClass()->hasMethod('__wakeup')) { return function (BaseProxy $proxy) use ($entityPersister, $classMetadata) { @@ -130,7 +130,7 @@ private function createInitializer(ClassMetadata $classMetadata, BasicEntityPers $properties = $proxy->__getLazyProperties(); foreach ($properties as $propertyName => $property) { - if (!isset($proxy->$propertyName)) { + if ( ! isset($proxy->$propertyName)) { $proxy->$propertyName = $properties[$propertyName]; } } @@ -162,7 +162,7 @@ private function createInitializer(ClassMetadata $classMetadata, BasicEntityPers $properties = $proxy->__getLazyProperties(); foreach ($properties as $propertyName => $property) { - if (!isset($proxy->$propertyName)) { + if ( ! isset($proxy->$propertyName)) { $proxy->$propertyName = $properties[$propertyName]; } } @@ -182,14 +182,14 @@ private function createInitializer(ClassMetadata $classMetadata, BasicEntityPers /** * Creates a closure capable of finalizing state a cloned proxy * - * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata - * @param \Doctrine\ORM\Persister\Entity\BasicEntityPersister $entityPersister + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata + * @param \Doctrine\ORM\Persister\Entity\EntityPersister $entityPersister * * @return \Closure * * @throws \Doctrine\ORM\EntityNotFoundException */ - private function createCloner(ClassMetadata $classMetadata, BasicEntityPersister $entityPersister) + private function createCloner(ClassMetadata $classMetadata, EntityPersister $entityPersister) { return function (BaseProxy $proxy) use ($entityPersister, $classMetadata) { if ($proxy->__isInitialized()) { @@ -198,7 +198,8 @@ private function createCloner(ClassMetadata $classMetadata, BasicEntityPersister $proxy->__setInitialized(true); $proxy->__setInitializer(null); - $class = $entityPersister->getClassMetadata(); + + $class = $entityPersister->getClassMetadata(); $original = $entityPersister->load($classMetadata->getIdentifierValues($proxy)); if (null === $original) { diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 082f95f50a2..9a32aeb3c68 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -401,7 +401,7 @@ private function _generateOrderedCollectionOrderByItems() foreach ($this->selectedClasses as $selectedClass) { $dqlAlias = $selectedClass['dqlAlias']; $qComp = $this->queryComponents[$dqlAlias]; - $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name); + $persister = $this->em->getEntityPersister($qComp['metadata']->name); if ( ! isset($qComp['relation']['orderBy'])) { continue; @@ -1238,7 +1238,7 @@ public function walkSelectExpression($selectExpression) $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName; $tableName = ($class->isInheritanceTypeJoined()) - ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) + ? $this->em->getEntityPersister($class->name)->getOwningTable($fieldName) : $class->getTableName(); $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 90d5aa27970..e530887af61 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -461,8 +461,11 @@ private function executeExtraUpdates() foreach ($this->extraUpdates as $oid => $update) { list ($entity, $changeset) = $update; + $persister = $this->em->getEntityPersister(get_class($entity)); + $this->entityChangeSets[$oid] = $changeset; - $this->getEntityPersister(get_class($entity))->update($entity); + + $persister->update($entity); } } @@ -932,7 +935,7 @@ private function executeInserts($class) { $entities = array(); $className = $class->name; - $persister = $this->getEntityPersister($className); + $persister = $this->em->getEntityPersister($className); $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist); foreach ($this->entityInsertions as $oid => $entity) { @@ -982,7 +985,7 @@ private function executeInserts($class) private function executeUpdates($class) { $className = $class->name; - $persister = $this->getEntityPersister($className); + $persister = $this->em->getEntityPersister($className); $preUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate); $postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate); @@ -1018,7 +1021,7 @@ private function executeUpdates($class) private function executeDeletions($class) { $className = $class->name; - $persister = $this->getEntityPersister($className); + $persister = $this->em->getEntityPersister($className); $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove); foreach ($this->entityDeletions as $oid => $entity) { @@ -1411,7 +1414,7 @@ public function getEntityState($entity, $assume = null) } // db lookup - if ($this->getEntityPersister($class->name)->exists($entity)) { + if ($this->em->getEntityPersister($class->name)->exists($entity)) { return self::STATE_DETACHED; } @@ -1428,7 +1431,7 @@ public function getEntityState($entity, $assume = null) } // db lookup - if ($this->getEntityPersister($class->name)->exists($entity)) { + if ($this->em->getEntityPersister($class->name)->exists($entity)) { return self::STATE_DETACHED; } @@ -2030,13 +2033,14 @@ private function doRefresh($entity, array &$visited) $visited[$oid] = $entity; // mark visited - $class = $this->em->getClassMetadata(get_class($entity)); + $class = $this->em->getClassMetadata(get_class($entity)); + $persister = $this->em->getEntityPersister($class->name); if ($this->getEntityState($entity) !== self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); } - $this->getEntityPersister($class->name)->refresh( + $persister->refresh( array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), $entity ); @@ -2305,9 +2309,10 @@ public function lock($entity, $lockMode, $lockVersion = null) throw TransactionRequiredException::transactionRequired(); } - $oid = spl_object_hash($entity); + $oid = spl_object_hash($entity); + $persister = $this->em->getEntityPersister($class->name); - $this->getEntityPersister($class->name)->lock( + $persister->lock( array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), $lockMode ); @@ -2584,7 +2589,9 @@ public function createEntity($className, array $data, &$hints = array()) case ($assoc['type'] & ClassMetadata::TO_ONE): if ( ! $assoc['isOwningSide']) { // Inverse side of x-to-one can never be lazy - $class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity)); + $persister = $this->em->getEntityPersister($assoc['targetEntity']); + + $class->reflFields[$field]->setValue($entity, $persister->loadOneToOneEntity($assoc, $entity)); continue 2; } @@ -2644,7 +2651,8 @@ public function createEntity($className, array $data, &$hints = array()) // If it might be a subtype, it can not be lazy. There isn't even // a way to solve this with deferred eager loading, which means putting // an entity with subclasses at a *-to-one location is really bad! (performance-wise) - $newValue = $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity, $associatedId); + $persister = $this->em->getEntityPersister($assoc['targetEntity']); + $newValue = $persister->loadOneToOneEntity($assoc, $entity, $associatedId); break; default: @@ -2742,9 +2750,10 @@ public function triggerEagerLoads() continue; } - $class = $this->em->getClassMetadata($entityName); + $class = $this->em->getClassMetadata($entityName); + $persister = $this->em->getEntityPersister($entityName); - $this->getEntityPersister($entityName)->loadAll( + $persister->loadAll( array_combine($class->identifier, array(array_values($ids))) ); } @@ -2762,7 +2771,7 @@ public function triggerEagerLoads() public function loadCollection(PersistentCollection $collection) { $assoc = $collection->getMapping(); - $persister = $this->getEntityPersister($assoc['targetEntity']); + $persister = $this->em->getEntityPersister($assoc['targetEntity']); switch ($assoc['type']) { case ClassMetadata::ONE_TO_MANY: @@ -2938,73 +2947,29 @@ public function size() /** * Gets the EntityPersister for an Entity. * + * @deprecated + * * @param string $entityName The name of the Entity. * - * @return \Doctrine\ORM\Persister\Entity\BasicEntityPersister + * @return \Doctrine\ORM\Persister\Entity\EntityPersister */ public function getEntityPersister($entityName) { - if (isset($this->persisters[$entityName])) { - return $this->persisters[$entityName]; - } - - $class = $this->em->getClassMetadata($entityName); - - switch (true) { - case ($class->customPersisterClassName): - $persisterClass = $class->customPersisterClassName; - $persister = new $persisterClass($this->em, $class); - break; - - case ($class->isInheritanceTypeNone()): - $persister = new Persister\Entity\BasicEntityPersister($this->em, $class); - break; - - case ($class->isInheritanceTypeSingleTable()): - $persister = new Persister\Entity\SingleTableEntityPersister($this->em, $class); - break; - - case ($class->isInheritanceTypeJoined()): - $persister = new Persister\Entity\JoinedSubclassEntityPersister($this->em, $class); - break; - - default: - $persister = new Persister\Entity\UnionSubclassEntityPersister($this->em, $class); - } - - $this->persisters[$entityName] = $persister; - - return $this->persisters[$entityName]; + return $this->em->getEntityPersister($entityName); } /** * Gets a collection persister for a collection-valued association. * - * @param array $association + * @deprecated * - * @return \Doctrine\ORM\Persisters\AbstractCollectionPersister + * @param array $association The association mapping. + * + * @return \Doctrine\ORM\Persisters\Collection\CollectionPersister */ - public function getCollectionPersister(array $association) + public function getCollectionPersister($association) { - $type = $association['type']; - - if (isset($this->collectionPersisters[$type])) { - return $this->collectionPersisters[$type]; - } - - switch ($type) { - case ClassMetadata::ONE_TO_MANY: - $persister = new Persister\Collection\OneToManyCollectionPersister($this->em); - break; - - case ClassMetadata::MANY_TO_MANY: - $persister = new Persister\Collection\ManyToManyCollectionPersister($this->em); - break; - } - - $this->collectionPersisters[$type] = $persister; - - return $this->collectionPersisters[$type]; + return $this->em->getCollectionPersister($association); } /** diff --git a/tests/Doctrine/Tests/Mocks/EntityManagerMock.php b/tests/Doctrine/Tests/Mocks/EntityManagerMock.php index b27e96c4b46..89d8c77d22e 100644 --- a/tests/Doctrine/Tests/Mocks/EntityManagerMock.php +++ b/tests/Doctrine/Tests/Mocks/EntityManagerMock.php @@ -21,6 +21,8 @@ namespace Doctrine\Tests\Mocks; +use Doctrine\Common\EventManager; +use Doctrine\ORM\Configuration; use Doctrine\ORM\Proxy\ProxyFactory; /** @@ -38,6 +40,11 @@ class EntityManagerMock extends \Doctrine\ORM\EntityManager */ private $_proxyFactoryMock; + /** + * @var array|null + */ + private $_persisterMock; + /** * {@inheritdoc} */ @@ -83,19 +90,46 @@ public function getProxyFactory() * * {@inheritdoc} */ - public static function create($conn, \Doctrine\ORM\Configuration $config = null, - \Doctrine\Common\EventManager $eventManager = null) + public static function create($conn, Configuration $config = null, EventManager $eventManager = null) { if (is_null($config)) { $config = new \Doctrine\ORM\Configuration(); + $config->setProxyDir(__DIR__ . '/../Proxies'); $config->setProxyNamespace('Doctrine\Tests\Proxies'); $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver(array(), true)); } + if (is_null($eventManager)) { $eventManager = new \Doctrine\Common\EventManager(); } return new EntityManagerMock($conn, $config, $eventManager); } + + /* MOCK API */ + + /** + * {@inheritdoc} + */ + public function getEntityPersister($entityName) + { + return isset($this->_persisterMock[$entityName]) + ? $this->_persisterMock[$entityName] + : parent::getEntityPersister($entityName); + } + + /** + * Sets a (mock) persister for an entity class that will be returned when + * getEntityPersister() is invoked for that class. + * + * @param string $entityName + * @param \Doctrine\ORM\Persister\Entity\BasicEntityPersister $persister + * + * @return void + */ + public function setEntityPersister($entityName, $persister) + { + $this->_persisterMock[$entityName] = $persister; + } } diff --git a/tests/Doctrine/Tests/Mocks/UnitOfWorkMock.php b/tests/Doctrine/Tests/Mocks/UnitOfWorkMock.php index 95ece79e85e..20969592b86 100644 --- a/tests/Doctrine/Tests/Mocks/UnitOfWorkMock.php +++ b/tests/Doctrine/Tests/Mocks/UnitOfWorkMock.php @@ -12,20 +12,6 @@ class UnitOfWorkMock extends \Doctrine\ORM\UnitOfWork */ private $_mockDataChangeSets = array(); - /** - * @var array|null - */ - private $_persisterMock; - - /** - * {@inheritdoc} - */ - public function getEntityPersister($entityName) - { - return isset($this->_persisterMock[$entityName]) ? - $this->_persisterMock[$entityName] : parent::getEntityPersister($entityName); - } - /** * {@inheritdoc} */ diff --git a/tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php b/tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php index 78206012691..4458de7ea5b 100644 --- a/tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php @@ -55,7 +55,8 @@ public function testReferenceProxyDelegatesLoadingToThePersister() $identifier = array('id' => 42); $proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature'; $persister = $this->getMock('Doctrine\ORM\Persister\Entity\BasicEntityPersister', array('load'), array(), '', false); - $this->uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); + + $this->emMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); $proxy = $this->proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier); @@ -74,7 +75,9 @@ public function testReferenceProxyDelegatesLoadingToThePersister() public function testSkipAbstractClassesOnGeneration() { $cm = new ClassMetadata(__NAMESPACE__ . '\\AbstractClass'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + $this->assertNotNull($cm->reflClass); $num = $this->proxyFactory->generateProxyClasses(array($cm)); @@ -88,7 +91,8 @@ public function testSkipAbstractClassesOnGeneration() public function testFailedProxyLoadingDoesNotMarkTheProxyAsInitialized() { $persister = $this->getMock('Doctrine\ORM\Persister\Entity\BasicEntityPersister', array('load'), array(), '', false); - $this->uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); + + $this->emMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); /* @var $proxy \Doctrine\Common\Proxy\Proxy */ $proxy = $this->proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', array('id' => 42)); @@ -115,7 +119,8 @@ public function testFailedProxyLoadingDoesNotMarkTheProxyAsInitialized() public function testFailedProxyCloningDoesNotMarkTheProxyAsInitialized() { $persister = $this->getMock('Doctrine\ORM\Persister\Entity\BasicEntityPersister', array('load'), array(), '', false); - $this->uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); + + $this->emMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); /* @var $proxy \Doctrine\Common\Proxy\Proxy */ $proxy = $this->proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', array('id' => 42)); diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index 97f60f859f1..af38dc5af0b 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -7,6 +7,7 @@ use Doctrine\Tests\Mocks\EntityManagerMock; use Doctrine\Tests\Mocks\UnitOfWorkMock; use Doctrine\Tests\Mocks\EntityPersisterMock; +use Doctrine\Tests\Mocks\DriverMock; use Doctrine\Tests\Models\Forum\ForumUser; use Doctrine\Tests\Models\Forum\ForumAvatar; @@ -26,22 +27,23 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase protected function setUp() { parent::setUp(); - $this->_connectionMock = new ConnectionMock(array(), new \Doctrine\Tests\Mocks\DriverMock()); - $this->_emMock = EntityManagerMock::create($this->_connectionMock); - // SUT - $this->_unitOfWork = new UnitOfWorkMock($this->_emMock); - $this->_emMock->setUnitOfWork($this->_unitOfWork); - } - protected function tearDown() { + $this->_connectionMock = new ConnectionMock(array(), new DriverMock()); + $this->_emMock = EntityManagerMock::create($this->_connectionMock); + $this->_unitOfWork = new UnitOfWorkMock($this->_emMock); + + $this->_emMock->setUnitOfWork($this->_unitOfWork); } public function testRegisterRemovedOnNewEntityIsIgnored() { $user = new ForumUser(); $user->username = 'romanb'; + $this->assertFalse($this->_unitOfWork->isScheduledForDelete($user)); + $this->_unitOfWork->scheduleForDelete($user); + $this->assertFalse($this->_unitOfWork->isScheduledForDelete($user)); } @@ -52,7 +54,9 @@ public function testSavingSingleEntityWithIdentityColumnForcesInsert() { // Setup fake persister and id generator for identity generation $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\Models\Forum\ForumUser")); - $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister); + + $this->_emMock->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister); + $userPersister->setMockIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY); // Test @@ -90,22 +94,28 @@ public function testSavingSingleEntityWithIdentityColumnForcesInsert() public function testCascadedIdentityColumnInsert() { // Setup fake persister and id generator for identity generation - //ForumUser + // ForumUser $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\Models\Forum\ForumUser")); - $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister); + + $this->_emMock->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister); + $userPersister->setMockIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY); + // ForumAvatar $avatarPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\Models\Forum\ForumAvatar")); - $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumAvatar', $avatarPersister); + + $this->_emMock->setEntityPersister('Doctrine\Tests\Models\Forum\ForumAvatar', $avatarPersister); + $avatarPersister->setMockIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY); // Test $user = new ForumUser(); $user->username = 'romanb'; + $avatar = new ForumAvatar(); $user->avatar = $avatar; - $this->_unitOfWork->persist($user); // save cascaded to avatar + $this->_unitOfWork->persist($user); // save cascaded to avatar $this->_unitOfWork->commit(); $this->assertTrue(is_numeric($user->id)); @@ -123,16 +133,21 @@ public function testCascadedIdentityColumnInsert() public function testChangeTrackingNotify() { $persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\ORM\NotifyChangedEntity")); - $this->_unitOfWork->setEntityPersister('Doctrine\Tests\ORM\NotifyChangedEntity', $persister); + + $this->_emMock->setEntityPersister('Doctrine\Tests\ORM\NotifyChangedEntity', $persister); + $itemPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\ORM\NotifyChangedRelatedItem")); - $this->_unitOfWork->setEntityPersister('Doctrine\Tests\ORM\NotifyChangedRelatedItem', $itemPersister); + + $this->_emMock->setEntityPersister('Doctrine\Tests\ORM\NotifyChangedRelatedItem', $itemPersister); $entity = new NotifyChangedEntity; $entity->setData('thedata'); - $this->_unitOfWork->persist($entity); + $this->_unitOfWork->persist($entity); $this->_unitOfWork->commit(); + $this->assertEquals(1, count($persister->getInserts())); + $persister->reset(); $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity)); @@ -141,25 +156,29 @@ public function testChangeTrackingNotify() $entity->setTransient('newtransientvalue'); $this->assertTrue($this->_unitOfWork->isScheduledForDirtyCheck($entity)); - $this->assertEquals(array('data' => array('thedata', 'newdata')), $this->_unitOfWork->getEntityChangeSet($entity)); $item = new NotifyChangedRelatedItem(); $entity->getItems()->add($item); $item->setOwner($entity); - $this->_unitOfWork->persist($item); + $this->_unitOfWork->persist($item); $this->_unitOfWork->commit(); + $this->assertEquals(1, count($itemPersister->getInserts())); + $persister->reset(); $itemPersister->reset(); - $entity->getItems()->removeElement($item); $item->setOwner(null); + $this->assertTrue($entity->getItems()->isDirty()); + $this->_unitOfWork->commit(); + $updates = $itemPersister->getUpdates(); + $this->assertEquals(1, count($updates)); $this->assertTrue($updates[0] === $item); } @@ -167,10 +186,12 @@ public function testChangeTrackingNotify() public function testGetEntityStateOnVersionedEntityWithAssignedIdentifier() { $persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\ORM\VersionedAssignedIdentifierEntity")); - $this->_unitOfWork->setEntityPersister('Doctrine\Tests\ORM\VersionedAssignedIdentifierEntity', $persister); + + $this->_emMock->setEntityPersister('Doctrine\Tests\ORM\VersionedAssignedIdentifierEntity', $persister); $e = new VersionedAssignedIdentifierEntity(); $e->id = 42; + $this->assertEquals(UnitOfWork::STATE_NEW, $this->_unitOfWork->getEntityState($e)); $this->assertFalse($persister->isExistsCalled()); } @@ -178,7 +199,8 @@ public function testGetEntityStateOnVersionedEntityWithAssignedIdentifier() public function testGetEntityStateWithAssignedIdentity() { $persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\Models\CMS\CmsPhonenumber")); - $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\CMS\CmsPhonenumber', $persister); + + $this->_emMock->setEntityPersister('Doctrine\Tests\Models\CMS\CmsPhonenumber', $persister); $ph = new \Doctrine\Tests\Models\CMS\CmsPhonenumber(); $ph->phonenumber = '12345'; @@ -190,10 +212,13 @@ public function testGetEntityStateWithAssignedIdentity() // if the entity is already managed the exists() check should be skipped $this->_unitOfWork->registerManaged($ph, array('phonenumber' => '12345'), array()); + $this->assertEquals(UnitOfWork::STATE_MANAGED, $this->_unitOfWork->getEntityState($ph)); $this->assertFalse($persister->isExistsCalled()); + $ph2 = new \Doctrine\Tests\Models\CMS\CmsPhonenumber(); $ph2->phonenumber = '12345'; + $this->assertEquals(UnitOfWork::STATE_DETACHED, $this->_unitOfWork->getEntityState($ph2)); $this->assertFalse($persister->isExistsCalled()); } @@ -206,11 +231,13 @@ public function testNoUndefinedIndexNoticeOnScheduleForUpdateWithoutChanges() // Setup fake persister and id generator $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\Models\Forum\ForumUser")); $userPersister->setMockIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY); - $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister); + + $this->_emMock->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister); // Create a test user $user = new ForumUser(); $user->name = 'Jasper'; + $this->_unitOfWork->persist($user); $this->_unitOfWork->commit(); @@ -227,6 +254,7 @@ public function testNoUndefinedIndexNoticeOnScheduleForUpdateWithoutChanges() public function testLockWithoutEntityThrowsException() { $this->setExpectedException('InvalidArgumentException'); + $this->_unitOfWork->lock(null, null, null); } }