diff --git a/lib/Doctrine/Common/Annotations/AnnotationReader.php b/lib/Doctrine/Common/Annotations/AnnotationReader.php index bed578e35..0863ea94f 100644 --- a/lib/Doctrine/Common/Annotations/AnnotationReader.php +++ b/lib/Doctrine/Common/Annotations/AnnotationReader.php @@ -1,7 +1,5 @@ * @author Guilherme Blanco * @author Jonathan Wage diff --git a/lib/Doctrine/Common/Annotations/Parser.php b/lib/Doctrine/Common/Annotations/Parser.php index de373c3f8..a90d1f361 100644 --- a/lib/Doctrine/Common/Annotations/Parser.php +++ b/lib/Doctrine/Common/Annotations/Parser.php @@ -22,10 +22,7 @@ namespace Doctrine\Common\Annotations; /** * A simple parser for docblock annotations. * - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel @@ -234,7 +231,7 @@ class Parser $nameParts[] = $this->_lexer->token['value']; } - // Effectively pick the name of class (append default NS if none, grab from NS alias, etc) + // Effectively pick the name of the class (append default NS if none, grab from NS alias, etc) if (count($nameParts) == 1) { if (strpos($nameParts[0], ':')) { list ($alias, $simpleName) = explode(':', $nameParts[0]); diff --git a/lib/Doctrine/Common/Cache/AbstractCache.php b/lib/Doctrine/Common/Cache/AbstractCache.php index ea5b5df5e..fa9e0a505 100644 --- a/lib/Doctrine/Common/Cache/AbstractCache.php +++ b/lib/Doctrine/Common/Cache/AbstractCache.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel diff --git a/lib/Doctrine/Common/ClassLoader.php b/lib/Doctrine/Common/ClassLoader.php index b2c525595..d971fe9a6 100644 --- a/lib/Doctrine/Common/ClassLoader.php +++ b/lib/Doctrine/Common/ClassLoader.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel @@ -39,7 +33,6 @@ class ArrayCollection implements Collection { /** * An array containing the entries of this collection. - * This is the internal php array. * * @var array */ @@ -54,7 +47,7 @@ class ArrayCollection implements Collection { $this->_elements = $elements; } - + /** * Gets the PHP array representation of this collection. * @@ -121,7 +114,7 @@ class ArrayCollection implements Collection * Removes an element with a specific key/index from the collection. * * @param mixed $key - * @return mixed + * @return mixed The removed element or NULL, if no element exists for the given key. */ public function remove($key) { @@ -131,7 +124,7 @@ class ArrayCollection implements Collection return $removed; } - + return null; } @@ -413,6 +406,7 @@ class ArrayCollection implements Collection /** * Returns a string representation of this object. * + * @return string */ public function __toString() { @@ -421,7 +415,6 @@ class ArrayCollection implements Collection /** * Clears the collection. - * */ public function clear() { diff --git a/lib/Doctrine/Common/Collections/Collection.php b/lib/Doctrine/Common/Collections/Collection.php index 6b415baaf..41af440b5 100644 --- a/lib/Doctrine/Common/Collections/Collection.php +++ b/lib/Doctrine/Common/Collections/Collection.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel diff --git a/lib/Doctrine/Common/Lexer.php b/lib/Doctrine/Common/Lexer.php index e4d39ce95..3949e11aa 100644 --- a/lib/Doctrine/Common/Lexer.php +++ b/lib/Doctrine/Common/Lexer.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel + * @todo Rename: AbstractLexer */ abstract class Lexer { @@ -50,7 +46,7 @@ abstract class Lexer private $_peek = 0; /** - * @var array The next token in the query string. + * @var array The next token in the input. */ public $lookahead; @@ -60,9 +56,12 @@ abstract class Lexer public $token; /** - * Inputs data to be tokenized + * Sets the input data to be tokenized. * - * @param string $input input to be tokenized + * The Lexer is immediately reset and the new input tokenized. + * Any unprocessed tokens from any previous input are lost. + * + * @param string $input The input to be tokenized. */ public function setInput($input) { @@ -72,20 +71,18 @@ abstract class Lexer } /** - * Resets the scanner - * + * Resets the lexer. */ public function reset() { $this->lookahead = null; - $this->token = null; - $this->_peek = 0; + $this->token = null; + $this->_peek = 0; $this->_position = 0; } - + /** - * Resets the peek pointer to 0 - * + * Resets the peek pointer to 0. */ public function resetPeek() { @@ -93,7 +90,7 @@ abstract class Lexer } /** - * Resets the lexer position on the input to the given position + * Resets the lexer position on the input to the given position. * * @param integer $position Position to place the lexical scanner */ @@ -235,14 +232,14 @@ abstract class Lexer } /** - * Lexical catchable patterns + * Lexical catchable patterns. * * @return array */ abstract protected function getCatchablePatterns(); /** - * Lexical non-catchable patterns + * Lexical non-catchable patterns. * * @return array */ diff --git a/lib/Doctrine/Common/Version.php b/lib/Doctrine/Common/Version.php index fd1b499f0..c01da049f 100644 --- a/lib/Doctrine/Common/Version.php +++ b/lib/Doctrine/Common/Version.php @@ -1,7 +1,5 @@ . + */ namespace Doctrine\ORM; @@ -12,6 +29,6 @@ class EntityNotFoundException extends ORMException { public function __construct() { - parent::__construct('Entity was found although one item was expected.'); + parent::__construct('Entity was not found.'); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 1382cb5e6..de919430d 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -1,7 +1,5 @@ * @author Guilherme Blanco * @author Jonathan Wage diff --git a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php index 3e7cdee21..a5dd39cfd 100644 --- a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php +++ b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php @@ -1,7 +1,5 @@ - * @author Benjamin Eberlei + * @since 2.0 + * @author Roman Borschel + * @author Benjamin Eberlei */ class LifecycleEventArgs extends \Doctrine\Common\EventArgs { diff --git a/lib/Doctrine/ORM/Id/AbstractIdGenerator.php b/lib/Doctrine/ORM/Id/AbstractIdGenerator.php index ffbad370e..cfe3b5daf 100644 --- a/lib/Doctrine/ORM/Id/AbstractIdGenerator.php +++ b/lib/Doctrine/ORM/Id/AbstractIdGenerator.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM\Id; diff --git a/lib/Doctrine/ORM/Id/AssignedGenerator.php b/lib/Doctrine/ORM/Id/AssignedGenerator.php index 226d2b406..f4bd3d631 100644 --- a/lib/Doctrine/ORM/Id/AssignedGenerator.php +++ b/lib/Doctrine/ORM/Id/AssignedGenerator.php @@ -1,7 +1,5 @@ * @author Guilherme Blanco * @author Jonathan Wage diff --git a/lib/Doctrine/ORM/Id/IdentityGenerator.php b/lib/Doctrine/ORM/Id/IdentityGenerator.php index e2c3bf423..96ad08add 100644 --- a/lib/Doctrine/ORM/Id/IdentityGenerator.php +++ b/lib/Doctrine/ORM/Id/IdentityGenerator.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM\Id; diff --git a/lib/Doctrine/ORM/Id/SequenceGenerator.php b/lib/Doctrine/ORM/Id/SequenceGenerator.php index fc9737653..0d564ed32 100644 --- a/lib/Doctrine/ORM/Id/SequenceGenerator.php +++ b/lib/Doctrine/ORM/Id/SequenceGenerator.php @@ -1,7 +1,5 @@ */ -class SequenceGenerator extends AbstractIdGenerator implements \Serializable +class SequenceGenerator extends AbstractIdGenerator implements Serializable { private $_allocationSize; private $_sequenceName; diff --git a/lib/Doctrine/ORM/Id/SequenceIdentityGenerator.php b/lib/Doctrine/ORM/Id/SequenceIdentityGenerator.php index 758d73474..c7158bbed 100644 --- a/lib/Doctrine/ORM/Id/SequenceIdentityGenerator.php +++ b/lib/Doctrine/ORM/Id/SequenceIdentityGenerator.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM\Id; diff --git a/lib/Doctrine/ORM/Id/TableGenerator.php b/lib/Doctrine/ORM/Id/TableGenerator.php index 12ee0a2b3..5c46f8b5c 100644 --- a/lib/Doctrine/ORM/Id/TableGenerator.php +++ b/lib/Doctrine/ORM/Id/TableGenerator.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM\Id; @@ -7,10 +24,7 @@ use Doctrine\ORM\EntityManager; /** * Id generator that uses a single-row database table and a hi/lo algorithm. * - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.org * @since 2.0 - * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage diff --git a/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php b/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php index 7af95e41e..8997b1ea5 100644 --- a/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php +++ b/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php @@ -1,7 +1,5 @@ * @author Roman Borschel */ diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index 9e5dcf2e9..aaa2c6ece 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -1,7 +1,5 @@ * @since 2.0 + * @todo Potentially remove if assoc mapping objects get replaced by simple arrays. */ abstract class AssociationMapping { @@ -58,7 +59,7 @@ abstract class AssociationMapping public $isCascadeRemove; /** - * READ-ONLY: Whether the association cascades save() operations from the source entity + * READ-ONLY: Whether the association cascades persist() operations from the source entity * to the target entity/entities. * * @var boolean diff --git a/lib/Doctrine/ORM/Mapping/Driver/Driver.php b/lib/Doctrine/ORM/Mapping/Driver/Driver.php index 1faaac964..b6cfe36b4 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/Driver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/Driver.php @@ -26,12 +26,9 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * Contract for metadata drivers. * - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.com - * @since 2.0 - * @version $Revision: 1393 $ - * @author Jonathan H. Wage - * @todo Rename: MetadataDriver + * @since 2.0 + * @author Jonathan H. Wage + * @todo Rename: MetadataDriver or MappingDriver */ interface Driver { diff --git a/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php b/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php index cd2a727fe..3abf3480c 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php @@ -1,7 +1,5 @@ - * @author Guilherme Blanco - * @author Jonathan H. Wage - * @author Roman Borschel + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + * @todo Rename: MappingDriverChain or MetadataDriverChain */ class DriverChain implements Driver { diff --git a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php index 35f7e451f..21bdafe20 100644 --- a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php +++ b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php @@ -37,6 +37,7 @@ namespace Doctrine\ORM\Mapping; * @since 2.0 * @author Roman Borschel * @author Giorgio Sironi + * @todo Potentially remove if assoc mapping objects get replaced by simple arrays. */ class ManyToManyMapping extends AssociationMapping { @@ -140,39 +141,11 @@ class ManyToManyMapping extends AssociationMapping * @param object The owner of the collection. * @param object The collection to populate. * @param array + * @todo Remove */ public function load($sourceEntity, $targetCollection, $em, array $joinColumnValues = array()) { - $sourceClass = $em->getClassMetadata($this->sourceEntityName); - $joinTableConditions = array(); - if ($this->isOwningSide) { - foreach ($this->relationToSourceKeyColumns as $relationKeyColumn => $sourceKeyColumn) { - // getting id - if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { - $joinTableConditions[$relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); - } else { - throw MappingException::joinColumnMustPointToMappedField( - $sourceClass->name, $sourceKeyColumn - ); - } - } - } else { - $owningAssoc = $em->getClassMetadata($this->targetEntityName)->associationMappings[$this->mappedBy]; - // TRICKY: since the association is inverted source and target are flipped - foreach ($owningAssoc->relationToTargetKeyColumns as $relationKeyColumn => $sourceKeyColumn) { - // getting id - if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { - $joinTableConditions[$relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); - } else { - throw MappingException::joinColumnMustPointToMappedField( - $sourceClass->name, $sourceKeyColumn - ); - } - } - } - - $persister = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName); - $persister->loadManyToManyCollection($this, $joinTableConditions, $targetCollection); + $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->loadManyToManyCollection($this, $sourceEntity, $targetCollection); } /** {@inheritdoc} */ diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index 8881a3c71..561f66bbd 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -1,7 +1,5 @@ * @author Giorgio Sironi * @since 2.0 + * @todo Potentially remove if assoc mapping objects get replaced by simple arrays. */ class OneToManyMapping extends AssociationMapping { @@ -99,8 +100,6 @@ class OneToManyMapping extends AssociationMapping /** * {@inheritdoc} - * - * @override */ public function isOneToMany() { @@ -115,24 +114,11 @@ class OneToManyMapping extends AssociationMapping * @param $em The EntityManager to use. * @param $joinColumnValues * @return void + * @todo Remove */ public function load($sourceEntity, $targetCollection, $em, array $joinColumnValues = array()) { - $persister = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName); - // a one-to-many is always inverse (does not have foreign key) - $sourceClass = $em->getClassMetadata($this->sourceEntityName); - $owningAssoc = $em->getClassMetadata($this->targetEntityName)->associationMappings[$this->mappedBy]; - // TRICKY: since the association is specular source and target are flipped - foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { - // getting id - if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { - $conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); - } else { - $conditions[$targetKeyColumn] = $joinColumnValues[$sourceKeyColumn]; - } - } - - $persister->loadOneToManyCollection($this, $conditions, $targetCollection); + $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->loadOneToManyCollection($this, $sourceEntity, $targetCollection); } /** diff --git a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php index 89a97449e..918228060 100644 --- a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php @@ -37,6 +37,7 @@ namespace Doctrine\ORM\Mapping; * @since 2.0 * @author Roman Borschel * @author Giorgio Sironi + * @todo Potentially remove if assoc mapping objects get replaced by simple arrays. */ class OneToOneMapping extends AssociationMapping { @@ -133,57 +134,11 @@ class OneToOneMapping extends AssociationMapping * @param object $targetEntity the entity to load data in * @param EntityManager $em * @param array $joinColumnValues Values of the join columns of $sourceEntity. + * @todo Remove */ public function load($sourceEntity, $targetEntity, $em, array $joinColumnValues = array()) { - $targetClass = $em->getClassMetadata($this->targetEntityName); - - if ($this->isOwningSide) { - // Mark inverse side as fetched in the hints, otherwise the UoW would - // try to load it in a separate query (remember: to-one inverse sides can not be lazy). - $hints = array(); - if ($this->inversedBy) { - $hints['fetched'][$targetClass->name][$this->inversedBy] = true; - if ($targetClass->subClasses) { - foreach ($targetClass->subClasses as $targetSubclassName) { - $hints['fetched'][$targetSubclassName][$this->inversedBy] = true; - } - } - } - /* cascade read-only status - if ($em->getUnitOfWork()->isReadOnly($sourceEntity)) { - $hints[Query::HINT_READ_ONLY] = true; - } - */ - - $targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($joinColumnValues, $targetEntity, $this, $hints); - - if ($targetEntity !== null && $this->inversedBy && ! $targetClass->isCollectionValuedAssociation($this->inversedBy)) { - $targetClass->reflFields[$this->inversedBy]->setValue($targetEntity, $sourceEntity); - } - } else { - $conditions = array(); - $sourceClass = $em->getClassMetadata($this->sourceEntityName); - $owningAssoc = $targetClass->getAssociationMapping($this->mappedBy); - // TRICKY: since the association is specular source and target are flipped - foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { - if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { - $conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); - } else { - throw MappingException::joinColumnMustPointToMappedField( - $sourceClass->name, $sourceKeyColumn - ); - } - } - - $targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity, $this); - - if ($targetEntity !== null) { - $targetClass->setFieldValue($targetEntity, $this->mappedBy, $sourceEntity); - } - } - - return $targetEntity; + return $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->loadOneToOneEntity($this, $sourceEntity, $targetEntity, $joinColumnValues); } /** diff --git a/lib/Doctrine/ORM/NoResultException.php b/lib/Doctrine/ORM/NoResultException.php index 1312ea1e3..80f08bb93 100644 --- a/lib/Doctrine/ORM/NoResultException.php +++ b/lib/Doctrine/ORM/NoResultException.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM; diff --git a/lib/Doctrine/ORM/NonUniqueResultException.php b/lib/Doctrine/ORM/NonUniqueResultException.php index 601de023f..811df75c1 100644 --- a/lib/Doctrine/ORM/NonUniqueResultException.php +++ b/lib/Doctrine/ORM/NonUniqueResultException.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM; diff --git a/lib/Doctrine/ORM/ORMException.php b/lib/Doctrine/ORM/ORMException.php index 4d39d1e47..abea24363 100644 --- a/lib/Doctrine/ORM/ORMException.php +++ b/lib/Doctrine/ORM/ORMException.php @@ -1,4 +1,21 @@ . + */ namespace Doctrine\ORM; diff --git a/lib/Doctrine/ORM/OptimisticLockException.php b/lib/Doctrine/ORM/OptimisticLockException.php index 796a7588c..ad6cde1ea 100644 --- a/lib/Doctrine/ORM/OptimisticLockException.php +++ b/lib/Doctrine/ORM/OptimisticLockException.php @@ -1,7 +1,5 @@ columnNames[$field]; - $sql = $this->_getSQLTableAlias($class) . '.' . $class->getQuotedColumnName($field, $this->_platform); + $sql = $this->_getSQLTableAlias($class->name) . '.' . $class->getQuotedColumnName($field, $this->_platform); $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); if ( ! isset($this->_resultColumnNames[$columnAlias])) { $this->_resultColumnNames[$columnAlias] = $columnName; diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index 9a68749b7..fd3f9097b 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -1,7 +1,5 @@ Class Table Inheritance strategy. * * @author Roman Borschel - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.org * @since 2.0 * @see http://martinfowler.com/eaaCatalog/classTableInheritance.html */ @@ -116,19 +113,19 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $this->_class : $this->_em->getClassMetadata($this->_class->rootEntityName); $rootPersister = $this->_em->getUnitOfWork()->getEntityPersister($rootClass->name); $rootTableName = $rootClass->table['name']; - $rootTableStmt = $this->_conn->prepare($rootPersister->getInsertSQL()); + $rootTableStmt = $this->_conn->prepare($rootPersister->_getInsertSQL()); // Prepare statements for sub tables. $subTableStmts = array(); if ($rootClass !== $this->_class) { - $subTableStmts[$this->_class->table['name']] = $this->_conn->prepare($this->getInsertSQL()); + $subTableStmts[$this->_class->table['name']] = $this->_conn->prepare($this->_getInsertSQL()); } foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $parentTableName = $parentClass->table['name']; if ($parentClass !== $rootClass) { $parentPersister = $this->_em->getUnitOfWork()->getEntityPersister($parentClassName); - $subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->getInsertSQL()); + $subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->_getInsertSQL()); } } @@ -231,27 +228,30 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister /** * {@inheritdoc} */ - protected function _getSelectEntitiesSQL(array &$criteria, $assoc = null, $orderBy = null) + protected function _getSelectEntitiesSQL(array $criteria, $assoc = null) { $idColumns = $this->_class->getIdentifierColumnNames(); - $baseTableAlias = $this->_getSQLTableAlias($this->_class); + $baseTableAlias = $this->_getSQLTableAlias($this->_class->name); + // Create the column list fragment only once if ($this->_selectColumnListSql === null) { // Add regular columns $columnList = ''; foreach ($this->_class->fieldMappings as $fieldName => $mapping) { if ($columnList != '') $columnList .= ', '; $columnList .= $this->_getSelectColumnSQL($fieldName, - isset($mapping['inherited']) ? $this->_em->getClassMetadata($mapping['inherited']) : $this->_class); + isset($mapping['inherited']) ? + $this->_em->getClassMetadata($mapping['inherited']) : + $this->_class); } // Add foreign key columns - foreach ($this->_class->associationMappings as $assoc) { - if ($assoc->isOwningSide && $assoc->isOneToOne()) { - $tableAlias = $assoc->inherited ? - $this->_getSQLTableAlias($this->_em->getClassMetadata($assoc->inherited)) + foreach ($this->_class->associationMappings as $assoc2) { + if ($assoc2->isOwningSide && $assoc2->isOneToOne()) { + $tableAlias = $assoc2->inherited ? + $this->_getSQLTableAlias($assoc2->inherited) : $baseTableAlias; - foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { + foreach ($assoc2->targetToSourceKeyColumns as $srcColumn) { $columnAlias = $srcColumn . $this->_sqlAliasCounter++; $columnList .= ", $tableAlias.$srcColumn AS $columnAlias"; $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); @@ -262,12 +262,12 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister } } - // Add discriminator column (DO NOT ALIAS THIS COLUMN, see StandardEntityPersister#_processSQLResultInheritanceAware). + // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#_processSQLResult). $discrColumn = $this->_class->discriminatorColumn['name']; if ($this->_class->rootEntityName == $this->_class->name) { $columnList .= ", $baseTableAlias.$discrColumn"; } else { - $columnList .= ', ' . $this->_getSQLTableAlias($this->_em->getClassMetadata($this->_class->rootEntityName)) + $columnList .= ', ' . $this->_getSQLTableAlias($this->_class->rootEntityName) . ".$discrColumn"; } @@ -279,7 +279,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $joinSql = ''; foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); - $tableAlias = $this->_getSQLTableAlias($parentClass); + $tableAlias = $this->_getSQLTableAlias($parentClassName); $joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; foreach ($idColumns as $idColumn) { @@ -291,7 +291,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // OUTER JOIN sub tables foreach ($this->_class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); - $tableAlias = $this->_getSQLTableAlias($subClass); + $tableAlias = $this->_getSQLTableAlias($subClassName); if ($this->_selectColumnListSql === null) { // Add subclass columns @@ -326,27 +326,14 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister } } - $conditionSql = ''; - foreach ($criteria as $field => $value) { - if ($conditionSql != '') $conditionSql .= ' AND '; - if (isset($this->_class->fieldMappings[$field]['inherited'])) { - $conditionSql .= $this->_getSQLTableAlias($this->_em->getClassMetadata($this->_class->fieldMappings[$field]['inherited'])) . '.'; - } else { - $conditionSql .= $baseTableAlias . '.'; - } - if (isset($this->_class->columnNames[$field])) { - $conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform); - } else if ($assoc !== null) { - $conditionSql .= $field; - } else { - throw ORMException::unrecognizedField($field); - } - $conditionSql .= ' = ?'; - } + $joinSql .= $assoc != null && $assoc->isManyToMany() ? + $this->_getSelectManyToManyJoinSQL($assoc) : ''; + + $conditionSql = $this->_getSelectConditionSQL($criteria, $assoc); $orderBySql = ''; - if ($orderBy !== null) { - $orderBySql = $this->_getCollectionOrderBySQL($orderBy, $baseTableAlias); + if ($assoc != null && isset($assoc->orderBy)) { + $orderBySql = $this->_getCollectionOrderBySQL($assoc->orderBy, $baseTableAlias); } if ($this->_selectColumnListSql === null) { @@ -359,10 +346,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister . ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql; } - /** Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */ + /* Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */ protected function _getSelectColumnListSQL() { - throw new \BadMethodCallException("Illegal invocation of ".__METHOD__." on JoinedSubclassPersister."); + throw new \BadMethodCallException("Illegal invocation of ".__METHOD__."."); } /** {@inheritdoc} */ @@ -398,17 +385,4 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister return $columns; } - - /** - * Gets the SQL to select a collection of entities in a many-many association. - * - * @param ManyToManyMapping $manyToMany - * @param array $criteria - * @return string - */ - protected function _getSelectManyToManyEntityCollectionSQL($manyToMany, array &$criteria) - { - // @todo - throw new \BadMethodCallException("Not yet implemented, see http://www.doctrine-project.org/jira/browse/DDC-342"); - } } diff --git a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php index 5f5a8a4d7..3f7574cba 100644 --- a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php +++ b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php @@ -41,24 +41,25 @@ class SingleTablePersister extends AbstractEntityInheritancePersister protected function _getSelectColumnListSQL() { $columnList = parent::_getSelectColumnListSQL(); + // Append discriminator column $discrColumn = $this->_class->discriminatorColumn['name']; $columnList .= ", $discrColumn"; $rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName); - $tableAlias = $this->_getSQLTableAlias($rootClass); + $tableAlias = $this->_getSQLTableAlias($rootClass->name); $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn); $this->_resultColumnNames[$resultColumnName] = $discrColumn; + // Append subclass columns foreach ($this->_class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); - // Append subclass columns + // Regular columns foreach ($subClass->fieldMappings as $fieldName => $mapping) { if ( ! isset($mapping['inherited'])) { $columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass); } } - - // Append subclass foreign keys + // Foreign key columns foreach ($subClass->associationMappings as $assoc) { if ($assoc->isOwningSide && $assoc->isOneToOne() && ! $assoc->inherited) { foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { @@ -87,14 +88,27 @@ class SingleTablePersister extends AbstractEntityInheritancePersister } /** {@inheritdoc} */ - protected function _getSQLTableAlias(ClassMetadata $class) + protected function _getSQLTableAlias($className) { - if (isset($this->_sqlTableAliases[$class->rootEntityName])) { - return $this->_sqlTableAliases[$class->rootEntityName]; - } - $tableAlias = $this->_em->getClassMetadata($class->rootEntityName)->table['name'][0] . $this->_sqlAliasCounter++; - $this->_sqlTableAliases[$class->rootEntityName] = $tableAlias; + return parent::_getSQLTableAlias($this->_class->rootEntityName); + } - return $tableAlias; + /** {@inheritdoc} */ + protected function _getSelectConditionSQL(array $criteria, $assoc = null) + { + $conditionSql = parent::_getSelectConditionSQL($criteria, $assoc); + + // Append discriminator condition + if ($conditionSql) $conditionSql .= ' AND '; + $values = array($this->_conn->quote($this->_class->discriminatorValue)); + $discrValues = array_flip($this->_class->discriminatorMap); + foreach ($this->_class->subClasses as $subclassName) { + $values[] = $this->_conn->quote($discrValues[$subclassName]); + } + $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.' + . $this->_class->discriminatorColumn['name'] + . ' IN (' . implode(', ', $values) . ')'; + + return $conditionSql; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index f599cc74b..29c04c09d 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -1,7 +1,5 @@ - * @author Giorgio Sironi - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @since 2.0 + * A persister is always responsible for a single entity type. + * + * EntityPersisters are used during a UnitOfWork to apply any changes to the persistent + * state of entities onto a relational database when the UnitOfWork is committed, + * as well as for basic querying of entities and their associations (not DQL). + * + * The persisting operations that are invoked during a commit of a UnitOfWork to + * persist the persistent entity state are: + * + * - {@link addInsert} : To schedule an entity for insertion. + * - {@link executeInserts} : To execute all scheduled insertions. + * - {@link update} : To update the persistent state of an entity. + * - {@link delete} : To delete the persistent state of an entity. + * + * As can be seen from the above list, insertions are batched and executed all at once + * for increased efficiency. + * + * The querying operations invoked during a UnitOfWork, either through direct find + * requests or lazy-loading, are the following: + * + * - {@link load} : Loads (the state of) a single, managed entity. + * - {@link loadAll} : Loads multiple, managed entities. + * - {@link loadOneToOneEntity} : Loads a one/many-to-one association (lazy-loading). + * - {@link loadOneToManyCollection} : Loads a one-to-many association (lazy-loading). + * - {@link loadManyToManyCollection} : Loads a many-to-many association (lazy-loading). + * + * The BasicEntityPersister implementation provides the default behavior for + * persisting and querying entities that are mapped to a single database table. + * + * Subclasses can be created to provide custom persisting and querying strategies, + * i.e. spanning multiple tables. + * + * @author Roman Borschel + * @author Giorgio Sironi + * @since 2.0 * @todo Rename: BasicEntityPersister */ class StandardEntityPersister @@ -50,16 +82,16 @@ class StandardEntityPersister protected $_class; /** - * The underlying Connection of the used EntityManager. + * The underlying DBAL Connection of the used EntityManager. * * @var Doctrine\DBAL\Connection $conn */ protected $_conn; - + /** * The database platform. * - * @var AbstractPlatform + * @var Doctrine\DBAL\Platforms\AbstractPlatform */ protected $_platform; @@ -87,8 +119,8 @@ class StandardEntityPersister protected $_resultColumnNames = array(); /** - * The map of column names to DBAL mapping types of all prepared columns used when INSERTing - * or UPDATEing an entity. + * The map of column names to DBAL mapping types of all prepared columns used + * when INSERTing or UPDATEing an entity. * * @var array * @see _prepareInsertData($entity) @@ -143,9 +175,9 @@ class StandardEntityPersister /** * Adds an entity to the queued insertions. - * The entity remains queued until {@link executeInserts()} is invoked. + * The entity remains queued until {@link executeInserts} is invoked. * - * @param object $entity The entitiy to queue for insertion. + * @param object $entity The entity to queue for insertion. */ public function addInsert($entity) { @@ -171,7 +203,7 @@ class StandardEntityPersister $idGen = $this->_class->idGenerator; $isPostInsertId = $idGen->isPostInsertGenerator(); - $stmt = $this->_conn->prepare($this->getInsertSQL()); + $stmt = $this->_conn->prepare($this->_getInsertSQL()); $tableName = $this->_class->table['name']; foreach ($this->_queuedInserts as $entity) { @@ -209,9 +241,9 @@ class StandardEntityPersister * by the preceding INSERT statement and assigns it back in to the * entities version field. * - * @param $class - * @param $entity - * @param $id + * @param Doctrine\ORM\Mapping\ClassMetadata $class + * @param object $entity + * @param mixed $id */ protected function _assignDefaultVersionValue($class, $entity, $id) { @@ -226,7 +258,16 @@ class StandardEntityPersister } /** - * Updates an entity. + * Updates a managed entity. The entity is updated according to its current changeset + * in the running UnitOfWork. If there is no changeset, nothing is updated. + * + * The data to update is retrieved through {@link _prepareUpdateData}. + * Subclasses that override this method are supposed to obtain the update data + * in the same way, through {@link _prepareUpdateData}. + * + * Subclasses are also supposed to take care of versioning when overriding this method, + * if necessary. The {@link _updateTable} method can be used to apply the data retrieved + * from {@_prepareUpdateData} on the target tables, thereby optionally applying versioning. * * @param object $entity The entity to update. */ @@ -241,14 +282,14 @@ class StandardEntityPersister /** * Performs an UPDATE statement for an entity on a specific table. - * The UPDATE can be optionally versioned, which requires the entity to have a version field. + * The UPDATE can optionally be versioned, which requires the entity to have a version field. * * @param object $entity The entity object being updated. * @param string $tableName The name of the table to apply the UPDATE on. * @param array $updateData The map of columns to update (column => value). * @param boolean $versioned Whether the UPDATE should be versioned. */ - protected function _updateTable($entity, $tableName, $updateData, $versioned = false) + protected final function _updateTable($entity, $tableName, array $updateData, $versioned = false) { $set = $params = $types = array(); @@ -261,7 +302,7 @@ class StandardEntityPersister $params[] = $value; $types[] = $this->_columnTypes[$columnName]; } - + $where = array(); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); foreach ($this->_class->identifier as $idField) { @@ -284,7 +325,7 @@ class StandardEntityPersister $types[] = $this->_class->fieldMappings[$versionField]['type']; } - $sql = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $set) + $sql = "UPDATE $tableName SET " . implode(', ', $set) . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; $result = $this->_conn->executeUpdate($sql, $params, $types); @@ -295,7 +336,12 @@ class StandardEntityPersister } /** - * Deletes an entity. + * Deletes a managed entity. + * + * The entity to delete must be managed and have a persistent identifier. + * The deletion happens instantaneously. + * + * Subclasses may override this method to customize the semantics of entity deletion. * * @param object $entity The entity to delete. */ @@ -319,7 +365,9 @@ class StandardEntityPersister } /** - * Prepares the data changeset of an entity for database insertion. + * Prepares the changeset of an entity for database insertion (UPDATE). + * + * The changeset is obtained from the currently running UnitOfWork. * * During this preparation the array that is passed as the second parameter is filled with * => pairs, grouped by table name. @@ -333,8 +381,6 @@ class StandardEntityPersister * ) * * - * Notes to inheritors: Be sure to call parent::_prepareData($entity, $result, $isInsert); - * * @param object $entity The entity for which to prepare the data. * @return array The prepared data. */ @@ -348,7 +394,7 @@ class StandardEntityPersister } foreach ($uow->getEntityChangeSet($entity) as $field => $change) { - if ($versioned && $versionField == $field) { + if ($versioned && $versionField == $field) { //TODO: Needed? continue; } @@ -356,9 +402,9 @@ class StandardEntityPersister $newVal = $change[1]; if (isset($this->_class->associationMappings[$field])) { - $assocMapping = $this->_class->associationMappings[$field]; + $assoc = $this->_class->associationMappings[$field]; // Only owning side of x-1 associations can have a FK column. - if ( ! $assocMapping->isOwningSide || ! $assocMapping->isOneToOne()) { + if ( ! $assoc->isOwningSide || ! $assoc->isOneToOne()) { continue; } @@ -379,10 +425,10 @@ class StandardEntityPersister $newValId = $uow->getEntityIdentifier($newVal); } - $targetClass = $this->_em->getClassMetadata($assocMapping->targetEntityName); + $targetClass = $this->_em->getClassMetadata($assoc->targetEntityName); $owningTable = $this->getOwningTable($field); - foreach ($assocMapping->sourceToTargetKeyColumns as $sourceColumn => $targetColumn) { + foreach ($assoc->sourceToTargetKeyColumns as $sourceColumn => $targetColumn) { if ($newVal === null) { $result[$owningTable][$sourceColumn] = null; } else { @@ -399,6 +445,16 @@ class StandardEntityPersister return $result; } + /** + * Prepares the data changeset of a managed entity for database insertion (initial INSERT). + * The changeset of the entity is obtained from the currently running UnitOfWork. + * + * The default insert data preparation is the same as for updates. + * + * @param object $entity The entity for which to prepare the data. + * @return array The prepared data for the tables to update. + * @see _prepareUpdateData + */ protected function _prepareInsertData($entity) { return $this->_prepareUpdateData($entity); @@ -407,8 +463,12 @@ class StandardEntityPersister /** * Gets the name of the table that owns the column the given field is mapped to. * - * @param string $fieldName - * @return string + * The default implementation in BasicEntityPersister always returns the name + * of the table the entity type of this persister is mapped to, since an entity + * is always persisted to a single table with a BasicEntityPersister. + * + * @param string $fieldName The field name. + * @return string The table name. */ public function getOwningTable($fieldName) { @@ -423,13 +483,13 @@ class StandardEntityPersister * a new entity is created. * @param $assoc The association that connects the entity to load to another entity, if any. * @param array $hints Hints for entity creation. - * @return The loaded entity instance or NULL if the entity/the data can not be found. + * @return object The loaded and managed entity instance or NULL if the entity can not be found. + * @todo Check identity map? loadById method? Try to guess whether $criteria is the id? */ public function load(array $criteria, $entity = null, $assoc = null, array $hints = array()) { $sql = $this->_getSelectEntitiesSQL($criteria, $assoc); - $params = array_values($criteria); - $stmt = $this->_conn->executeQuery($sql, $params); + $stmt = $this->_conn->executeQuery($sql, array_values($criteria)); $result = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); @@ -437,17 +497,80 @@ class StandardEntityPersister } /** - * Refreshes an entity. + * Loads an entity of this persister's mapped class as part of a single-valued + * association from another entity. + * + * @param OneToOneMapping $assoc The association to load. + * @param object $sourceEntity The entity that owns the association (not necessarily the "owning side"). + * @param object $targetEntity The existing ghost entity (proxy) to load, if any. + * @param array $identifier The identifier of the entity to load. Must be provided if + * the association to load represents the owning side, otherwise + * the identifier is derived from the $sourceEntity. + * @return object The loaded and managed entity instance or NULL if the entity can not be found. + */ + public function loadOneToOneEntity(OneToOneMapping $assoc, $sourceEntity, $targetEntity, array $identifier = array()) + { + $targetClass = $this->_em->getClassMetadata($assoc->targetEntityName); + + if ($assoc->isOwningSide) { + // Mark inverse side as fetched in the hints, otherwise the UoW would + // try to load it in a separate query (remember: to-one inverse sides can not be lazy). + $hints = array(); + if ($assoc->inversedBy) { + $hints['fetched'][$targetClass->name][$assoc->inversedBy] = true; + if ($targetClass->subClasses) { + foreach ($targetClass->subClasses as $targetSubclassName) { + $hints['fetched'][$targetSubclassName][$assoc->inversedBy] = true; + } + } + } + /* cascade read-only status + if ($this->_em->getUnitOfWork()->isReadOnly($sourceEntity)) { + $hints[Query::HINT_READ_ONLY] = true; + } + */ + + $targetEntity = $this->load($identifier, $targetEntity, $assoc, $hints); + + // Complete bidirectional association, if necessary + if ($targetEntity !== null && $assoc->inversedBy && ! $targetClass->isCollectionValuedAssociation($assoc->inversedBy)) { + $targetClass->reflFields[$assoc->inversedBy]->setValue($targetEntity, $sourceEntity); + } + } else { + $sourceClass = $this->_em->getClassMetadata($assoc->sourceEntityName); + $owningAssoc = $targetClass->getAssociationMapping($assoc->mappedBy); + // TRICKY: since the association is specular source and target are flipped + foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { + if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { + $identifier[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); + } else { + throw MappingException::joinColumnMustPointToMappedField( + $sourceClass->name, $sourceKeyColumn + ); + } + } + + $targetEntity = $this->load($identifier, $targetEntity, $assoc); + + if ($targetEntity !== null) { + $targetClass->setFieldValue($targetEntity, $assoc->mappedBy, $sourceEntity); + } + } + + return $targetEntity; + } + + /** + * Refreshes a managed entity. * - * @param array $id The identifier of the entity as an associative array from column names to values. + * @param array $id The identifier of the entity as an associative array from + * column or field names to values. * @param object $entity The entity to refresh. */ public function refresh(array $id, $entity) { $sql = $this->_getSelectEntitiesSQL($id); - $params = array_values($id); - - $stmt = $this->_conn->executeQuery($sql, $params); + $stmt = $this->_conn->executeQuery($sql, array_values($id)); $result = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); @@ -527,10 +650,8 @@ class StandardEntityPersister public function loadAll(array $criteria = array()) { $entities = array(); - $sql = $this->_getSelectEntitiesSQL($criteria); - $params = array_values($criteria); - $stmt = $this->_conn->executeQuery($sql, $params); + $stmt = $this->_conn->executeQuery($sql, array_values($criteria)); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); $stmt->closeCursor(); @@ -541,37 +662,44 @@ class StandardEntityPersister return $entities; } - /** - * Loads a collection of entities in a one-to-many association. - * - * @param OneToManyMapping $assoc - * @param array $criteria The criteria by which to select the entities. - * @param PersistentCollection The collection to fill. - */ - public function loadOneToManyCollection($assoc, array $criteria, PersistentCollection $coll) - { - $owningAssoc = $this->_class->associationMappings[$coll->getMapping()->mappedBy]; - $sql = $this->_getSelectEntitiesSQL($criteria, $owningAssoc, $assoc->orderBy); - $params = array_values($criteria); - $stmt = $this->_conn->executeQuery($sql, $params); - while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { - $coll->hydrateAdd($this->_createEntity($result)); - } - $stmt->closeCursor(); - } - /** * Loads a collection of entities of a many-to-many association. * - * @param ManyToManyMapping $assoc - * @param array $criteria + * @param ManyToManyMapping $assoc The association mapping of the association being loaded. + * @param object $sourceEntity The entity that owns the collection. * @param PersistentCollection $coll The collection to fill. */ - public function loadManyToManyCollection($assoc, array $criteria, PersistentCollection $coll) + public function loadManyToManyCollection(ManyToManyMapping $assoc, $sourceEntity, PersistentCollection $coll) { - $sql = $this->_getSelectManyToManyEntityCollectionSQL($assoc, $criteria); - $params = array_values($criteria); - $stmt = $this->_conn->executeQuery($sql, $params); + $criteria = array(); + $sourceClass = $this->_em->getClassMetadata($assoc->sourceEntityName); + $joinTableConditions = array(); + if ($assoc->isOwningSide) { + foreach ($assoc->relationToSourceKeyColumns as $relationKeyColumn => $sourceKeyColumn) { + if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { + $criteria[$relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); + } else { + throw MappingException::joinColumnMustPointToMappedField( + $sourceClass->name, $sourceKeyColumn + ); + } + } + } else { + $owningAssoc = $this->_em->getClassMetadata($assoc->targetEntityName)->associationMappings[$assoc->mappedBy]; + // TRICKY: since the association is inverted source and target are flipped + foreach ($owningAssoc->relationToTargetKeyColumns as $relationKeyColumn => $sourceKeyColumn) { + if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { + $criteria[$relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); + } else { + throw MappingException::joinColumnMustPointToMappedField( + $sourceClass->name, $sourceKeyColumn + ); + } + } + } + + $sql = $this->_getSelectEntitiesSQL($criteria, $assoc); + $stmt = $this->_conn->executeQuery($sql, array_values($criteria)); while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { $coll->hydrateAdd($this->_createEntity($result)); } @@ -613,10 +741,15 @@ class StandardEntityPersister /** * Processes an SQL result set row that contains data for an entity of the type * this persister is responsible for. + * + * Subclasses are supposed to override this method if they need to change the + * hydration procedure for entities loaded through basic find operations or + * lazy-loading (not DQL). * * @param array $sqlResult The SQL result set row to process. * @return array A tuple where the first value is the actual type of the entity and - * the second value the prepared data of the entity. + * the second value the prepared data of the entity (a map from field + * names to values). */ protected function _processSQLResult(array $sqlResult) { @@ -640,51 +773,37 @@ class StandardEntityPersister * * @param array $criteria * @param AssociationMapping $assoc - * @param string $orderBy * @return string + * @todo Refactor: _getSelectSQL(...) */ - protected function _getSelectEntitiesSQL(array &$criteria, $assoc = null, $orderBy = null) + protected function _getSelectEntitiesSQL(array $criteria, $assoc = null) { - // Construct WHERE conditions - $conditionSql = ''; - foreach ($criteria as $field => $value) { - if ($conditionSql != '') { - $conditionSql .= ' AND '; - } - - if (isset($this->_class->columnNames[$field])) { - $conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform); - } else if (isset($this->_class->fieldNames[$field])) { - $conditionSql .= $this->_class->getQuotedColumnName($this->_class->fieldNames[$field], $this->_platform); - } else if ($assoc !== null) { - $conditionSql .= $field; - } else { - throw ORMException::unrecognizedField($field); - } - $conditionSql .= ' = ?'; - } + $joinSql = $assoc != null && $assoc->isManyToMany() ? + $this->_getSelectManyToManyJoinSQL($assoc) : ''; - $orderBySql = ''; - if ($orderBy !== null) { - $orderBySql = $this->_getCollectionOrderBySQL( - $orderBy, $this->_getSQLTableAlias($this->_class) - ); - } + $conditionSql = $this->_getSelectConditionSQL($criteria, $assoc); + + $orderBySql = $assoc !== null && isset($assoc->orderBy) ? + $this->_getCollectionOrderBySQL($assoc->orderBy, $this->_getSQLTableAlias($this->_class->name)) + : ''; return 'SELECT ' . $this->_getSelectColumnListSQL() . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' - . $this->_getSQLTableAlias($this->_class) - . ($conditionSql ? ' WHERE ' . $conditionSql : '') . $orderBySql; + . $this->_getSQLTableAlias($this->_class->name) + . $joinSql + . ($conditionSql ? ' WHERE ' . $conditionSql : '') + . $orderBySql; } /** - * Generate ORDER BY SQL snippet for ordered collections. + * Gets the ORDER BY SQL snippet for ordered collections. * * @param array $orderBy * @param string $baseTableAlias * @return string + * @todo Rename: _getOrderBySQL */ - protected function _getCollectionOrderBySQL(array $orderBy, $baseTableAlias) + protected final function _getCollectionOrderBySQL(array $orderBy, $baseTableAlias) { $orderBySql = ''; foreach ($orderBy as $fieldName => $orientation) { @@ -693,27 +812,28 @@ class StandardEntityPersister } $tableAlias = isset($this->_class->fieldMappings[$fieldName]['inherited']) ? - $this->_getSQLTableAlias($this->_em->getClassMetadata($this->_class->fieldMappings[$fieldName]['inherited'])) + $this->_getSQLTableAlias($this->_class->fieldMappings[$fieldName]['inherited']) : $baseTableAlias; $columnName = $this->_class->getQuotedColumnName($fieldName, $this->_platform); - if ($orderBySql != '') { - $orderBySql .= ', '; - } else { - $orderBySql = ' ORDER BY '; - } + $orderBySql .= $orderBySql ? ', ' : 'ORDER BY '; $orderBySql .= $tableAlias . '.' . $columnName . ' ' . $orientation; } return $orderBySql; } - + /** * Gets the SQL fragment with the list of columns to select when querying for - * an entity within this persister. + * an entity in this persister. + * + * Subclasses should override this method to alter or change the select column + * list SQL fragment. Note that in the implementation of StandardEntityPersister + * the resulting SQL fragment is generated only once and cached in {@link _selectColumnListSql}. + * Subclasses may or may not do the same. * * @return string The SQL fragment. - * @todo Rename: _getSelectColumnListSQL() + * @todo Rename: _getSelectColumnsSQL() */ protected function _getSelectColumnListSQL() { @@ -725,7 +845,7 @@ class StandardEntityPersister // Add regular columns to select list foreach ($this->_class->fieldNames as $field) { - if ($columnList != '') $columnList .= ', '; + if ($columnList) $columnList .= ', '; $columnList .= $this->_getSelectColumnSQL($field, $this->_class); } @@ -733,15 +853,15 @@ class StandardEntityPersister return $this->_selectColumnListSql; } - + /** - * Gets the SQL to select a collection of entities in a many-many association. + * Gets the SQL join fragment used when selecting entities from a + * many-to-many association. * * @param ManyToManyMapping $manyToMany - * @param array $criteria * @return string */ - protected function _getSelectManyToManyEntityCollectionSQL($manyToMany, array &$criteria) + protected function _getSelectManyToManyJoinSQL(ManyToManyMapping $manyToMany) { if ($manyToMany->isOwningSide) { $owningAssoc = $manyToMany; @@ -756,32 +876,12 @@ class StandardEntityPersister $joinSql = ''; foreach ($joinClauses as $joinTableColumn => $sourceColumn) { if ($joinSql != '') $joinSql .= ' AND '; - $joinSql .= $this->_getSQLTableAlias($this->_class) . + $joinSql .= $this->_getSQLTableAlias($this->_class->name) . '.' . $this->_class->getQuotedColumnName($this->_class->fieldNames[$sourceColumn], $this->_platform) . ' = ' . $joinTableName . '.' . $joinTableColumn; } - $joinSql = ' INNER JOIN ' . $joinTableName . ' ON ' . $joinSql; - - $conditionSql = ''; - foreach ($criteria as $joinColumn => $value) { - if ($conditionSql != '') $conditionSql .= ' AND '; - $columnName = $joinTableName . '.' . $joinColumn; - $conditionSql .= $columnName . ' = ?'; - } - - $orderBySql = ''; - if ($manyToMany->orderBy !== null) { - $orderBySql = $this->_getCollectionOrderBySQL( - $manyToMany->orderBy, $this->_getSQLTableAlias($this->_class) - ); - } - - return 'SELECT ' . $this->_getSelectColumnListSQL() - . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' - . $this->_getSQLTableAlias($this->_class) - . $joinSql - . ' WHERE ' . $conditionSql . $orderBySql; + return " INNER JOIN $joinTableName ON $joinSql"; } /** @@ -789,19 +889,36 @@ class StandardEntityPersister * * @return string */ - public function getInsertSQL() + protected function _getInsertSQL() { if ($this->_insertSql === null) { - $this->_insertSql = $this->_generateInsertSQL(); + $insertSql = ''; + $columns = $this->_getInsertColumnList(); + if (empty($columns)) { + $insertSql = $this->_platform->getEmptyIdentityInsertSQL( + $this->_class->getQuotedTableName($this->_platform), + $this->_class->getQuotedColumnName($this->_class->identifier[0], $this->_platform) + ); + } else { + $columns = array_unique($columns); + $values = array_fill(0, count($columns), '?'); + + $insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform) + . ' (' . implode(', ', $columns) . ') ' + . 'VALUES (' . implode(', ', $values) . ')'; + } + $this->_insertSql = $insertSql; } return $this->_insertSql; } /** * Gets the list of columns to put in the INSERT SQL statement. - * + * + * Subclasses should override this method to alter or change the list of + * columns placed in the INSERT statements used by the persister. + * * @return array The list of columns. - * @internal INSERT SQL is cached by getInsertSQL() per request. */ protected function _getInsertColumnList() { @@ -826,33 +943,6 @@ class StandardEntityPersister return $columns; } - /** - * Generates the INSERT SQL used by the persister to persist entities. - * - * @return string - * @internal Result is cached by getInsertSQL() per request. - */ - protected function _generateInsertSQL() - { - $insertSql = ''; - $columns = $this->_getInsertColumnList(); - if (empty($columns)) { - $insertSql = $this->_platform->getEmptyIdentityInsertSQL( - $this->_class->getQuotedTableName($this->_platform), - $this->_class->getQuotedColumnName($this->_class->identifier[0], $this->_platform) - ); - } else { - $columns = array_unique($columns); - $values = array_fill(0, count($columns), '?'); - - $insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform) - . ' (' . implode(', ', $columns) . ') ' - . 'VALUES (' . implode(', ', $values) . ')'; - } - - return $insertSql; - } - /** * Gets the SQL snippet of a qualified column name for the given field name. * @@ -863,7 +953,7 @@ class StandardEntityPersister protected function _getSelectColumnSQL($field, ClassMetadata $class) { $columnName = $class->columnNames[$field]; - $sql = $this->_getSQLTableAlias($class) . '.' . $class->getQuotedColumnName($field, $this->_platform); + $sql = $this->_getSQLTableAlias($class->name) . '.' . $class->getQuotedColumnName($field, $this->_platform); $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); if ( ! isset($this->_resultColumnNames[$columnAlias])) { $this->_resultColumnNames[$columnAlias] = $columnName; @@ -875,17 +965,19 @@ class StandardEntityPersister /** * Gets the SQL snippet for all join columns of the given class that are to be * placed in an SQL SELECT statement. - * + * + * @param $class * @return string + * @todo Not reused... inline? */ - protected function _getSelectJoinColumnsSQL(ClassMetadata $class) + private function _getSelectJoinColumnsSQL(ClassMetadata $class) { $sql = ''; foreach ($class->associationMappings as $assoc) { if ($assoc->isOwningSide && $assoc->isOneToOne()) { foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { $columnAlias = $srcColumn . $this->_sqlAliasCounter++; - $sql .= ', ' . $this->_getSQLTableAlias($this->_class) . ".$srcColumn AS $columnAlias"; + $sql .= ', ' . $this->_getSQLTableAlias($this->_class->name) . ".$srcColumn AS $columnAlias"; $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); if ( ! isset($this->_resultColumnNames[$resultColumnName])) { $this->_resultColumnNames[$resultColumnName] = $srcColumn; @@ -898,19 +990,92 @@ class StandardEntityPersister } /** - * Gets the SQL table alias for the given class. + * Gets the SQL table alias for the given class name. * - * @param ClassMetadata $class + * @param string $className * @return string The SQL table alias. + * @todo Remove. Binding table aliases to class names is not such a good idea. */ - protected function _getSQLTableAlias(ClassMetadata $class) + protected function _getSQLTableAlias($className) { - if (isset($this->_sqlTableAliases[$class->name])) { - return $this->_sqlTableAliases[$class->name]; + if (isset($this->_sqlTableAliases[$className])) { + return $this->_sqlTableAliases[$className]; } - $tableAlias = $class->table['name'][0] . $this->_sqlAliasCounter++; - $this->_sqlTableAliases[$class->name] = $tableAlias; + $tableAlias = 't' . $this->_sqlAliasCounter++; + $this->_sqlTableAliases[$className] = $tableAlias; return $tableAlias; } + + /** + * Gets the conditional SQL fragment used in the WHERE clause when selecting + * entities in this persister. + * + * Subclasses are supposed to override this method if they intend to change + * or alter the criteria by which entities are selected. + * + * @param array $criteria + * @param AssociationMapping $assoc + * @return string + */ + protected function _getSelectConditionSQL(array $criteria, $assoc = null) + { + $conditionSql = ''; + foreach ($criteria as $field => $value) { + $conditionSql .= $conditionSql ? ' AND ' : ''; + + if (isset($this->_class->columnNames[$field])) { + if (isset($this->_class->fieldMappings[$field]['inherited'])) { + $conditionSql .= $this->_getSQLTableAlias($this->_class->fieldMappings[$field]['inherited']) . '.'; + } else { + $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.'; + } + $conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform); + } else if ($assoc !== null) { + if ($assoc->isManyToMany()) { + $owningAssoc = $assoc->isOwningSide ? $assoc : $this->_em->getClassMetadata($assoc->targetEntityName) + ->associationMappings[$assoc->mappedBy]; + $conditionSql .= $owningAssoc->getQuotedJoinTableName($this->_platform) . '.' . $field; + } else { + $conditionSql .= $field; + } + } else { + throw ORMException::unrecognizedField($field); + } + $conditionSql .= ' = ?'; + } + + return $conditionSql; + } + + /** + * Loads a collection of entities in a one-to-many association. + * + * @param OneToManyMapping $assoc + * @param array $criteria The criteria by which to select the entities. + * @param PersistentCollection The collection to load/fill. + */ + public function loadOneToManyCollection(OneToManyMapping $assoc, $sourceEntity, PersistentCollection $coll) + { + $criteria = array(); + $owningAssoc = $this->_class->associationMappings[$assoc->mappedBy]; + $sourceClass = $this->_em->getClassMetadata($assoc->sourceEntityName); + foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { + $criteria[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); + } + + $sql = $this->_getSelectEntitiesSQL($criteria, $assoc); + $params = array_values($criteria); + $stmt = $this->_conn->executeQuery($sql, $params); + while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { + $coll->hydrateAdd($this->_createEntity($result)); + } + $stmt->closeCursor(); + } + + //TODO + /*protected function _getOneToOneEagerFetchSQL() + { + + }*/ } diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index d4e255dcb..fceab4023 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -1,7 +1,5 @@ ProxyFactory class that is - * connected to the given EntityManager. - * - * @param EntityManager $em The EntityManager the new factory works for. - * @param string $proxyDir The directory to use for the proxy classes. It must exist. - * @param string $proxyNs The namespace to use for the proxy classes. - * @param boolean $autoGenerate Whether to automatically generate proxy classes. + * Initializes a new instance of the ProxyFactory class that is + * connected to the given EntityManager. + * + * @param EntityManager $em The EntityManager the new factory works for. + * @param string $proxyDir The directory to use for the proxy classes. It must exist. + * @param string $proxyNs The namespace to use for the proxy classes. + * @param boolean $autoGenerate Whether to automatically generate proxy classes. */ public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = false) { @@ -240,7 +238,7 @@ class ProxyFactory return $sleepImpl; } - /** Reference Proxy class code template */ + /** Proxy class code template */ private static $_proxyClassTemplate = '_selectedClasses AS $dqlAlias => $class) { $qComp = $this->_queryComponents[$dqlAlias]; - if (isset($qComp['relation']) && ($qComp['relation']->isManyToMany() || $qComp['relation']->isOneToMany()) - && $qComp['relation']->orderBy != null) { - + if (isset($qComp['relation']->orderBy)) { foreach ($qComp['relation']->orderBy AS $fieldName => $orientation) { if ($qComp['metadata']->isInheritanceTypeJoined()) { $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName); @@ -299,8 +297,8 @@ class SqlWalker implements TreeWalker if ($sql != '') { $sql .= ', '; } - $sql .= $this->getSqlTableAlias($tableName, $dqlAlias) . "." . - $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . " ".$orientation; + $sql .= $this->getSqlTableAlias($tableName, $dqlAlias) . '.' . + $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . " $orientation"; } } } @@ -313,7 +311,7 @@ class SqlWalker implements TreeWalker * @param string $dqlAlias * @return string */ - private function _generateDiscriminatorColumnConditionSql($dqlAlias) + private function _generateDiscriminatorColumnConditionSQL($dqlAlias) { $sql = ''; @@ -338,7 +336,6 @@ class SqlWalker implements TreeWalker return $sql; } - /** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. * @@ -351,7 +348,7 @@ class SqlWalker implements TreeWalker if (($whereClause = $AST->whereClause) !== null) { $sql .= $this->walkWhereClause($whereClause); - } else if (($discSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias)) !== '') { + } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_currentRootAlias)) !== '') { $sql .= ' WHERE ' . $discSql; } @@ -385,7 +382,7 @@ class SqlWalker implements TreeWalker if (($whereClause = $AST->whereClause) !== null) { $sql .= $this->walkWhereClause($whereClause); - } else if (($discSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias)) !== '') { + } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_currentRootAlias)) !== '') { $sql .= ' WHERE ' . $discSql; } @@ -405,7 +402,7 @@ class SqlWalker implements TreeWalker if (($whereClause = $AST->whereClause) !== null) { $sql .= $this->walkWhereClause($whereClause); - } else if (($discSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias)) !== '') { + } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_currentRootAlias)) !== '') { $sql .= ' WHERE ' . $discSql; } @@ -780,7 +777,7 @@ class SqlWalker implements TreeWalker ). ')'; } - $discrSql = $this->_generateDiscriminatorColumnConditionSql($joinedDqlAlias); + $discrSql = $this->_generateDiscriminatorColumnConditionSQL($joinedDqlAlias); if ($discrSql) { $sql .= ' AND ' . $discrSql; @@ -1217,7 +1214,7 @@ class SqlWalker implements TreeWalker ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms) ); - $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias); + $discrSql = $this->_generateDiscriminatorColumnConditionSQL($this->_currentRootAlias); if ($discrSql) { $sql .= ' AND ' . $discrSql; diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 46628c904..9cfbeda75 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1,7 +1,5 @@ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel - * @internal This class contains performance-critical code. + * @internal This class contains highly performance-sensitive code. */ class UnitOfWork implements PropertyChangedListener { diff --git a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest2.php b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest2.php index 7256d571e..d720f5d92 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest2.php +++ b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest2.php @@ -17,9 +17,10 @@ class ClassTableInheritanceTest2 extends \Doctrine\Tests\OrmFunctionalTestCase $this->_schemaTool->createSchema(array( $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\CTIParent'), $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\CTIChild'), - $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\CTIRelated') + $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\CTIRelated'), + $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\CTIRelated2') )); - } catch (\Exception $e) { + } catch (\Exception $ignored) { // Swallow all exceptions. We do not test the schema tool here. } } @@ -49,6 +50,27 @@ class ClassTableInheritanceTest2 extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertSame($related2, $related2->getCTIParent()->getRelated()); } + + public function testManyToManyToCTIHierarchy() + { + //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger()); + $mmrel = new CTIRelated2; + $child = new CTIChild; + $child->setData('child'); + $mmrel->addCTIChild($child); + + $this->_em->persist($mmrel); + $this->_em->persist($child); + + $this->_em->flush(); + $this->_em->clear(); + + $mmrel2 = $this->_em->find(get_class($mmrel), $mmrel->getId()); + $this->assertFalse($mmrel2->getCTIChildren()->isInitialized()); + $this->assertEquals(1, count($mmrel2->getCTIChildren())); + $this->assertTrue($mmrel2->getCTIChildren()->isInitialized()); + $this->assertTrue($mmrel2->getCTIChildren()->get(0) instanceof CTIChild); + } } /** @@ -126,3 +148,29 @@ class CTIRelated { $this->ctiParent = $ctiParent; } } + +/** @Entity */ +class CTIRelated2 +{ + /** @Id @Column(type="integer") @GeneratedValue */ + private $id; + /** @ManyToMany(targetEntity="CTIChild") */ + private $ctiChildren; + + + public function __construct() { + $this->ctiChildren = new \Doctrine\Common\Collections\ArrayCollection; + } + + public function getId() { + return $this->id; + } + + public function addCTIChild(CTIChild $child) { + $this->ctiChildren->add($child); + } + + public function getCTIChildren() { + return $this->ctiChildren; + } +} diff --git a/tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php index 01583d1e0..17f01f27c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SingleTableInheritanceTest.php @@ -66,6 +66,7 @@ class SingleTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->clear(); + // READ by DQL on subtype $query = $this->_em->createQuery("select e from Doctrine\Tests\ORM\Functional\ChildEntity e"); $entities = $query->getResult(); $this->assertEquals(1, count($entities)); @@ -77,6 +78,18 @@ class SingleTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->clear(); + // READ by findAll() on subtype + $entities = $this->_em->getRepository('Doctrine\Tests\ORM\Functional\ChildEntity')->findAll(); + $this->assertEquals(1, count($entities)); + $this->assertTrue($entities[0] instanceof ChildEntity); + $this->assertTrue(is_numeric($entities[0]->getId())); + $this->assertEquals('thedata', $entities[0]->getData()); + $this->assertEquals(1234, $entities[0]->getNumber()); + $this->assertNull($entities[0]->getParentRelated()); + + $this->_em->clear(); + + // READ by joining into an STI hierarchy from outwards $query = $this->_em->createQuery("select r,o from Doctrine\Tests\ORM\Functional\RelatedEntity r join r.owner o"); $entities = $query->getResult(); @@ -194,7 +207,7 @@ class SingleTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase class ParentEntity { /** * @Id - * @Column(type="integer") + * @Column(name="parent_id", type="integer") * @GeneratedValue(strategy="AUTO") */ private $id; @@ -317,7 +330,7 @@ class ParentRelatedEntity { public function setData($data) {$this->data = $data;} /** * @OneToOne(targetEntity="ParentEntity") - * @JoinColumn(name="parent_id", referencedColumnName="id") + * @JoinColumn(name="parent_id", referencedColumnName="parent_id") */ private $parent; public function getParent() {return $this->parent;}