From e1f2b8abec86f21ada30abfa1f26e5623f0b3b91 Mon Sep 17 00:00:00 2001 From: romanb Date: Thu, 30 Jul 2009 15:16:02 +0000 Subject: [PATCH] [2.0] Implemented support for mapped superclasses. Fixed #2353. --- .../Common/Collections/ArrayCollection.php | 3 +- lib/Doctrine/ORM/EntityManager.php | 34 +++--- lib/Doctrine/ORM/EntityRepository.php | 50 ++++---- .../ORM/Internal/Hydration/ArrayHydrator.php | 14 ++- .../ORM/Internal/Hydration/ObjectHydrator.php | 14 ++- lib/Doctrine/ORM/Mapping/ClassMetadata.php | 17 ++- .../ORM/Mapping/ClassMetadataFactory.php | 31 +++-- .../ORM/Mapping/Driver/AnnotationDriver.php | 24 ++-- .../Mapping/Driver/DoctrineAnnotations.php | 3 + lib/Doctrine/ORM/NativeQuery.php | 2 +- lib/Doctrine/ORM/OptimisticLockException.php | 3 +- lib/Doctrine/ORM/Query/ResultSetMapping.php | 17 ++- lib/Doctrine/ORM/QueryBuilder.php | 27 +++-- lib/Doctrine/ORM/Tools/SchemaTool.php | 11 +- lib/Doctrine/ORM/UnitOfWork.php | 13 +- .../Tests/ORM/Functional/AllTests.php | 1 + .../ORM/Functional/MappedSuperclassTest.php | 111 ++++++++++++++++++ tests/Doctrine/Tests/ORM/Mapping/AllTests.php | 1 + .../Mapping/BasicInheritanceMappingTest.php | 90 ++++++++++++++ 19 files changed, 350 insertions(+), 116 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php diff --git a/lib/Doctrine/Common/Collections/ArrayCollection.php b/lib/Doctrine/Common/Collections/ArrayCollection.php index 4c0ca0ad1..c1167127d 100644 --- a/lib/Doctrine/Common/Collections/ArrayCollection.php +++ b/lib/Doctrine/Common/Collections/ArrayCollection.php @@ -302,8 +302,7 @@ class ArrayCollection implements Collection */ public function isEmpty() { - // Note: Little "trick". Empty arrays evaluate to FALSE. No need to count(). - return ! (bool) $this->_elements; + return ! $this->_elements; } /** diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 7363f580b..587f9326b 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -21,13 +21,13 @@ namespace Doctrine\ORM; -use Doctrine\Common\EventManager; -use Doctrine\Common\DoctrineException; -use Doctrine\DBAL\Connection; -use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataFactory; -use Doctrine\ORM\Proxy\ProxyFactory; -use Doctrine\ORM\Proxy\ProxyClassGenerator; +use Doctrine\Common\EventManager, + Doctrine\Common\DoctrineException, + Doctrine\DBAL\Connection, + Doctrine\ORM\Mapping\ClassMetadata, + Doctrine\ORM\Mapping\ClassMetadataFactory, + Doctrine\ORM\Proxy\ProxyFactory, + Doctrine\ORM\Proxy\ProxyClassGenerator; /** * The EntityManager is the central access point to ORM functionality. @@ -146,7 +146,6 @@ class EntityManager $this->_metadataFactory = new ClassMetadataFactory($this); $this->_metadataFactory->setCacheDriver($this->_config->getMetadataCacheImpl()); $this->_unitOfWork = new UnitOfWork($this); - //FIX: this should be in a factory $this->_proxyFactory = new ProxyFactory($this, new ProxyClassGenerator($this, $this->_config->getCacheDir())); } @@ -179,7 +178,7 @@ class EntityManager } /** - * Commits a running transaction. + * Commits a transaction on the underlying database connection. * * This causes a flush() of the EntityManager if the flush mode is set to * AUTO or COMMIT. @@ -482,8 +481,7 @@ class EntityManager * Determines whether an entity instance is managed in this EntityManager. * * @param object $entity - * @return boolean TRUE if this EntityManager currently manages the given entity - * (and has it in the identity map), FALSE otherwise. + * @return boolean TRUE if this EntityManager currently manages the given entity, FALSE otherwise. */ public function contains($entity) { @@ -514,12 +512,12 @@ class EntityManager /** * Throws an exception if the EntityManager is closed or currently not active. * - * @throws EntityManagerException If the EntityManager is closed or not active. + * @throws EntityManagerException If the EntityManager is closed. */ private function _errorIfClosed() { if ($this->_closed) { - throw EntityManagerException::notActiveOrClosed(); + throw EntityManagerException::closed(); } } @@ -543,19 +541,19 @@ class EntityManager if ( ! isset($this->_hydrators[$hydrationMode])) { switch ($hydrationMode) { case Query::HYDRATE_OBJECT: - $this->_hydrators[$hydrationMode] = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this); + $this->_hydrators[$hydrationMode] = new Internal\Hydration\ObjectHydrator($this); break; case Query::HYDRATE_ARRAY: - $this->_hydrators[$hydrationMode] = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this); + $this->_hydrators[$hydrationMode] = new Internal\Hydration\ArrayHydrator($this); break; case Query::HYDRATE_SCALAR: - $this->_hydrators[$hydrationMode] = new \Doctrine\ORM\Internal\Hydration\ScalarHydrator($this); + $this->_hydrators[$hydrationMode] = new Internal\Hydration\ScalarHydrator($this); break; case Query::HYDRATE_SINGLE_SCALAR: - $this->_hydrators[$hydrationMode] = new \Doctrine\ORM\Internal\Hydration\SingleScalarHydrator($this); + $this->_hydrators[$hydrationMode] = new Internal\Hydration\SingleScalarHydrator($this); break; case Query::HYDRATE_NONE: - $this->_hydrators[$hydrationMode] = new \Doctrine\ORM\Internal\Hydration\NoneHydrator($this); + $this->_hydrators[$hydrationMode] = new Internal\Hydration\NoneHydrator($this); break; default: throw DoctrineException::updateMe("No hydrator found for hydration mode '$hydrationMode'."); diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 3b1dbd604..056c4a184 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -36,23 +36,28 @@ namespace Doctrine\ORM; */ class EntityRepository { - protected $_entityName; - protected $_em; - protected $_classMetadata; + private $_entityName; + private $_em; + private $_class; - public function __construct($em, \Doctrine\ORM\Mapping\ClassMetadata $classMetadata) + /** + * Initializes a new EntityRepository. + * + * @param EntityManager $em The EntityManager to use. + * @param ClassMetadata $classMetadata The class descriptor. + */ + public function __construct($em, \Doctrine\ORM\Mapping\ClassMetadata $class) { - $this->_entityName = $classMetadata->name; + $this->_entityName = $class->name; $this->_em = $em; - $this->_classMetadata = $classMetadata; + $this->_class = $class; } /** - * creates a new Doctrine_Query object and adds the component name - * of this table as the query 'from' part + * Creates a new Doctrine_Query object and adds the component name + * of this table as the query 'from' part. * * @param string Optional alias name for component aliasing. - * * @return Doctrine_Query */ protected function _createQuery($alias = '') @@ -68,26 +73,26 @@ class EntityRepository */ public function clear() { - $this->_em->getUnitOfWork()->clearIdentitiesForEntity($this->_classMetadata->rootEntityName); + $this->_em->clear($this->_class->rootEntityName); } /** - * Finds an entity by its primary key. + * Finds an entity by its primary key / identifier. * - * @param $id The identifier. - * @param int $hydrationMode The hydration mode to use. - * @return mixed Array or Doctrine_Entity or false if no result + * @param $id The identifier. + * @param int $hydrationMode The hydration mode to use. + * @return mixed Array or Object or false if no result. */ public function find($id, $hydrationMode = null) { // Check identity map first - if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_classMetadata->rootEntityName)) { + if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) { return $entity; // Hit! } if ( ! is_array($id) || count($id) <= 1) { $value = is_array($id) ? array_values($id) : array($id); - $id = array_combine($this->_classMetadata->identifier, $value); + $id = array_combine($this->_class->identifier, $value); } return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id); @@ -127,9 +132,10 @@ class EntityRepository */ protected function findOneBy($fieldName, $value, $hydrationMode = null) { - $results = $this->_createQuery()->where($fieldName . ' = ?')->limit(1)->execute( - array($value), $hydrationMode); - return $hydrationMode === Doctrine::HYDRATE_ARRAY ? array_shift($results) : $results->getFirst(); + $results = $this->_createQuery()->where($fieldName . ' = ?') + ->setMaxResults(1) + ->execute(array($value), $hydrationMode); + return $hydrationMode === Query::HYDRATE_ARRAY ? array_shift($results) : $results->getFirst(); } /** @@ -162,10 +168,10 @@ class EntityRepository $fieldName = Doctrine::tableize($by); $hydrationMode = isset($arguments[1]) ? $arguments[1]:null; - if ($this->_classMetadata->hasField($fieldName)) { + if ($this->_class->hasField($fieldName)) { return $this->$method($fieldName, $arguments[0], $hydrationMode); - } else if ($this->_classMetadata->hasRelation($by)) { - $relation = $this->_classMetadata->getRelation($by); + } else if ($this->_class->hasRelation($by)) { + $relation = $this->_class->getRelation($by); if ($relation['type'] === Doctrine_Relation::MANY) { throw DoctrineException::updateMe('Cannot findBy many relationship.'); } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index ce581edad..495135c43 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -42,12 +42,12 @@ class ArrayHydrator extends AbstractHydrator /** @override */ protected function _prepare() { - $this->_isSimpleQuery = $this->_rsm->getEntityResultCount() <= 1; + $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1; $this->_identifierMap = array(); $this->_resultPointers = array(); $this->_idTemplate = array(); $this->_resultCounter = 0; - foreach ($this->_rsm->getAliasMap() as $dqlAlias => $className) { + foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->_identifierMap[$dqlAlias] = array(); $this->_resultPointers[$dqlAlias] = array(); $this->_idTemplate[$dqlAlias] = ''; @@ -89,8 +89,6 @@ class ArrayHydrator extends AbstractHydrator // It's a joined result $parent = $this->_rsm->parentAliasMap[$dqlAlias]; - $relation = $this->_rsm->relationMap[$dqlAlias]; - $relationAlias = $relation->getSourceFieldName(); $path = $parent . '.' . $dqlAlias; // Get a reference to the right element in the result tree. @@ -105,6 +103,11 @@ class ArrayHydrator extends AbstractHydrator unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 continue; } + + $relation = $this->_rsm->relationMap[$dqlAlias]; + $relationAlias = $relation->sourceFieldName; + //$relationAlias = $this->_rsm->relationMap[$dqlAlias]; + //$relation = $this->_ce[$parentClass]->associationMappings[$relationField]; // Check the type of the relation (many or single-valued) if ( ! $relation->isOneToOne()) { @@ -113,9 +116,11 @@ class ArrayHydrator extends AbstractHydrator if ( ! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = array(); } + $indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); $index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false; + if ( ! $indexExists || ! $indexIsValid) { $element = $data; if (isset($this->_rsm->indexByMap[$dqlAlias])) { @@ -176,7 +181,6 @@ class ArrayHydrator extends AbstractHydrator $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; } $this->updateResultPointer($result, $index, $dqlAlias, false); - //unset($rowData[$rootAlias]); } } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index eefdd8770..c13f8d107 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -92,6 +92,7 @@ class ObjectHydrator extends AbstractHydrator // Remember which associations are "fetch joined" if (isset($this->_rsm->relationMap[$dqlAlias])) { $assoc = $this->_rsm->relationMap[$dqlAlias]; + //$assoc = $class->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; $this->_fetchedAssociations[$assoc->sourceEntityName][$assoc->sourceFieldName] = true; if ($assoc->mappedByFieldName) { $this->_fetchedAssociations[$assoc->targetEntityName][$assoc->mappedByFieldName] = true; @@ -148,9 +149,9 @@ class ObjectHydrator extends AbstractHydrator end($coll); $this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; } else if ($coll instanceof Collection) { - if (count($coll) > 0) { + //if ( ! $coll->isEmpty()) { $this->_resultPointers[$dqlAlias] = $coll->last(); - } + //} } else { $this->_resultPointers[$dqlAlias] = $coll; } @@ -301,8 +302,6 @@ class ObjectHydrator extends AbstractHydrator // It's a joined result $parent = $this->_rsm->parentAliasMap[$dqlAlias]; - $relation = $this->_rsm->relationMap[$dqlAlias]; - $relationField = $relation->sourceFieldName; // Get a reference to the right element in the result tree. // This element will get the associated element attached. @@ -319,9 +318,13 @@ class ObjectHydrator extends AbstractHydrator $parentClass = get_class($baseElement); $oid = spl_object_hash($baseElement); + $relation = $this->_rsm->relationMap[$dqlAlias]; + //$relationField = $this->_rsm->relationMap[$dqlAlias]; + //$relation = $this->_ce[$parentClass]->associationMappings[$relationField]; + $relationField = $relation->sourceFieldName; $reflField = $this->_ce[$parentClass]->reflFields[$relationField]; $reflFieldValue = $reflField->getValue($baseElement); - + // Check the type of the relation (many or single-valued) if ( ! $relation->isOneToOne()) { // Collection-valued association @@ -406,7 +409,6 @@ class ObjectHydrator extends AbstractHydrator } } else { // Its a root result element - $this->_rootAliases[$dqlAlias] = true; // Mark as root alias if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index a8460f5de..fc240f37c 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -24,8 +24,8 @@ namespace Doctrine\ORM\Mapping; use Doctrine\Common\DoctrineException; /** - * A ClassMetadata instance holds all the ORM metadata of an entity and - * it's associations. It is the backbone of Doctrine's metadata mapping. + * A ClassMetadata instance holds all the object-relational mapping metadata + * of an entity and it's associations. * * Once populated, ClassMetadata instances are usually cached in a serialized form. * @@ -142,6 +142,13 @@ final class ClassMetadata * @var string */ public $customRepositoryClassName; + + /** + * Whether this class describes the mapping of a mapped superclass. + * + * @var boolean + */ + public $isMappedSuperclass = false; /** * The names of the parent classes (ancestors). @@ -1352,14 +1359,16 @@ final class ClassMetadata * @param string $owningClassName The name of the class that defined this mapping. * @todo Rename: addInheritedAssociationMapping */ - public function addAssociationMapping(AssociationMapping $mapping, $owningClassName) + public function addAssociationMapping(AssociationMapping $mapping, $owningClassName = null) { $sourceFieldName = $mapping->sourceFieldName; if (isset($this->associationMappings[$sourceFieldName])) { throw MappingException::duplicateFieldMapping(); } $this->associationMappings[$sourceFieldName] = $mapping; - $this->inheritedAssociationFields[$sourceFieldName] = $owningClassName; + if ($owningClassName !== null) { + $this->inheritedAssociationFields[$sourceFieldName] = $owningClassName; + } $this->_registerMappingIfInverse($mapping); } diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index fc1e5f6b9..ca0c85e28 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -21,9 +21,9 @@ namespace Doctrine\ORM\Mapping; -use Doctrine\Common\DoctrineException; -use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\ORM\Events; +use Doctrine\Common\DoctrineException, + Doctrine\DBAL\Platforms\AbstractPlatform, + Doctrine\ORM\Events; /** * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the @@ -89,7 +89,7 @@ class ClassMetadataFactory } /** - * Returns the metadata object for a class. + * Gets the class metadata descriptor for a class. * * @param string $className The name of the class. * @return Doctrine\ORM\Mapping\ClassMetadata @@ -112,6 +112,11 @@ class ClassMetadataFactory return $this->_loadedMetadata[$className]; } + /** + * + * @param $className + * @return boolean + */ public function hasMetadataFor($className) { return isset($this->_loadedMetadata[$className]); @@ -156,11 +161,14 @@ class ClassMetadataFactory foreach ($parentClasses as $className) { if (isset($this->_loadedMetadata[$className])) { $parent = $this->_loadedMetadata[$className]; - array_unshift($visited, $className); + if ( ! $parent->isMappedSuperclass) { + array_unshift($visited, $className); + } continue; } $class = $this->_newClassMetadataInstance($className); + if ($parent) { $class->setInheritanceType($parent->inheritanceType); $class->setDiscriminatorColumn($parent->discriminatorColumn); @@ -176,7 +184,7 @@ class ClassMetadataFactory $this->_driver->loadMetadataForClass($className, $class); // Verify & complete identifier mapping - if ( ! $class->identifier) { + if ( ! $class->identifier && ! $class->isMappedSuperclass) { throw MappingException::identifierRequired($className); } if ($parent) { @@ -207,7 +215,10 @@ class ClassMetadataFactory $this->_loadedMetadata[$className] = $class; $parent = $class; - array_unshift($visited, $className); + + if ( ! $class->isMappedSuperclass) { + array_unshift($visited, $className); + } } } @@ -231,7 +242,7 @@ class ClassMetadataFactory private function _addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass) { foreach ($parentClass->fieldMappings as $fieldName => $mapping) { - if ( ! isset($mapping['inherited'])) { + if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { $mapping['inherited'] = $parentClass->name; } $subClass->addFieldMapping($mapping); @@ -253,9 +264,11 @@ class ClassMetadataFactory if (isset($parentClass->inheritedAssociationFields[$mapping->sourceFieldName])) { // parent class also inherited that one $subClass->addAssociationMapping($mapping, $parentClass->inheritedAssociationFields[$mapping->sourceFieldName]); - } else { + } else if ( ! $parentClass->isMappedSuperclass) { // parent class defined that one $subClass->addAssociationMapping($mapping, $parentClass->name); + } else { + $subClass->addAssociationMapping($mapping); } } } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index f259eb101..c64a57723 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -21,11 +21,11 @@ namespace Doctrine\ORM\Mapping\Driver; -use Doctrine\Common\DoctrineException; -use Doctrine\Common\Cache\ArrayCache; -use Doctrine\Common\Annotations\AnnotationReader; -use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\MappingException; +use Doctrine\Common\DoctrineException, + Doctrine\Common\Cache\ArrayCache, + Doctrine\Common\Annotations\AnnotationReader, + Doctrine\ORM\Mapping\ClassMetadata, + Doctrine\ORM\Mapping\MappingException; require __DIR__ . '/DoctrineAnnotations.php'; @@ -60,13 +60,15 @@ class AnnotationDriver implements Driver $classAnnotations = $this->_reader->getClassAnnotations($class); - // Evaluate DoctrineEntity annotation - if ( ! isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) { - throw DoctrineException::updateMe("$className is no entity."); + // Evaluate Entity annotation + if (isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) { + $entityAnnot = $classAnnotations['Doctrine\ORM\Mapping\Entity']; + $metadata->setCustomRepositoryClass($entityAnnot->repositoryClass); + } else if (isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'])) { + $metadata->isMappedSuperclass = true; + } else { + throw DoctrineException::updateMe("$className is no entity or mapped superclass."); } - $entityAnnot = $classAnnotations['Doctrine\ORM\Mapping\Entity']; - - $metadata->setCustomRepositoryClass($entityAnnot->repositoryClass); // Evaluate DoctrineTable annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\Table'])) { diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index 7285ccb4c..8c1004f39 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -21,11 +21,14 @@ namespace Doctrine\ORM\Mapping; +use \Doctrine\Common\Annotations\Annotation; + /* Annotations */ final class Entity extends \Doctrine\Common\Annotations\Annotation { public $repositoryClass; } +final class MappedSuperclass extends Annotation {} final class InheritanceType extends \Doctrine\Common\Annotations\Annotation {} final class DiscriminatorColumn extends \Doctrine\Common\Annotations\Annotation { public $name; diff --git a/lib/Doctrine/ORM/NativeQuery.php b/lib/Doctrine/ORM/NativeQuery.php index 85ba00905..76bdbd9b5 100644 --- a/lib/Doctrine/ORM/NativeQuery.php +++ b/lib/Doctrine/ORM/NativeQuery.php @@ -35,7 +35,7 @@ final class NativeQuery extends AbstractQuery * Initializes a new instance of the NativeQuery class that is bound * to the given EntityManager. * - * @param EntityManager $em + * @param EntityManager $em The EntityManager to use. */ public function __construct(EntityManager $em) { diff --git a/lib/Doctrine/ORM/OptimisticLockException.php b/lib/Doctrine/ORM/OptimisticLockException.php index 486196f21..efcfaeb70 100644 --- a/lib/Doctrine/ORM/OptimisticLockException.php +++ b/lib/Doctrine/ORM/OptimisticLockException.php @@ -22,9 +22,8 @@ namespace Doctrine\ORM; /** - * EntityManagerException + * OptimisticLockException * - * @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/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index f44d72611..607619e5a 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -25,11 +25,16 @@ namespace Doctrine\ORM\Query; * A ResultSetMapping describes how a result set of an SQL query maps to a Doctrine result. * * IMPORTANT NOTE: - * The properties of this class are only public for fast internal READ access. + * The properties of this class are only public for fast internal READ access and to (drastically) + * reduce the size of serialized instances for more effective caching due to better (un-)serialization + * performance. + * * Users should use the public methods. * * @author Roman Borschel * @since 2.0 + * @todo Do not store AssociationMappings in $relationMap. These bloat serialized instances + * and in turn unserialize performance suffers which is important for most effective caching. */ class ResultSetMapping { @@ -54,7 +59,7 @@ class ResultSetMapping /** Maps alias names to field names that should be used for indexing. */ public $indexByMap = array(); /** A list of columns that should be ignored/skipped during hydration. */ - public $ignoredColumns = array(); + //public $ignoredColumns = array(); /** * @@ -318,19 +323,19 @@ class ResultSetMapping * * @param string $columnName */ - public function addIgnoredColumn($columnName) + /*public function addIgnoredColumn($columnName) { $this->ignoredColumns[$columnName] = true; - } + }*/ /** * * @param string $columnName * @return boolean */ - public function isIgnoredColumn($columnName) + /*public function isIgnoredColumn($columnName) { return isset($this->ignoredColumns[$columnName]); - } + }*/ } diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index 07f4b113c..b28c1a458 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -47,12 +47,12 @@ class QueryBuilder const STATE_CLEAN = 1; /** - * @var EntityManager $em Instance of an EntityManager to use for query + * @var EntityManager $em Instance of an EntityManager to use for query. */ private $_em; /** - * @var array $dqlParts The array of DQL parts collected + * @var array $dqlParts The array of DQL parts collected. */ private $_dqlParts = array( 'select' => array(), @@ -64,25 +64,30 @@ class QueryBuilder ); /** - * @var integer $type The type of query this is. Can be select, update or delete + * @var integer The type of query this is. Can be select, update or delete. */ private $_type = self::SELECT; /** - * @var integer $state The state of the query object. Can be dirty or clean. + * @var integer The state of the query object. Can be dirty or clean. */ private $_state = self::STATE_CLEAN; /** - * @var string $dql The complete DQL string for this query + * @var string The complete DQL string for this query. */ private $_dql; /** - * @var Query $q The Query instance used for this QueryBuilder + * @var Query The Query instance used for this QueryBuilder. */ private $_q; - + + /** + * Initializes a new QueryBuilder that uses the given EntityManager. + * + * @param EntityManager $entityManager The EntityManager to use. + */ public function __construct(EntityManager $entityManager) { $this->_em = $entityManager; @@ -399,12 +404,10 @@ class QueryBuilder * * BNF: * - * UpdateStatement = UpdateClause [WhereClause] [OrderByClause] [LimitClause] [OffsetClause] + * UpdateStatement = UpdateClause [WhereClause] [OrderByClause] * UpdateClause = "UPDATE" RangeVariableDeclaration "SET" UpdateItem {"," UpdateItem} * WhereClause = "WHERE" ConditionalExpression * OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} - * LimitClause = "LIMIT" integer - * OffsetClause = "OFFSET" integer * * @return string $dql */ @@ -422,15 +425,13 @@ class QueryBuilder * * BNF: * - * SelectStatement = [SelectClause] FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] [LimitClause] [OffsetClause] + * SelectStatement = [SelectClause] FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * SelectClause = "SELECT" ["ALL" | "DISTINCT"] SelectExpression {"," SelectExpression} * FromClause = "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} * WhereClause = "WHERE" ConditionalExpression * GroupByClause = "GROUP" "BY" GroupByItem {"," GroupByItem} * HavingClause = "HAVING" ConditionalExpression * OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} - * LimitClause = "LIMIT" integer - * OffsetClause = "OFFSET" integer * * @return string $dql */ diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index 0f0898042..c9885467e 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -252,11 +252,12 @@ class SchemaTool $joinTableColumns = array(); $joinTableOptions = array(); $joinTable = $mapping->getJoinTable(); - $constraint1 = array(); - $constraint1['tableName'] = $joinTable['name']; - $constraint1['foreignTable'] = $class->getTableName(); - $constraint1['local'] = array(); - $constraint1['foreign'] = array(); + $constraint1 = array( + 'tableName' => $joinTable['name'], + 'foreignTable' => $class->getTableName(), + 'local' => array(), + 'foreign' => array() + ); foreach ($joinTable['joinColumns'] as $joinColumn) { $column = array(); $column['primary'] = true; diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 28caf3255..7975f14bf 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1552,8 +1552,7 @@ class UnitOfWork implements PropertyChangedListener if ( ! $assocMapping->isCascadeRemove) { continue; } - $relatedEntities = $class->reflFields[$assocMapping->sourceFieldName] - ->getValue($entity); + $relatedEntities = $class->reflFields[$assocMapping->sourceFieldName]->getValue($entity); if ($relatedEntities instanceof Collection || is_array($relatedEntities)) { foreach ($relatedEntities as $relatedEntity) { $this->_doRemove($relatedEntity, $visited); @@ -1607,16 +1606,6 @@ class UnitOfWork implements PropertyChangedListener { $this->_orphanRemovals[spl_object_hash($entity)] = $entity; } - - /*public function scheduleCollectionUpdate(PersistentCollection $coll) - { - $this->_collectionUpdates[] = $coll; - }*/ - - /*public function isCollectionScheduledForUpdate(PersistentCollection $coll) - { - //... - }*/ /** * INTERNAL: diff --git a/tests/Doctrine/Tests/ORM/Functional/AllTests.php b/tests/Doctrine/Tests/ORM/Functional/AllTests.php index 6730e9356..fa43b9aaf 100644 --- a/tests/Doctrine/Tests/ORM/Functional/AllTests.php +++ b/tests/Doctrine/Tests/ORM/Functional/AllTests.php @@ -38,6 +38,7 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\ORM\Functional\ReferenceProxyTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\LifecycleCallbackTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\StandardEntityPersisterTest'); + $suite->addTestSuite('Doctrine\Tests\ORM\Functional\MappedSuperclassTest'); $suite->addTest(Locking\AllTests::suite()); diff --git a/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php b/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php new file mode 100644 index 000000000..90ab33fa3 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php @@ -0,0 +1,111 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\EntitySubClass'), + )); + } catch (\Exception $e) { + // Swallow all exceptions. We do not test the schema tool here. + } + } + + public function testCRUD() + { + $e = new EntitySubClass; + $e->setId(1); + $e->setName('Roman'); + $e->setMapped1(42); + $e->setMapped2('bar'); + + $this->_em->persist($e); + $this->_em->flush(); + $this->_em->clear(); + + $e2 = $this->_em->find('Doctrine\Tests\ORM\Functional\EntitySubClass', 1); + $this->assertEquals(1, $e2->getId()); + $this->assertEquals('Roman', $e2->getName()); + $this->assertNull($e2->getMappedRelated1()); + $this->assertEquals(42, $e2->getMapped1()); + $this->assertEquals('bar', $e2->getMapped2()); + } +} + +/** @MappedSuperclass */ +class MappedSuperclassBase { + /** @Column(type="integer") */ + private $mapped1; + /** @Column(type="string") */ + private $mapped2; + /** + * @OneToOne(targetEntity="MappedSuperclassRelated1") + * @JoinColumn(name="related1_id", referencedColumnName="id") + */ + private $mappedRelated1; + private $transient; + + public function setMapped1($val) { + $this->mapped1 = $val; + } + + public function getMapped1() { + return $this->mapped1; + } + + public function setMapped2($val) { + $this->mapped2 = $val; + } + + public function getMapped2() { + return $this->mapped2; + } + + public function getMappedRelated1() { + return $this->mappedRelated1; + } +} + +/** @Entity */ +class MappedSuperclassRelated1 { + /** @Id @Column(type="integer") */ + private $id; + /** @Column(type="string") */ + private $name; +} + +/** @Entity */ +class EntitySubClass extends MappedSuperclassBase { + /** @Id @Column(type="integer") */ + private $id; + /** @Column(type="string") */ + private $name; + + public function setName($name) { + $this->name = $name; + } + + public function getName() { + return $this->name; + } + + public function setId($id) { + $this->id = $id; + } + + public function getId() { + return $this->id; + } +} + diff --git a/tests/Doctrine/Tests/ORM/Mapping/AllTests.php b/tests/Doctrine/Tests/ORM/Mapping/AllTests.php index 64df08927..47d506a3a 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AllTests.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AllTests.php @@ -24,6 +24,7 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\YamlDriverTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\ClassMetadataFactoryTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\ClassMetadataLoadEventTest'); + $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\BasicInheritanceMappingTest'); return $suite; } diff --git a/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php b/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php new file mode 100644 index 000000000..0b54cdca5 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php @@ -0,0 +1,90 @@ +_factory = new ClassMetadataFactory($this->_getTestEntityManager()); + } + + /** + * @expectedException Doctrine\Common\DoctrineException + */ + public function testGetMetadataForTransientClassThrowsException() + { + $this->_factory->getMetadataFor('Doctrine\Tests\ORM\Mapping\TransientBaseClass'); + } + + public function testGetMetadataForSubclassWithTransientBaseClass() + { + $class = $this->_factory->getMetadataFor('Doctrine\Tests\ORM\Mapping\EntitySubClass'); + + $this->assertTrue(empty($class->subClasses)); + $this->assertTrue(empty($class->parentClasses)); + $this->assertTrue(isset($class->fieldMappings['id'])); + $this->assertTrue(isset($class->fieldMappings['name'])); + } + + public function testGetMetadataForSubclassWithMappedSuperclass() + { + $class = $this->_factory->getMetadataFor('Doctrine\Tests\ORM\Mapping\EntitySubClass2'); + + $this->assertTrue(empty($class->subClasses)); + $this->assertTrue(empty($class->parentClasses)); + + $this->assertTrue(isset($class->fieldMappings['mapped1'])); + $this->assertTrue(isset($class->fieldMappings['mapped2'])); + $this->assertTrue(isset($class->fieldMappings['id'])); + $this->assertTrue(isset($class->fieldMappings['name'])); + + $this->assertFalse(isset($class->fieldMappings['mapped1']['inherited'])); + $this->assertFalse(isset($class->fieldMappings['mapped2']['inherited'])); + $this->assertFalse(isset($class->fieldMappings['transient'])); + + $this->assertTrue(empty($class->inheritedAssociationFields)); + $this->assertTrue(isset($class->associationMappings['mappedRelated1'])); + } +} + +class TransientBaseClass { + private $transient1; + private $transient2; +} + +/** @Entity */ +class EntitySubClass extends TransientBaseClass +{ + /** @Id @Column(type="integer") */ + private $id; + /** @Column(type="string") */ + private $name; +} + +/** @MappedSuperclass */ +class MappedSuperclassBase { + /** @Column(type="integer") */ + private $mapped1; + /** @Column(type="string") */ + private $mapped2; + /** + * @OneToOne(targetEntity="MappedSuperclassRelated1") + * @JoinColumn(name="related1_id", referencedColumnName="id") + */ + private $mappedRelated1; + private $transient; +} + +/** @Entity */ +class EntitySubClass2 extends MappedSuperclassBase { + /** @Id @Column(type="integer") */ + private $id; + /** @Column(type="string") */ + private $name; +}