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
*/
public function isEmpty()
{
// Note: Little "trick". Empty arrays evaluate to FALSE. No need to count().
return ! (bool) $this->_elements;
return ! $this->_elements;
}
/**
......
......@@ -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'.");
......
......@@ -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 <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->_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.');
}
......
......@@ -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]);
}
}
......
......@@ -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]])) {
......
......@@ -24,8 +24,8 @@ namespace Doctrine\ORM\Mapping;
use Doctrine\Common\DoctrineException;
/**
* A <tt>ClassMetadata</tt> instance holds all the ORM metadata of an entity and
* it's associations. It is the backbone of Doctrine's metadata mapping.
* A <tt>ClassMetadata</tt> 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);
}
......
......@@ -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);
}
}
}
......
......@@ -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'])) {
......
......@@ -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;
......
......@@ -35,7 +35,7 @@ final class NativeQuery extends AbstractQuery
* Initializes a new instance of the <tt>NativeQuery</tt> class that is bound
* to the given EntityManager.
*
* @param EntityManager $em
* @param EntityManager $em The EntityManager to use.
*/
public function __construct(EntityManager $em)
{
......
......@@ -22,9 +22,8 @@
namespace Doctrine\ORM;
/**
* EntityManagerException
* OptimisticLockException
*
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
......
......@@ -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 <roman@code-factory.org>
* @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]);
}
}*/
}
......@@ -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 <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
*
* @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
*/
......
......@@ -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;
......
......@@ -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:
......
......@@ -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());
......
<?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
$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;
}
......
<?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