Commit e1f2b8ab authored by romanb's avatar romanb

[2.0] Implemented support for mapped superclasses. Fixed #2353.

parent 77206615
...@@ -302,8 +302,7 @@ class ArrayCollection implements Collection ...@@ -302,8 +302,7 @@ class ArrayCollection implements Collection
*/ */
public function isEmpty() public function isEmpty()
{ {
// Note: Little "trick". Empty arrays evaluate to FALSE. No need to count(). return ! $this->_elements;
return ! (bool) $this->_elements;
} }
/** /**
......
...@@ -21,13 +21,13 @@ ...@@ -21,13 +21,13 @@
namespace Doctrine\ORM; namespace Doctrine\ORM;
use Doctrine\Common\EventManager; use Doctrine\Common\EventManager,
use Doctrine\Common\DoctrineException; Doctrine\Common\DoctrineException,
use Doctrine\DBAL\Connection; Doctrine\DBAL\Connection,
use Doctrine\ORM\Mapping\ClassMetadata; Doctrine\ORM\Mapping\ClassMetadata,
use Doctrine\ORM\Mapping\ClassMetadataFactory; Doctrine\ORM\Mapping\ClassMetadataFactory,
use Doctrine\ORM\Proxy\ProxyFactory; Doctrine\ORM\Proxy\ProxyFactory,
use Doctrine\ORM\Proxy\ProxyClassGenerator; Doctrine\ORM\Proxy\ProxyClassGenerator;
/** /**
* The EntityManager is the central access point to ORM functionality. * The EntityManager is the central access point to ORM functionality.
...@@ -146,7 +146,6 @@ class EntityManager ...@@ -146,7 +146,6 @@ class EntityManager
$this->_metadataFactory = new ClassMetadataFactory($this); $this->_metadataFactory = new ClassMetadataFactory($this);
$this->_metadataFactory->setCacheDriver($this->_config->getMetadataCacheImpl()); $this->_metadataFactory->setCacheDriver($this->_config->getMetadataCacheImpl());
$this->_unitOfWork = new UnitOfWork($this); $this->_unitOfWork = new UnitOfWork($this);
//FIX: this should be in a factory
$this->_proxyFactory = new ProxyFactory($this, new ProxyClassGenerator($this, $this->_config->getCacheDir())); $this->_proxyFactory = new ProxyFactory($this, new ProxyClassGenerator($this, $this->_config->getCacheDir()));
} }
...@@ -179,7 +178,7 @@ class EntityManager ...@@ -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 * This causes a flush() of the EntityManager if the flush mode is set to
* AUTO or COMMIT. * AUTO or COMMIT.
...@@ -482,8 +481,7 @@ class EntityManager ...@@ -482,8 +481,7 @@ class EntityManager
* Determines whether an entity instance is managed in this EntityManager. * Determines whether an entity instance is managed in this EntityManager.
* *
* @param object $entity * @param object $entity
* @return boolean TRUE if this EntityManager currently manages the given entity * @return boolean TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
* (and has it in the identity map), FALSE otherwise.
*/ */
public function contains($entity) public function contains($entity)
{ {
...@@ -514,12 +512,12 @@ class EntityManager ...@@ -514,12 +512,12 @@ class EntityManager
/** /**
* Throws an exception if the EntityManager is closed or currently not active. * 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() private function _errorIfClosed()
{ {
if ($this->_closed) { if ($this->_closed) {
throw EntityManagerException::notActiveOrClosed(); throw EntityManagerException::closed();
} }
} }
...@@ -543,19 +541,19 @@ class EntityManager ...@@ -543,19 +541,19 @@ class EntityManager
if ( ! isset($this->_hydrators[$hydrationMode])) { if ( ! isset($this->_hydrators[$hydrationMode])) {
switch ($hydrationMode) { switch ($hydrationMode) {
case Query::HYDRATE_OBJECT: case Query::HYDRATE_OBJECT:
$this->_hydrators[$hydrationMode] = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this); $this->_hydrators[$hydrationMode] = new Internal\Hydration\ObjectHydrator($this);
break; break;
case Query::HYDRATE_ARRAY: case Query::HYDRATE_ARRAY:
$this->_hydrators[$hydrationMode] = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this); $this->_hydrators[$hydrationMode] = new Internal\Hydration\ArrayHydrator($this);
break; break;
case Query::HYDRATE_SCALAR: case Query::HYDRATE_SCALAR:
$this->_hydrators[$hydrationMode] = new \Doctrine\ORM\Internal\Hydration\ScalarHydrator($this); $this->_hydrators[$hydrationMode] = new Internal\Hydration\ScalarHydrator($this);
break; break;
case Query::HYDRATE_SINGLE_SCALAR: case Query::HYDRATE_SINGLE_SCALAR:
$this->_hydrators[$hydrationMode] = new \Doctrine\ORM\Internal\Hydration\SingleScalarHydrator($this); $this->_hydrators[$hydrationMode] = new Internal\Hydration\SingleScalarHydrator($this);
break; break;
case Query::HYDRATE_NONE: case Query::HYDRATE_NONE:
$this->_hydrators[$hydrationMode] = new \Doctrine\ORM\Internal\Hydration\NoneHydrator($this); $this->_hydrators[$hydrationMode] = new Internal\Hydration\NoneHydrator($this);
break; break;
default: default:
throw DoctrineException::updateMe("No hydrator found for hydration mode '$hydrationMode'."); throw DoctrineException::updateMe("No hydrator found for hydration mode '$hydrationMode'.");
......
...@@ -36,23 +36,28 @@ namespace Doctrine\ORM; ...@@ -36,23 +36,28 @@ namespace Doctrine\ORM;
*/ */
class EntityRepository class EntityRepository
{ {
protected $_entityName; private $_entityName;
protected $_em; private $_em;
protected $_classMetadata; private $_class;
public function __construct($em, \Doctrine\ORM\Mapping\ClassMetadata $classMetadata) /**
* Initializes a new <tt>EntityRepository</tt>.
*
* @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->_em = $em;
$this->_classMetadata = $classMetadata; $this->_class = $class;
} }
/** /**
* creates a new Doctrine_Query object and adds the component name * Creates a new Doctrine_Query object and adds the component name
* of this table as the query 'from' part * of this table as the query 'from' part.
* *
* @param string Optional alias name for component aliasing. * @param string Optional alias name for component aliasing.
*
* @return Doctrine_Query * @return Doctrine_Query
*/ */
protected function _createQuery($alias = '') protected function _createQuery($alias = '')
...@@ -68,26 +73,26 @@ class EntityRepository ...@@ -68,26 +73,26 @@ class EntityRepository
*/ */
public function clear() 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 $id The identifier.
* @param int $hydrationMode The hydration mode to use. * @param int $hydrationMode The hydration mode to use.
* @return mixed Array or Doctrine_Entity or false if no result * @return mixed Array or Object or false if no result.
*/ */
public function find($id, $hydrationMode = null) public function find($id, $hydrationMode = null)
{ {
// Check identity map first // 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! return $entity; // Hit!
} }
if ( ! is_array($id) || count($id) <= 1) { if ( ! is_array($id) || count($id) <= 1) {
$value = is_array($id) ? array_values($id) : array($id); $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); return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id);
...@@ -127,9 +132,10 @@ class EntityRepository ...@@ -127,9 +132,10 @@ class EntityRepository
*/ */
protected function findOneBy($fieldName, $value, $hydrationMode = null) protected function findOneBy($fieldName, $value, $hydrationMode = null)
{ {
$results = $this->_createQuery()->where($fieldName . ' = ?')->limit(1)->execute( $results = $this->_createQuery()->where($fieldName . ' = ?')
array($value), $hydrationMode); ->setMaxResults(1)
return $hydrationMode === Doctrine::HYDRATE_ARRAY ? array_shift($results) : $results->getFirst(); ->execute(array($value), $hydrationMode);
return $hydrationMode === Query::HYDRATE_ARRAY ? array_shift($results) : $results->getFirst();
} }
/** /**
...@@ -162,10 +168,10 @@ class EntityRepository ...@@ -162,10 +168,10 @@ class EntityRepository
$fieldName = Doctrine::tableize($by); $fieldName = Doctrine::tableize($by);
$hydrationMode = isset($arguments[1]) ? $arguments[1]:null; $hydrationMode = isset($arguments[1]) ? $arguments[1]:null;
if ($this->_classMetadata->hasField($fieldName)) { if ($this->_class->hasField($fieldName)) {
return $this->$method($fieldName, $arguments[0], $hydrationMode); return $this->$method($fieldName, $arguments[0], $hydrationMode);
} else if ($this->_classMetadata->hasRelation($by)) { } else if ($this->_class->hasRelation($by)) {
$relation = $this->_classMetadata->getRelation($by); $relation = $this->_class->getRelation($by);
if ($relation['type'] === Doctrine_Relation::MANY) { if ($relation['type'] === Doctrine_Relation::MANY) {
throw DoctrineException::updateMe('Cannot findBy many relationship.'); throw DoctrineException::updateMe('Cannot findBy many relationship.');
} }
......
...@@ -42,12 +42,12 @@ class ArrayHydrator extends AbstractHydrator ...@@ -42,12 +42,12 @@ class ArrayHydrator extends AbstractHydrator
/** @override */ /** @override */
protected function _prepare() protected function _prepare()
{ {
$this->_isSimpleQuery = $this->_rsm->getEntityResultCount() <= 1; $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1;
$this->_identifierMap = array(); $this->_identifierMap = array();
$this->_resultPointers = array(); $this->_resultPointers = array();
$this->_idTemplate = array(); $this->_idTemplate = array();
$this->_resultCounter = 0; $this->_resultCounter = 0;
foreach ($this->_rsm->getAliasMap() as $dqlAlias => $className) { foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = array(); $this->_identifierMap[$dqlAlias] = array();
$this->_resultPointers[$dqlAlias] = array(); $this->_resultPointers[$dqlAlias] = array();
$this->_idTemplate[$dqlAlias] = ''; $this->_idTemplate[$dqlAlias] = '';
...@@ -89,8 +89,6 @@ class ArrayHydrator extends AbstractHydrator ...@@ -89,8 +89,6 @@ class ArrayHydrator extends AbstractHydrator
// It's a joined result // It's a joined result
$parent = $this->_rsm->parentAliasMap[$dqlAlias]; $parent = $this->_rsm->parentAliasMap[$dqlAlias];
$relation = $this->_rsm->relationMap[$dqlAlias];
$relationAlias = $relation->getSourceFieldName();
$path = $parent . '.' . $dqlAlias; $path = $parent . '.' . $dqlAlias;
// Get a reference to the right element in the result tree. // Get a reference to the right element in the result tree.
...@@ -105,6 +103,11 @@ class ArrayHydrator extends AbstractHydrator ...@@ -105,6 +103,11 @@ class ArrayHydrator extends AbstractHydrator
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
continue; 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) // Check the type of the relation (many or single-valued)
if ( ! $relation->isOneToOne()) { if ( ! $relation->isOneToOne()) {
...@@ -113,9 +116,11 @@ class ArrayHydrator extends AbstractHydrator ...@@ -113,9 +116,11 @@ class ArrayHydrator extends AbstractHydrator
if ( ! isset($baseElement[$relationAlias])) { if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = array(); $baseElement[$relationAlias] = array();
} }
$indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); $indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
$index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; $index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false; $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
if ( ! $indexExists || ! $indexIsValid) { if ( ! $indexExists || ! $indexIsValid) {
$element = $data; $element = $data;
if (isset($this->_rsm->indexByMap[$dqlAlias])) { if (isset($this->_rsm->indexByMap[$dqlAlias])) {
...@@ -176,7 +181,6 @@ class ArrayHydrator extends AbstractHydrator ...@@ -176,7 +181,6 @@ class ArrayHydrator extends AbstractHydrator
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
} }
$this->updateResultPointer($result, $index, $dqlAlias, false); $this->updateResultPointer($result, $index, $dqlAlias, false);
//unset($rowData[$rootAlias]);
} }
} }
......
...@@ -92,6 +92,7 @@ class ObjectHydrator extends AbstractHydrator ...@@ -92,6 +92,7 @@ class ObjectHydrator extends AbstractHydrator
// Remember which associations are "fetch joined" // Remember which associations are "fetch joined"
if (isset($this->_rsm->relationMap[$dqlAlias])) { if (isset($this->_rsm->relationMap[$dqlAlias])) {
$assoc = $this->_rsm->relationMap[$dqlAlias]; $assoc = $this->_rsm->relationMap[$dqlAlias];
//$assoc = $class->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
$this->_fetchedAssociations[$assoc->sourceEntityName][$assoc->sourceFieldName] = true; $this->_fetchedAssociations[$assoc->sourceEntityName][$assoc->sourceFieldName] = true;
if ($assoc->mappedByFieldName) { if ($assoc->mappedByFieldName) {
$this->_fetchedAssociations[$assoc->targetEntityName][$assoc->mappedByFieldName] = true; $this->_fetchedAssociations[$assoc->targetEntityName][$assoc->mappedByFieldName] = true;
...@@ -148,9 +149,9 @@ class ObjectHydrator extends AbstractHydrator ...@@ -148,9 +149,9 @@ class ObjectHydrator extends AbstractHydrator
end($coll); end($coll);
$this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; $this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
} else if ($coll instanceof Collection) { } else if ($coll instanceof Collection) {
if (count($coll) > 0) { //if ( ! $coll->isEmpty()) {
$this->_resultPointers[$dqlAlias] = $coll->last(); $this->_resultPointers[$dqlAlias] = $coll->last();
} //}
} else { } else {
$this->_resultPointers[$dqlAlias] = $coll; $this->_resultPointers[$dqlAlias] = $coll;
} }
...@@ -301,8 +302,6 @@ class ObjectHydrator extends AbstractHydrator ...@@ -301,8 +302,6 @@ class ObjectHydrator extends AbstractHydrator
// It's a joined result // It's a joined result
$parent = $this->_rsm->parentAliasMap[$dqlAlias]; $parent = $this->_rsm->parentAliasMap[$dqlAlias];
$relation = $this->_rsm->relationMap[$dqlAlias];
$relationField = $relation->sourceFieldName;
// Get a reference to the right element in the result tree. // Get a reference to the right element in the result tree.
// This element will get the associated element attached. // This element will get the associated element attached.
...@@ -319,9 +318,13 @@ class ObjectHydrator extends AbstractHydrator ...@@ -319,9 +318,13 @@ class ObjectHydrator extends AbstractHydrator
$parentClass = get_class($baseElement); $parentClass = get_class($baseElement);
$oid = spl_object_hash($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]; $reflField = $this->_ce[$parentClass]->reflFields[$relationField];
$reflFieldValue = $reflField->getValue($baseElement); $reflFieldValue = $reflField->getValue($baseElement);
// Check the type of the relation (many or single-valued) // Check the type of the relation (many or single-valued)
if ( ! $relation->isOneToOne()) { if ( ! $relation->isOneToOne()) {
// Collection-valued association // Collection-valued association
...@@ -406,7 +409,6 @@ class ObjectHydrator extends AbstractHydrator ...@@ -406,7 +409,6 @@ class ObjectHydrator extends AbstractHydrator
} }
} else { } else {
// Its a root result element // Its a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root alias $this->_rootAliases[$dqlAlias] = true; // Mark as root alias
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
......
...@@ -24,8 +24,8 @@ namespace Doctrine\ORM\Mapping; ...@@ -24,8 +24,8 @@ namespace Doctrine\ORM\Mapping;
use Doctrine\Common\DoctrineException; use Doctrine\Common\DoctrineException;
/** /**
* A <tt>ClassMetadata</tt> instance holds all the ORM metadata of an entity and * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
* it's associations. It is the backbone of Doctrine's metadata mapping. * of an entity and it's associations.
* *
* Once populated, ClassMetadata instances are usually cached in a serialized form. * Once populated, ClassMetadata instances are usually cached in a serialized form.
* *
...@@ -142,6 +142,13 @@ final class ClassMetadata ...@@ -142,6 +142,13 @@ final class ClassMetadata
* @var string * @var string
*/ */
public $customRepositoryClassName; public $customRepositoryClassName;
/**
* Whether this class describes the mapping of a mapped superclass.
*
* @var boolean
*/
public $isMappedSuperclass = false;
/** /**
* The names of the parent classes (ancestors). * The names of the parent classes (ancestors).
...@@ -1352,14 +1359,16 @@ final class ClassMetadata ...@@ -1352,14 +1359,16 @@ final class ClassMetadata
* @param string $owningClassName The name of the class that defined this mapping. * @param string $owningClassName The name of the class that defined this mapping.
* @todo Rename: addInheritedAssociationMapping * @todo Rename: addInheritedAssociationMapping
*/ */
public function addAssociationMapping(AssociationMapping $mapping, $owningClassName) public function addAssociationMapping(AssociationMapping $mapping, $owningClassName = null)
{ {
$sourceFieldName = $mapping->sourceFieldName; $sourceFieldName = $mapping->sourceFieldName;
if (isset($this->associationMappings[$sourceFieldName])) { if (isset($this->associationMappings[$sourceFieldName])) {
throw MappingException::duplicateFieldMapping(); throw MappingException::duplicateFieldMapping();
} }
$this->associationMappings[$sourceFieldName] = $mapping; $this->associationMappings[$sourceFieldName] = $mapping;
$this->inheritedAssociationFields[$sourceFieldName] = $owningClassName; if ($owningClassName !== null) {
$this->inheritedAssociationFields[$sourceFieldName] = $owningClassName;
}
$this->_registerMappingIfInverse($mapping); $this->_registerMappingIfInverse($mapping);
} }
......
...@@ -21,9 +21,9 @@ ...@@ -21,9 +21,9 @@
namespace Doctrine\ORM\Mapping; namespace Doctrine\ORM\Mapping;
use Doctrine\Common\DoctrineException; use Doctrine\Common\DoctrineException,
use Doctrine\DBAL\Platforms\AbstractPlatform; Doctrine\DBAL\Platforms\AbstractPlatform,
use Doctrine\ORM\Events; Doctrine\ORM\Events;
/** /**
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
...@@ -89,7 +89,7 @@ class ClassMetadataFactory ...@@ -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. * @param string $className The name of the class.
* @return Doctrine\ORM\Mapping\ClassMetadata * @return Doctrine\ORM\Mapping\ClassMetadata
...@@ -112,6 +112,11 @@ class ClassMetadataFactory ...@@ -112,6 +112,11 @@ class ClassMetadataFactory
return $this->_loadedMetadata[$className]; return $this->_loadedMetadata[$className];
} }
/**
*
* @param $className
* @return boolean
*/
public function hasMetadataFor($className) public function hasMetadataFor($className)
{ {
return isset($this->_loadedMetadata[$className]); return isset($this->_loadedMetadata[$className]);
...@@ -156,11 +161,14 @@ class ClassMetadataFactory ...@@ -156,11 +161,14 @@ class ClassMetadataFactory
foreach ($parentClasses as $className) { foreach ($parentClasses as $className) {
if (isset($this->_loadedMetadata[$className])) { if (isset($this->_loadedMetadata[$className])) {
$parent = $this->_loadedMetadata[$className]; $parent = $this->_loadedMetadata[$className];
array_unshift($visited, $className); if ( ! $parent->isMappedSuperclass) {
array_unshift($visited, $className);
}
continue; continue;
} }
$class = $this->_newClassMetadataInstance($className); $class = $this->_newClassMetadataInstance($className);
if ($parent) { if ($parent) {
$class->setInheritanceType($parent->inheritanceType); $class->setInheritanceType($parent->inheritanceType);
$class->setDiscriminatorColumn($parent->discriminatorColumn); $class->setDiscriminatorColumn($parent->discriminatorColumn);
...@@ -176,7 +184,7 @@ class ClassMetadataFactory ...@@ -176,7 +184,7 @@ class ClassMetadataFactory
$this->_driver->loadMetadataForClass($className, $class); $this->_driver->loadMetadataForClass($className, $class);
// Verify & complete identifier mapping // Verify & complete identifier mapping
if ( ! $class->identifier) { if ( ! $class->identifier && ! $class->isMappedSuperclass) {
throw MappingException::identifierRequired($className); throw MappingException::identifierRequired($className);
} }
if ($parent) { if ($parent) {
...@@ -207,7 +215,10 @@ class ClassMetadataFactory ...@@ -207,7 +215,10 @@ class ClassMetadataFactory
$this->_loadedMetadata[$className] = $class; $this->_loadedMetadata[$className] = $class;
$parent = $class; $parent = $class;
array_unshift($visited, $className);
if ( ! $class->isMappedSuperclass) {
array_unshift($visited, $className);
}
} }
} }
...@@ -231,7 +242,7 @@ class ClassMetadataFactory ...@@ -231,7 +242,7 @@ class ClassMetadataFactory
private function _addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass) private function _addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
{ {
foreach ($parentClass->fieldMappings as $fieldName => $mapping) { foreach ($parentClass->fieldMappings as $fieldName => $mapping) {
if ( ! isset($mapping['inherited'])) { if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name; $mapping['inherited'] = $parentClass->name;
} }
$subClass->addFieldMapping($mapping); $subClass->addFieldMapping($mapping);
...@@ -253,9 +264,11 @@ class ClassMetadataFactory ...@@ -253,9 +264,11 @@ class ClassMetadataFactory
if (isset($parentClass->inheritedAssociationFields[$mapping->sourceFieldName])) { if (isset($parentClass->inheritedAssociationFields[$mapping->sourceFieldName])) {
// parent class also inherited that one // parent class also inherited that one
$subClass->addAssociationMapping($mapping, $parentClass->inheritedAssociationFields[$mapping->sourceFieldName]); $subClass->addAssociationMapping($mapping, $parentClass->inheritedAssociationFields[$mapping->sourceFieldName]);
} else { } else if ( ! $parentClass->isMappedSuperclass) {
// parent class defined that one // parent class defined that one
$subClass->addAssociationMapping($mapping, $parentClass->name); $subClass->addAssociationMapping($mapping, $parentClass->name);
} else {
$subClass->addAssociationMapping($mapping);
} }
} }
} }
......
...@@ -21,11 +21,11 @@ ...@@ -21,11 +21,11 @@
namespace Doctrine\ORM\Mapping\Driver; namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Common\DoctrineException; use Doctrine\Common\DoctrineException,
use Doctrine\Common\Cache\ArrayCache; Doctrine\Common\Cache\ArrayCache,
use Doctrine\Common\Annotations\AnnotationReader; Doctrine\Common\Annotations\AnnotationReader,
use Doctrine\ORM\Mapping\ClassMetadata; Doctrine\ORM\Mapping\ClassMetadata,
use Doctrine\ORM\Mapping\MappingException; Doctrine\ORM\Mapping\MappingException;
require __DIR__ . '/DoctrineAnnotations.php'; require __DIR__ . '/DoctrineAnnotations.php';
...@@ -60,13 +60,15 @@ class AnnotationDriver implements Driver ...@@ -60,13 +60,15 @@ class AnnotationDriver implements Driver
$classAnnotations = $this->_reader->getClassAnnotations($class); $classAnnotations = $this->_reader->getClassAnnotations($class);
// Evaluate DoctrineEntity annotation // Evaluate Entity annotation
if ( ! isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) { if (isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) {
throw DoctrineException::updateMe("$className is no 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 // Evaluate DoctrineTable annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\Table'])) { if (isset($classAnnotations['Doctrine\ORM\Mapping\Table'])) {
......
...@@ -21,11 +21,14 @@ ...@@ -21,11 +21,14 @@
namespace Doctrine\ORM\Mapping; namespace Doctrine\ORM\Mapping;
use \Doctrine\Common\Annotations\Annotation;
/* Annotations */ /* Annotations */
final class Entity extends \Doctrine\Common\Annotations\Annotation { final class Entity extends \Doctrine\Common\Annotations\Annotation {
public $repositoryClass; public $repositoryClass;
} }
final class MappedSuperclass extends Annotation {}
final class InheritanceType extends \Doctrine\Common\Annotations\Annotation {} final class InheritanceType extends \Doctrine\Common\Annotations\Annotation {}
final class DiscriminatorColumn extends \Doctrine\Common\Annotations\Annotation { final class DiscriminatorColumn extends \Doctrine\Common\Annotations\Annotation {
public $name; public $name;
......
...@@ -35,7 +35,7 @@ final class NativeQuery extends AbstractQuery ...@@ -35,7 +35,7 @@ final class NativeQuery extends AbstractQuery
* Initializes a new instance of the <tt>NativeQuery</tt> class that is bound * Initializes a new instance of the <tt>NativeQuery</tt> class that is bound
* to the given EntityManager. * to the given EntityManager.
* *
* @param EntityManager $em * @param EntityManager $em The EntityManager to use.
*/ */
public function __construct(EntityManager $em) public function __construct(EntityManager $em)
{ {
......
...@@ -22,9 +22,8 @@ ...@@ -22,9 +22,8 @@
namespace Doctrine\ORM; namespace Doctrine\ORM;
/** /**
* EntityManagerException * OptimisticLockException
* *
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org * @link www.doctrine-project.org
......
...@@ -25,11 +25,16 @@ namespace Doctrine\ORM\Query; ...@@ -25,11 +25,16 @@ namespace Doctrine\ORM\Query;
* A ResultSetMapping describes how a result set of an SQL query maps to a Doctrine result. * A ResultSetMapping describes how a result set of an SQL query maps to a Doctrine result.
* *
* IMPORTANT NOTE: * 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. * Users should use the public methods.
* *
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @since 2.0 * @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 class ResultSetMapping
{ {
...@@ -54,7 +59,7 @@ class ResultSetMapping ...@@ -54,7 +59,7 @@ class ResultSetMapping
/** Maps alias names to field names that should be used for indexing. */ /** Maps alias names to field names that should be used for indexing. */
public $indexByMap = array(); public $indexByMap = array();
/** A list of columns that should be ignored/skipped during hydration. */ /** A list of columns that should be ignored/skipped during hydration. */
public $ignoredColumns = array(); //public $ignoredColumns = array();
/** /**
* *
...@@ -318,19 +323,19 @@ class ResultSetMapping ...@@ -318,19 +323,19 @@ class ResultSetMapping
* *
* @param string $columnName * @param string $columnName
*/ */
public function addIgnoredColumn($columnName) /*public function addIgnoredColumn($columnName)
{ {
$this->ignoredColumns[$columnName] = true; $this->ignoredColumns[$columnName] = true;
} }*/
/** /**
* *
* @param string $columnName * @param string $columnName
* @return boolean * @return boolean
*/ */
public function isIgnoredColumn($columnName) /*public function isIgnoredColumn($columnName)
{ {
return isset($this->ignoredColumns[$columnName]); return isset($this->ignoredColumns[$columnName]);
} }*/
} }
...@@ -47,12 +47,12 @@ class QueryBuilder ...@@ -47,12 +47,12 @@ class QueryBuilder
const STATE_CLEAN = 1; 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; private $_em;
/** /**
* @var array $dqlParts The array of DQL parts collected * @var array $dqlParts The array of DQL parts collected.
*/ */
private $_dqlParts = array( private $_dqlParts = array(
'select' => array(), 'select' => array(),
...@@ -64,25 +64,30 @@ class QueryBuilder ...@@ -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; 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; 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; private $_dql;
/** /**
* @var Query $q The Query instance used for this QueryBuilder * @var Query The Query instance used for this QueryBuilder.
*/ */
private $_q; private $_q;
/**
* Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
*
* @param EntityManager $entityManager The EntityManager to use.
*/
public function __construct(EntityManager $entityManager) public function __construct(EntityManager $entityManager)
{ {
$this->_em = $entityManager; $this->_em = $entityManager;
...@@ -399,12 +404,10 @@ class QueryBuilder ...@@ -399,12 +404,10 @@ class QueryBuilder
* *
* BNF: * BNF:
* *
* UpdateStatement = UpdateClause [WhereClause] [OrderByClause] [LimitClause] [OffsetClause] * UpdateStatement = UpdateClause [WhereClause] [OrderByClause]
* UpdateClause = "UPDATE" RangeVariableDeclaration "SET" UpdateItem {"," UpdateItem} * UpdateClause = "UPDATE" RangeVariableDeclaration "SET" UpdateItem {"," UpdateItem}
* WhereClause = "WHERE" ConditionalExpression * WhereClause = "WHERE" ConditionalExpression
* OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} * OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem}
* LimitClause = "LIMIT" integer
* OffsetClause = "OFFSET" integer
* *
* @return string $dql * @return string $dql
*/ */
...@@ -422,15 +425,13 @@ class QueryBuilder ...@@ -422,15 +425,13 @@ class QueryBuilder
* *
* BNF: * BNF:
* *
* SelectStatement = [SelectClause] FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] [LimitClause] [OffsetClause] * SelectStatement = [SelectClause] FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
* SelectClause = "SELECT" ["ALL" | "DISTINCT"] SelectExpression {"," SelectExpression} * SelectClause = "SELECT" ["ALL" | "DISTINCT"] SelectExpression {"," SelectExpression}
* FromClause = "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} * FromClause = "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}
* WhereClause = "WHERE" ConditionalExpression * WhereClause = "WHERE" ConditionalExpression
* GroupByClause = "GROUP" "BY" GroupByItem {"," GroupByItem} * GroupByClause = "GROUP" "BY" GroupByItem {"," GroupByItem}
* HavingClause = "HAVING" ConditionalExpression * HavingClause = "HAVING" ConditionalExpression
* OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} * OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem}
* LimitClause = "LIMIT" integer
* OffsetClause = "OFFSET" integer
* *
* @return string $dql * @return string $dql
*/ */
......
...@@ -252,11 +252,12 @@ class SchemaTool ...@@ -252,11 +252,12 @@ class SchemaTool
$joinTableColumns = array(); $joinTableColumns = array();
$joinTableOptions = array(); $joinTableOptions = array();
$joinTable = $mapping->getJoinTable(); $joinTable = $mapping->getJoinTable();
$constraint1 = array(); $constraint1 = array(
$constraint1['tableName'] = $joinTable['name']; 'tableName' => $joinTable['name'],
$constraint1['foreignTable'] = $class->getTableName(); 'foreignTable' => $class->getTableName(),
$constraint1['local'] = array(); 'local' => array(),
$constraint1['foreign'] = array(); 'foreign' => array()
);
foreach ($joinTable['joinColumns'] as $joinColumn) { foreach ($joinTable['joinColumns'] as $joinColumn) {
$column = array(); $column = array();
$column['primary'] = true; $column['primary'] = true;
......
...@@ -1552,8 +1552,7 @@ class UnitOfWork implements PropertyChangedListener ...@@ -1552,8 +1552,7 @@ class UnitOfWork implements PropertyChangedListener
if ( ! $assocMapping->isCascadeRemove) { if ( ! $assocMapping->isCascadeRemove) {
continue; continue;
} }
$relatedEntities = $class->reflFields[$assocMapping->sourceFieldName] $relatedEntities = $class->reflFields[$assocMapping->sourceFieldName]->getValue($entity);
->getValue($entity);
if ($relatedEntities instanceof Collection || is_array($relatedEntities)) { if ($relatedEntities instanceof Collection || is_array($relatedEntities)) {
foreach ($relatedEntities as $relatedEntity) { foreach ($relatedEntities as $relatedEntity) {
$this->_doRemove($relatedEntity, $visited); $this->_doRemove($relatedEntity, $visited);
...@@ -1607,16 +1606,6 @@ class UnitOfWork implements PropertyChangedListener ...@@ -1607,16 +1606,6 @@ class UnitOfWork implements PropertyChangedListener
{ {
$this->_orphanRemovals[spl_object_hash($entity)] = $entity; $this->_orphanRemovals[spl_object_hash($entity)] = $entity;
} }
/*public function scheduleCollectionUpdate(PersistentCollection $coll)
{
$this->_collectionUpdates[] = $coll;
}*/
/*public function isCollectionScheduledForUpdate(PersistentCollection $coll)
{
//...
}*/
/** /**
* INTERNAL: * INTERNAL:
......
...@@ -38,6 +38,7 @@ class AllTests ...@@ -38,6 +38,7 @@ class AllTests
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\ReferenceProxyTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\ReferenceProxyTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\LifecycleCallbackTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\LifecycleCallbackTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\StandardEntityPersisterTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\StandardEntityPersisterTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\MappedSuperclassTest');
$suite->addTest(Locking\AllTests::suite()); $suite->addTest(Locking\AllTests::suite());
......
<?php
namespace Doctrine\Tests\ORM\Functional;
require_once __DIR__ . '/../../TestInit.php';
/**
* MappedSuperclassTest
*
* @author robo
*/
class MappedSuperclassTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp() {
parent::setUp();
try {
$this->_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;
}
}
...@@ -24,6 +24,7 @@ class AllTests ...@@ -24,6 +24,7 @@ class AllTests
$suite->addTestSuite('Doctrine\Tests\ORM\Mapping\YamlDriverTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\YamlDriverTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Mapping\ClassMetadataFactoryTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\ClassMetadataFactoryTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Mapping\ClassMetadataLoadEventTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Mapping\ClassMetadataLoadEventTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Mapping\BasicInheritanceMappingTest');
return $suite; return $suite;
} }
......
<?php
namespace Doctrine\Tests\ORM\Mapping;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
require_once __DIR__ . '/../../TestInit.php';
class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase
{
private $_factory;
protected function setUp() {
$this->_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;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment