diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index fc2669b27..d974f1913 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -124,9 +124,9 @@ class EntityManager private $_hydrators = array(); /** - * The proxy factory which creates association or reference proxies. + * The proxy factory used to create dynamic proxies. * - * @var ProxyFactory + * @var Doctrine\ORM\Proxy\ProxyFactory */ private $_proxyFactory; diff --git a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php index d753e869e..c97b7a7e5 100644 --- a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php +++ b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php @@ -4,7 +4,7 @@ namespace Doctrine\ORM\Event; class LifecycleEventArgs extends \Doctrine\Common\EventArgs { - private $_em; + //private $_em; private $_entity; public function __construct($entity) @@ -17,8 +17,10 @@ class LifecycleEventArgs extends \Doctrine\Common\EventArgs return $this->_entity; } + /* public function getEntityManager() { return $this->_em; } + */ } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index f797f4f29..1eee52e6e 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -62,7 +62,6 @@ class ObjectHydrator extends AbstractHydrator foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->_identifierMap[$dqlAlias] = array(); - //$this->_resultPointers[$dqlAlias] = array(); $this->_idTemplate[$dqlAlias] = ''; $class = $this->_em->getClassMetadata($className); @@ -73,16 +72,26 @@ class ObjectHydrator extends AbstractHydrator // Remember which associations are "fetch joined", so that we know where to inject // collection stubs or proxies and where not. if (isset($this->_rsm->relationMap[$dqlAlias])) { - $targetClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; - $targetClass = $this->_getClassMetadata($targetClassName); - $assoc = $targetClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; - $this->_hints['fetched'][$assoc->sourceEntityName][$assoc->sourceFieldName] = true; + $sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; + $sourceClass = $this->_getClassMetadata($sourceClassName); + $assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; + $this->_hints['fetched'][$sourceClassName][$assoc->sourceFieldName] = true; + if ($sourceClass->subClasses) { + foreach ($sourceClass->subClasses as $sourceSubclassName) { + $this->_hints['fetched'][$sourceSubclassName][$assoc->sourceFieldName] = true; + } + } if ($assoc->mappedByFieldName) { - $this->_hints['fetched'][$assoc->targetEntityName][$assoc->mappedByFieldName] = true; + $this->_hints['fetched'][$className][$assoc->mappedByFieldName] = true; } else { - if (isset($targetClass->inverseMappings[$className][$assoc->sourceFieldName])) { - $inverseAssoc = $targetClass->inverseMappings[$className][$assoc->sourceFieldName]; - $this->_hints['fetched'][$assoc->targetEntityName][$inverseAssoc->sourceFieldName] = true; + if (isset($sourceClass->inverseMappings[$className][$assoc->sourceFieldName])) { + $inverseAssoc = $sourceClass->inverseMappings[$className][$assoc->sourceFieldName]; + $this->_hints['fetched'][$className][$inverseAssoc->sourceFieldName] = true; + if ($class->subClasses) { + foreach ($class->subClasses as $targetSubclassName) { + $this->_hints['fetched'][$targetSubclassName][$inverseAssoc->sourceFieldName] = true; + } + } } } } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index f75982a0c..74eb21696 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -383,8 +383,9 @@ class AnnotationDriver implements Driver } /** - * Whether the class with the specified name should have its metadata loaded. - * This is only the case if it is annotated with either @Entity or + * Whether the class with the specified name is transient. Only non-transient + * classes, that is entities and mapped superclasses, should have their metadata loaded. + * A class is non-transient if it is annotated with either @Entity or * @MappedSuperclass in the class doc block. * * @param string $className diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index dcf9330c7..e3dcf5cdd 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -121,4 +121,10 @@ class MappingException extends \Doctrine\ORM\ORMException { return new self('An error occurred in ' . $entity, 0, $previousException); } + + public static function joinColumnMustPointToMappedField($className, $joinColumn) + { + return new self('The column ' . $joinColumn . ' must be mapped to a field in class ' + . $className . ' since it is referenced by a join column of another class.'); + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php index bb157aa9c..d2afdd14e 100644 --- a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php @@ -76,7 +76,7 @@ class OneToManyMapping extends AssociationMapping { parent::_validateAndCompleteMapping($mapping); - // one-side MUST be inverse (must have mappedBy) + // OneToMany-side MUST be inverse (must have mappedBy) if ( ! isset($mapping['mappedBy'])) { throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']); } diff --git a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php index 45fe95f32..6a113ccde 100644 --- a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php @@ -221,9 +221,16 @@ class OneToOneMapping extends AssociationMapping $targetClass->inverseMappings[$this->sourceEntityName][$this->sourceFieldName]->sourceFieldName : false; + // 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 ($inverseField) { - $hints['fetched'][$targetClass->rootEntityName][$inverseField] = true; + $hints['fetched'][$targetClass->name][$inverseField] = true; + if ($targetClass->subClasses) { + foreach ($targetClass->subClasses as $targetSubclassName) { + $hints['fetched'][$targetSubclassName][$inverseField] = true; + } + } } /* cascade read-only status if ($em->getUnitOfWork()->isReadOnly($sourceEntity)) { @@ -233,7 +240,7 @@ class OneToOneMapping extends AssociationMapping $targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($joinColumnValues, $targetEntity, $this, $hints); - if ($targetEntity !== null && $inverseField && ! $targetClass->isCollectionValuedAssociation($inverseField)) { + if ($targetEntity !== null && $inverseField && ! $targetClass->isCollectionValuedAssociation($inverseField)) { $targetClass->reflFields[$inverseField]->setValue($targetEntity, $sourceEntity); } } else { @@ -245,8 +252,9 @@ class OneToOneMapping extends AssociationMapping if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { $conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); } else { - //TODO: Should never happen. Exception? - $conditions[$targetKeyColumn] = $joinColumnValues[$sourceKeyColumn]; + throw MappingException::joinColumnMustPointToMappedField( + $sourceClass->name, $sourceKeyColumn + ); } } @@ -262,7 +270,7 @@ class OneToOneMapping extends AssociationMapping } /** - * @internal Experimental. For MetaModel API, Doctrine 2.1. + * @internal Experimental. For MetaModel API, Doctrine 2.1 or later. */ public static function __set_state(array $state) { diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index 0118a5712..25ef7e607 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -53,7 +53,7 @@ class StandardEntityPersister protected $_class; /** - * The Connection instance. + * The underlying Connection of the used EntityManager. * * @var Doctrine\DBAL\Connection $conn */ @@ -289,28 +289,6 @@ class StandardEntityPersister $this->_conn->delete($this->_class->primaryTable['name'], $id); } - /** - * Adds an entity to delete. - * - * @param object $entity - * @todo Impl. - */ - public function addDelete($entity) - { - - } - - /** - * Executes all pending entity deletions. - * - * @see addDelete() - * @todo Impl. - */ - public function executeDeletions() - { - - } - /** * Gets the ClassMetadata instance of the entity class this persister is used for. * @@ -560,7 +538,7 @@ class StandardEntityPersister } /** - * Loads a collection of entities into a one-to-many association. + * Loads a collection of entities in a one-to-many association. * * @param array $criteria The criteria by which to select the entities. * @param PersistentCollection The collection to fill. diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index 7765af686..07598be36 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -584,16 +584,16 @@ class QueryBuilder * ->set('u.password', md5('password')) * ->where($or); * - * @param mixed $where The WHERE statement - * @return QueryBuilder $qb + * @param mixed $predicates The predicates. + * @return QueryBuilder */ - public function where($where) + public function where($predicates) { - if ( ! (func_num_args() == 1 && ($where instanceof Expr\Andx || $where instanceof Expr\Orx))) { - $where = new Expr\Andx(func_get_args()); + if ( ! (func_num_args() == 1 && ($predicates instanceof Expr\Andx || $predicates instanceof Expr\Orx))) { + $predicates = new Expr\Andx(func_get_args()); } - return $this->add('where', $where); + return $this->add('where', $predicates); } /** diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index e11350fc4..58103185c 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1217,7 +1217,7 @@ class UnitOfWork implements PropertyChangedListener break; case self::STATE_DETACHED: throw new \InvalidArgumentException( - "Behavior of save() for a detached entity is not yet defined."); + "Behavior of persist() for a detached entity is not yet defined."); case self::STATE_REMOVED: // Entity becomes managed again if ($this->isScheduledForDelete($entity)) { @@ -1766,10 +1766,10 @@ class UnitOfWork implements PropertyChangedListener if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { foreach ($class->associationMappings as $field => $assoc) { // Check if the association is not among the fetch-joined associations already. - if (isset($hints['fetched'][$class->rootEntityName][$field])) { + if (isset($hints['fetched'][$className][$field])) { continue; } - + $targetClass = $this->_em->getClassMetadata($assoc->targetEntityName); if ($assoc->isOneToOne()) { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC279Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC279Test.php index 27faabbeb..e3d4d1949 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC279Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC279Test.php @@ -10,9 +10,10 @@ class DDC279Test extends \Doctrine\Tests\OrmFunctionalTestCase { parent::setUp(); $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC279EntityXAbstract'), $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC279EntityX'), $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC279EntityY'), - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC279EntityZ') + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC279EntityZ'), )); } @@ -39,34 +40,31 @@ class DDC279Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->_em->clear(); - $result = $this->_em->createQuery( + $query = $this->_em->createQuery( 'SELECT x, y, z FROM Doctrine\Tests\ORM\Functional\Ticket\DDC279EntityX x '. - 'INNER JOIN x.y y INNER JOIN y.z z WHERE x.id = 1' - )->getArrayResult(); + 'INNER JOIN x.y y INNER JOIN y.z z WHERE x.id = ?1' + )->setParameter(1, $x->id); + + $result = $query->getResult(); + + $expected1 = 'Y'; + $expected2 = 'Z'; - $expected = array( - 0 => array( - 'id' => 1, - 'data' => 'X', - 'y' => array( - 'id' => 1, - 'data' => 'Y', - 'z' => array( - 'id' => 1, - 'data' => 'Z', - ) - ), - ), - ); - - $this->assertEquals($expected, $result); + $this->assertEquals(1, count($result)); + + $this->assertEquals($expected1, $result[0]->y->data); + $this->assertEquals($expected2, $result[0]->y->z->data); } } + /** * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"DDC279EntityX" = "DDC279EntityX"}) */ -class DDC279EntityX +abstract class DDC279EntityXAbstract { /** * @Id @@ -80,8 +78,15 @@ class DDC279EntityX */ public $data; +} + +/** + * @Entity + */ +class DDC279EntityX extends DDC279EntityXAbstract +{ /** - * @OneToOne(targetEntity="Doctrine\Tests\ORM\Functional\Ticket\DDC279EntityY") + * @OneToOne(targetEntity="DDC279EntityY") * @JoinColumn(name="y_id", referencedColumnName="id") */ public $y; @@ -105,7 +110,7 @@ class DDC279EntityY public $data; /** - * @OneToOne(targetEntity="Doctrine\Tests\ORM\Functional\Ticket\DDC279EntityZ") + * @OneToOne(targetEntity="DDC279EntityZ") * @JoinColumn(name="z_id", referencedColumnName="id") */ public $z;