diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index bb18f32c6..5688d8652 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -146,6 +146,7 @@ class ArrayHydrator extends AbstractHydrator $baseElement =& $this->_resultPointers[$parent]; } else { unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 + continue; } @@ -167,6 +168,7 @@ class ArrayHydrator extends AbstractHydrator if ( ! $indexExists || ! $indexIsValid) { $element = $data; + if (isset($this->_rsm->indexByMap[$dqlAlias])) { $baseElement[$relationAlias][$row[$this->_rsm->indexByMap[$dqlAlias]]] = $element; } else { @@ -183,9 +185,15 @@ class ArrayHydrator extends AbstractHydrator } else { $oneToOne = true; - if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) { + if ( + ( ! isset($nonemptyComponents[$dqlAlias])) && + ( ! isset($baseElement[$relationAlias])) + ) { $baseElement[$relationAlias] = null; - } else if ( ! isset($baseElement[$relationAlias])) { + } else if ( + ( ! isset($baseElement[$relationAlias])) || + ( ! isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]])) + ) { $baseElement[$relationAlias] = $data; } } @@ -195,7 +203,6 @@ class ArrayHydrator extends AbstractHydrator if ($coll !== null) { $this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne); } - } else { // It's a root result element @@ -204,22 +211,21 @@ class ArrayHydrator extends AbstractHydrator // if this row has a NULL value for the root result id then make it a null result. if ( ! isset($nonemptyComponents[$dqlAlias]) ) { - if ($this->_rsm->isMixed) { - $result[] = array($entityKey => null); - } else { - $result[] = null; - } + $result[] = $this->_rsm->isMixed + ? array($entityKey => null) + : null; + $resultKey = $this->_resultCounter; ++$this->_resultCounter; + continue; } // Check for an existing element if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { - $element = $rowData[$dqlAlias]; - if ($this->_rsm->isMixed) { - $element = array($entityKey => $element); - } + $element = $this->_rsm->isMixed + ? array($entityKey => $rowData[$dqlAlias]) + : $rowData[$dqlAlias]; if (isset($this->_rsm->indexByMap[$dqlAlias])) { $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]]; @@ -227,6 +233,7 @@ class ArrayHydrator extends AbstractHydrator } else { $resultKey = $this->_resultCounter; $result[] = $element; + ++$this->_resultCounter; } @@ -234,11 +241,13 @@ class ArrayHydrator extends AbstractHydrator } else { $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; $resultKey = $index; + /*if ($this->_rsm->isMixed) { $result[] =& $result[$index]; ++$this->_resultCounter; }*/ } + $this->updateResultPointer($result, $index, $dqlAlias, false); } } @@ -247,11 +256,9 @@ class ArrayHydrator extends AbstractHydrator if (isset($scalars)) { if ( ! isset($resultKey) ) { // this only ever happens when no object is fetched (scalar result only) - if (isset($this->_rsm->indexByMap['scalars'])) { - $resultKey = $row[$this->_rsm->indexByMap['scalars']]; - } else { - $resultKey = $this->_resultCounter - 1; - } + $resultKey = isset($this->_rsm->indexByMap['scalars']) + ? $row[$this->_rsm->indexByMap['scalars']] + : $this->_resultCounter - 1; } foreach ($scalars as $name => $value) { @@ -279,6 +286,12 @@ class ArrayHydrator extends AbstractHydrator return; } + if ($oneToOne) { + $this->_resultPointers[$dqlAlias] =& $coll; + + return; + } + if ($index !== false) { $this->_resultPointers[$dqlAlias] =& $coll[$index]; @@ -289,12 +302,6 @@ class ArrayHydrator extends AbstractHydrator return; } - if ($oneToOne) { - $this->_resultPointers[$dqlAlias] =& $coll; - - return; - } - end($coll); $this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; diff --git a/tests/Doctrine/Tests/Models/Taxi/Car.php b/tests/Doctrine/Tests/Models/Taxi/Car.php new file mode 100644 index 000000000..2db5ecd36 --- /dev/null +++ b/tests/Doctrine/Tests/Models/Taxi/Car.php @@ -0,0 +1,42 @@ +brand = $brand; + } + + public function setModel($model) + { + $this->model = $model; + } +} diff --git a/tests/Doctrine/Tests/Models/Taxi/Driver.php b/tests/Doctrine/Tests/Models/Taxi/Driver.php new file mode 100644 index 000000000..ccb493f1d --- /dev/null +++ b/tests/Doctrine/Tests/Models/Taxi/Driver.php @@ -0,0 +1,37 @@ +name = $name; + } +} diff --git a/tests/Doctrine/Tests/Models/Taxi/PaidRide.php b/tests/Doctrine/Tests/Models/Taxi/PaidRide.php new file mode 100644 index 000000000..040e3917e --- /dev/null +++ b/tests/Doctrine/Tests/Models/Taxi/PaidRide.php @@ -0,0 +1,42 @@ +driver = $driver; + $this->car = $car; + } + + public function setFare($fare) + { + $this->fare = $fare; + } +} diff --git a/tests/Doctrine/Tests/Models/Taxi/Ride.php b/tests/Doctrine/Tests/Models/Taxi/Ride.php new file mode 100644 index 000000000..e90a38217 --- /dev/null +++ b/tests/Doctrine/Tests/Models/Taxi/Ride.php @@ -0,0 +1,32 @@ +driver = $driver; + $this->car = $car; + } +} diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1884Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1884Test.php new file mode 100644 index 000000000..de02e3067 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1884Test.php @@ -0,0 +1,158 @@ + + */ +class DDC1884Test extends \Doctrine\Tests\OrmFunctionalTestCase +{ + protected function setUp() + { + $this->useModelSet('taxi'); + parent::setUp(); + + list($bimmer, $crysler, $merc, $volvo) = $this->createCars('Doctrine\Tests\Models\Taxi\Car'); + list($john, $foo) = $this->createDrivers('Doctrine\Tests\Models\Taxi\Driver'); + $this->_em->flush(); + + $ride1 = new Ride($john, $bimmer); + $ride2 = new Ride($john, $merc); + $ride3 = new Ride($john, $volvo); + $ride4 = new Ride($foo, $merc); + + $this->_em->persist($ride1); + $this->_em->persist($ride2); + $this->_em->persist($ride3); + $this->_em->persist($ride4); + + $ride5 = new PaidRide($john, $bimmer); + $ride5->setFare(10.50); + + $ride6 = new PaidRide($john, $merc); + $ride6->setFare(16.00); + + $ride7 = new PaidRide($john, $volvo); + $ride7->setFare(20.70); + + $ride8 = new PaidRide($foo, $merc); + $ride8->setFare(32.15); + + $this->_em->persist($ride5); + $this->_em->persist($ride6); + $this->_em->persist($ride7); + $this->_em->persist($ride8); + + $this->_em->flush(); + } + + private function createCars($class) + { + $bimmer = new $class; + $bimmer->setBrand('BMW'); + $bimmer->setModel('7-Series'); + + $crysler = new $class; + $crysler->setBrand('Crysler'); + $crysler->setModel('300'); + + $merc = new $class; + $merc->setBrand('Mercedes'); + $merc->setModel('C-Class'); + + $volvo = new $class; + $volvo->setBrand('Volvo'); + $volvo->setModel('XC90'); + + $this->_em->persist($bimmer); + $this->_em->persist($crysler); + $this->_em->persist($merc); + $this->_em->persist($volvo); + + return array($bimmer, $crysler, $merc, $volvo); + } + + private function createDrivers($class) + { + $john = new $class; + $john->setName('John Doe'); + + $foo = new $class; + $foo->setName('Foo Bar'); + + $this->_em->persist($foo); + $this->_em->persist($john); + + return array($john, $foo); + } + + /** + * 1) Ride contains only columns that are part of its composite primary key + * 2) We use fetch joins here + */ + public function testSelectFromInverseSideWithCompositePkAndSolelyIdentifierColumnsUsingFetchJoins() + { + $qb = $this->_em->createQueryBuilder(); + + $result = $qb->select('d, dr, c') + ->from('Doctrine\Tests\Models\Taxi\Driver', 'd') + ->leftJoin('d.freeDriverRides', 'dr') + ->leftJoin('dr.car', 'c') + ->where('d.name = ?1') + ->setParameter(1, 'John Doe') + ->getQuery() + ->getArrayResult(); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('freeDriverRides', $result[0]); + $this->assertCount(3, $result[0]['freeDriverRides']); + } + + /** + * 1) PaidRide contains an extra column that is not part of the composite primary key + * 2) Again we will use fetch joins + */ + public function testSelectFromInverseSideWithCompositePkUsingFetchJoins() + { + $qb = $this->_em->createQueryBuilder(); + + $result = $qb->select('d, dr, c') + ->from('Doctrine\Tests\Models\Taxi\Driver', 'd') + ->leftJoin('d.driverRides', 'dr') + ->leftJoin('dr.car', 'c') + ->where('d.name = ?1') + ->setParameter(1, 'John Doe') + ->getQuery()->getArrayResult(); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('driverRides', $result[0]); + $this->assertCount(3, $result[0]['driverRides']); + } + + /** + * The other way around will fail too + */ + public function testSelectFromOwningSideUsingFetchJoins() + { + $qb = $this->_em->createQueryBuilder(); + + $result = $qb->select('r, d, c') + ->from('Doctrine\Tests\Models\Taxi\PaidRide', 'r') + ->leftJoin('r.driver', 'd') + ->leftJoin('r.car', 'c') + ->where('d.name = ?1') + ->setParameter(1, 'John Doe') + ->getQuery()->getArrayResult(); + + $this->assertCount(3, $result); + $this->assertArrayHasKey('driver', $result[0]); + $this->assertArrayHasKey('car', $result[0]); + } +} diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 9c922802c..1b70be721 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -155,7 +155,13 @@ abstract class OrmFunctionalTestCase extends OrmTestCase 'Doctrine\Tests\Models\CompositeKeyInheritance\JoinedChildClass', 'Doctrine\Tests\Models\CompositeKeyInheritance\SingleRootClass', 'Doctrine\Tests\Models\CompositeKeyInheritance\SingleChildClass', - ) + ), + 'taxi' => array( + 'Doctrine\Tests\Models\Taxi\PaidRide', + 'Doctrine\Tests\Models\Taxi\Ride', + 'Doctrine\Tests\Models\Taxi\Car', + 'Doctrine\Tests\Models\Taxi\Driver', + ), ); /** @@ -284,6 +290,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase $conn->executeUpdate('DELETE FROM SingleRootClass'); } + if (isset($this->_usedModelSets['taxi'])) { + $conn->executeUpdate('DELETE FROM taxi_paid_ride'); + $conn->executeUpdate('DELETE FROM taxi_ride'); + $conn->executeUpdate('DELETE FROM taxi_car'); + $conn->executeUpdate('DELETE FROM taxi_driver'); + } $this->_em->clear(); }