diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 823f301b2..c17f91ae8 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -404,6 +404,11 @@ class ObjectHydrator extends AbstractHydrator continue; } + $parentClass = $this->ce[$this->_rsm->aliasMap[$parentAlias]]; + $relationField = $this->_rsm->relationMap[$dqlAlias]; + $relation = $parentClass->associationMappings[$relationField]; + $reflField = $parentClass->reflFields[$relationField]; + // Get a reference to the parent object to which the joined element belongs. if ($this->_rsm->isMixed && isset($this->rootAliases[$parentAlias])) { $first = reset($this->resultPointers); @@ -411,20 +416,28 @@ class ObjectHydrator extends AbstractHydrator } else if (isset($this->resultPointers[$parentAlias])) { $parentObject = $this->resultPointers[$parentAlias]; } else { - // Parent object of relation not found, so skip it. + // Parent object of relation not found, mark as not-fetched again + $element = $this->getEntity($data, $dqlAlias); + + // Update result pointer and provide initial fetch data for parent + $this->resultPointers[$dqlAlias] = $element; + $rowData[$parentAlias][$relationField] = $element; + + // Mark as not-fetched again + unset($this->_hints['fetched'][$parentAlias][$relationField]); + + ////unset($rowData[$dqlAlias]); + //$rowData[$dqlAlias] = $data; continue; } - $parentClass = $this->ce[$this->_rsm->aliasMap[$parentAlias]]; - $oid = spl_object_hash($parentObject); - $relationField = $this->_rsm->relationMap[$dqlAlias]; - $relation = $parentClass->associationMappings[$relationField]; - $reflField = $parentClass->reflFields[$relationField]; + $oid = spl_object_hash($parentObject); // Check the type of the relation (many or single-valued) if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { - $reflFieldValue = $reflField->getValue($parentObject); // PATH A: Collection-valued association + $reflFieldValue = $reflField->getValue($parentObject); + if (isset($nonemptyComponents[$dqlAlias])) { $collKey = $oid . $relationField; if (isset($this->initializedCollections[$collKey])) { @@ -473,6 +486,7 @@ class ObjectHydrator extends AbstractHydrator } else { // PATH B: Single-valued association $reflFieldValue = $reflField->getValue($parentObject); + if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && !$reflFieldValue->__isInitialized__)) { // we only need to take action if this value is null, // we refresh the entity or its an unitialized proxy. @@ -531,7 +545,7 @@ class ObjectHydrator extends AbstractHydrator // check for existing result from the iterations before if ( ! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) { - $element = $this->getEntity($rowData[$dqlAlias], $dqlAlias); + $element = $this->getEntity($data, $dqlAlias); if ($this->_rsm->isMixed) { $element = array($entityKey => $element); @@ -566,10 +580,6 @@ class ObjectHydrator extends AbstractHydrator $index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]]; $this->resultPointers[$dqlAlias] = $result[$index]; $resultKey = $index; - /*if ($this->_rsm->isMixed) { - $result[] = $result[$index]; - ++$this->_resultCounter; - }*/ } } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2575Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2575Test.php new file mode 100644 index 000000000..2f6b406d3 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2575Test.php @@ -0,0 +1,160 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2575Root'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2575A'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2575B'), + )); + + $entityRoot1 = new DDC2575Root(1); + $entityB1 = new DDC2575B(2); + $entityA1 = new DDC2575A($entityRoot1, $entityB1); + + $this->_em->persist($entityRoot1); + $this->_em->persist($entityA1); + $this->_em->persist($entityB1); + + $entityRoot2 = new DDC2575Root(3); + $entityB2 = new DDC2575B(4); + $entityA2 = new DDC2575A($entityRoot2, $entityB2); + + $this->_em->persist($entityRoot2); + $this->_em->persist($entityA2); + $this->_em->persist($entityB2); + + $this->_em->flush(); + + $this->rootsEntities[] = $entityRoot1; + $this->rootsEntities[] = $entityRoot2; + + $this->aEntities[] = $entityA1; + $this->aEntities[] = $entityA2; + + $this->bEntities[] = $entityB1; + $this->bEntities[] = $entityB2; + + $this->_em->clear(); + } + + public function testHydrationIssue() + { + $repository = $this->_em->getRepository(__NAMESPACE__ . '\DDC2575Root'); + $qb = $repository->createQueryBuilder('r') + ->select('r, a, b') + ->leftJoin('r.aRelation', 'a') + ->leftJoin('a.bRelation', 'b'); + + $query = $qb->getQuery(); + $result = $query->getResult(); + + $this->assertCount(2, $result); + + $row = $result[0]; + $this->assertNotNull($row->aRelation); + $this->assertEquals(1, $row->id); + $this->assertNotNull($row->aRelation->rootRelation); + $this->assertSame($row, $row->aRelation->rootRelation); + $this->assertNotNull($row->aRelation->bRelation); + $this->assertEquals(2, $row->aRelation->bRelation->id); + + $row = $result[1]; + $this->assertNotNull($row->aRelation); + $this->assertEquals(3, $row->id); + $this->assertNotNull($row->aRelation->rootRelation); + $this->assertSame($row, $row->aRelation->rootRelation); + $this->assertNotNull($row->aRelation->bRelation); + $this->assertEquals(4, $row->aRelation->bRelation->id); + } +} + +/** + * @Entity + */ +class DDC2575Root +{ + /** + * @Id + * @Column(type="integer") + */ + public $id; + + /** + * @Column(type="integer") + */ + public $sampleField; + + /** + * @OneToOne(targetEntity="DDC2575A", mappedBy="rootRelation") + **/ + public $aRelation; + + public function __construct($id, $value = 0) + { + $this->id = $id; + $this->sampleField = $value; + } + +} + +/** + * @Entity + */ +class DDC2575A +{ + /** + * @Id + * @OneToOne(targetEntity="DDC2575Root", inversedBy="aRelation") + * @JoinColumn(name="root_id", referencedColumnName="id", nullable=FALSE, onDelete="CASCADE") + */ + public $rootRelation; + + /** + * @ManyToOne(targetEntity="DDC2575B") + * @JoinColumn(name="b_id", referencedColumnName="id", nullable=FALSE, onDelete="CASCADE") + */ + public $bRelation; + + public function __construct(DDC2575Root $rootRelation, DDC2575B $bRelation) + { + $this->rootRelation = $rootRelation; + $this->bRelation = $bRelation; + } +} + +/** + * @Entity + */ +class DDC2575B +{ + /** + * @Id + * @Column(type="integer") + */ + public $id; + + /** + * @Column(type="integer") + */ + public $sampleField; + + public function __construct($id, $value = 0) + { + $this->id = $id; + $this->sampleField = $value; + } +} \ No newline at end of file