diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index a5d97d30966..bb5bcb4df48 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -10,16 +10,22 @@ use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Query; use Doctrine\ORM\UnitOfWork; +use ReflectionClass; +use ReflectionException; use function array_fill_keys; +use function array_key_exists; use function array_keys; use function array_map; +use function assert; use function count; use function is_array; use function key; use function ltrim; use function spl_object_id; +use const PHP_VERSION_ID; + /** * The ObjectHydrator constructs an object graph out of an SQL result set. * @@ -564,8 +570,30 @@ protected function hydrateRowData(array $row, array &$result) foreach ($rowData['newObjects'] as $objIndex => $newObject) { $class = $newObject['class']; - $args = $newObject['args']; - $obj = $class->newInstanceArgs($args); + assert($class instanceof ReflectionClass); + $args = $newObject['args']; + + if (PHP_VERSION_ID >= 80000) { + $obj = $class->newInstanceArgs($args); + } else { + $constructor = $class->getConstructor(); + $unnamedArgs = []; + + $constructorArguments = $constructor->getParameters(); + $constructorArgumentsCount = count($constructorArguments); + + foreach ($constructorArguments as $argument) { + if (array_key_exists($argument->getName(), $args)) { + $unnamedArgs[$argument->getPosition()] = $args[$argument->getName()]; + } elseif (array_key_exists($argument->getPosition(), $args)) { + $unnamedArgs[$argument->getPosition()] = $args[$argument->getPosition()]; + } else { + $unnamedArgs[$argument->getPosition()] = $argument->getDefaultValue(); + } + } + + $obj = $class->newInstanceArgs($unnamedArgs); + } if ($scalarCount === 0 && count($rowData['newObjects']) === 1) { $result[$resultKey] = $obj; diff --git a/tests/Doctrine/Tests/ORM/Functional/NewOperatorTest.php b/tests/Doctrine/Tests/ORM/Functional/NewOperatorTest.php index f3986700426..952c6f1f44f 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NewOperatorTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NewOperatorTest.php @@ -1076,69 +1076,85 @@ public function testClassCantBeInstantiatedException(): void $this->_em->createQuery($dql)->getResult(); } - public function testQueryWithOnlyNamedArgumentsInOrder(): void + /** @return array */ + public static function provideQueriesWithNamedArguments(): array { - $dql = ' - SELECT - new Doctrine\Tests\Models\CMS\CmsUserDTO( - name: u.name, - email: e.email, - address: a.city, - ) - FROM - Doctrine\Tests\Models\CMS\CmsUser u - JOIN - u.email e - JOIN - u.address a - ORDER BY - u.name'; - - $query = $this->_em->createQuery($dql); - $result = $query->getResult(); - - self::assertCount(3, $result); - - self::assertInstanceOf(CmsUserDTO::class, $result[0]); - self::assertInstanceOf(CmsUserDTO::class, $result[1]); - self::assertInstanceOf(CmsUserDTO::class, $result[2]); - - self::assertEquals($this->fixtures[0]->name, $result[0]->name); - self::assertEquals($this->fixtures[1]->name, $result[1]->name); - self::assertEquals($this->fixtures[2]->name, $result[2]->name); - - self::assertEquals($this->fixtures[0]->email->email, $result[0]->email); - self::assertEquals($this->fixtures[1]->email->email, $result[1]->email); - self::assertEquals($this->fixtures[2]->email->email, $result[2]->email); - - self::assertEquals($this->fixtures[0]->address->city, $result[0]->address); - self::assertEquals($this->fixtures[1]->address->city, $result[1]->address); - self::assertEquals($this->fixtures[2]->address->city, $result[2]->address); - - self::assertNull($result[0]->phonenumbers); - self::assertNull($result[1]->phonenumbers); - self::assertNull($result[2]->phonenumbers); + return [ + 'Only named arguments in order' => [ + 'query' => ' + SELECT + new Doctrine\Tests\Models\CMS\CmsUserDTO( + name: u.name, + email: e.email, + address: a.city, + ) + FROM + Doctrine\Tests\Models\CMS\CmsUser u + JOIN + u.email e + JOIN + u.address a + ORDER BY + u.name', + ], + 'Only named arguments not in order' => [ + 'query' => ' + SELECT + new Doctrine\Tests\Models\CMS\CmsUserDTO( + email: e.email, + name: u.name, + address: a.city, + ) + FROM + Doctrine\Tests\Models\CMS\CmsUser u + JOIN + u.email e + JOIN + u.address a + ORDER BY + u.name', + ], + 'Both named and unnamed arguments' => [ + 'query' => ' + SELECT + new Doctrine\Tests\Models\CMS\CmsUserDTO( + u.name, + address: a.city, + email: e.email, + ) + FROM + Doctrine\Tests\Models\CMS\CmsUser u + JOIN + u.email e + JOIN + u.address a + ORDER BY + u.name', + ], + 'Both named and unnamed arguments without trailling comma' => [ + 'query' => ' + SELECT + new Doctrine\Tests\Models\CMS\CmsUserDTO( + u.name, + address: a.city, + email: e.email + ) + FROM + Doctrine\Tests\Models\CMS\CmsUser u + JOIN + u.email e + JOIN + u.address a + ORDER BY + u.name', + ], + ]; } - public function testQueryWithOnlyNamedArgumentsNotInOrder(): void + /** @dataProvider provideQueriesWithNamedArguments */ + public function testQueryWithNamedArguments(string $query): void { - $dql = ' - SELECT - new Doctrine\Tests\Models\CMS\CmsUserDTO( - email: e.email, - name: u.name, - address: a.city, - ) - FROM - Doctrine\Tests\Models\CMS\CmsUser u - JOIN - u.email e - JOIN - u.address a - ORDER BY - u.name'; - - $query = $this->_em->createQuery($dql); + $query = $this->_em->createQuery($query); $result = $query->getResult(); self::assertCount(3, $result); @@ -1164,14 +1180,14 @@ public function testQueryWithOnlyNamedArgumentsNotInOrder(): void self::assertNull($result[2]->phonenumbers); } - public function testQueryWithNamedAndUnnamedArguments(): void + public function testQueryWithUnnamedArgumentAfterNamedArgument(): void { $dql = ' SELECT new Doctrine\Tests\Models\CMS\CmsUserDTO( - u.name, address: a.city, email: e.email, + u.name, ) FROM Doctrine\Tests\Models\CMS\CmsUser u @@ -1182,40 +1198,20 @@ public function testQueryWithNamedAndUnnamedArguments(): void ORDER BY u.name'; - $query = $this->_em->createQuery($dql); - $result = $query->getResult(); - - self::assertCount(3, $result); - - self::assertInstanceOf(CmsUserDTO::class, $result[0]); - self::assertInstanceOf(CmsUserDTO::class, $result[1]); - self::assertInstanceOf(CmsUserDTO::class, $result[2]); - - self::assertEquals($this->fixtures[0]->name, $result[0]->name); - self::assertEquals($this->fixtures[1]->name, $result[1]->name); - self::assertEquals($this->fixtures[2]->name, $result[2]->name); - - self::assertEquals($this->fixtures[0]->email->email, $result[0]->email); - self::assertEquals($this->fixtures[1]->email->email, $result[1]->email); - self::assertEquals($this->fixtures[2]->email->email, $result[2]->email); - - self::assertEquals($this->fixtures[0]->address->city, $result[0]->address); - self::assertEquals($this->fixtures[1]->address->city, $result[1]->address); - self::assertEquals($this->fixtures[2]->address->city, $result[2]->address); + $query = $this->_em->createQuery($dql); + $this->expectException(QueryException::class); + $this->expectExceptionMessage('[Syntax Error] Cannot specify unnamed arguments after named ones.'); - self::assertNull($result[0]->phonenumbers); - self::assertNull($result[1]->phonenumbers); - self::assertNull($result[2]->phonenumbers); + $query->getResult(); } - public function testQueryWithNamedAndUnnamedArgumentsWithoutTraillingComma(): void + public function testQueryWithNamedArgumentsWithoutOptionalParameters(): void { $dql = ' SELECT new Doctrine\Tests\Models\CMS\CmsUserDTO( - u.name, address: a.city, - email: e.email + email: e.email, ) FROM Doctrine\Tests\Models\CMS\CmsUser u @@ -1226,18 +1222,16 @@ public function testQueryWithNamedAndUnnamedArgumentsWithoutTraillingComma(): vo ORDER BY u.name'; - $query = $this->_em->createQuery($dql); + $query = $this->_em->createQuery($dql); $result = $query->getResult(); - self::assertCount(3, $result); - self::assertInstanceOf(CmsUserDTO::class, $result[0]); self::assertInstanceOf(CmsUserDTO::class, $result[1]); self::assertInstanceOf(CmsUserDTO::class, $result[2]); - self::assertEquals($this->fixtures[0]->name, $result[0]->name); - self::assertEquals($this->fixtures[1]->name, $result[1]->name); - self::assertEquals($this->fixtures[2]->name, $result[2]->name); + self::assertNull($result[0]->name); + self::assertNull($result[1]->name); + self::assertNull($result[2]->name); self::assertEquals($this->fixtures[0]->email->email, $result[0]->email); self::assertEquals($this->fixtures[1]->email->email, $result[1]->email); @@ -1252,29 +1246,23 @@ public function testQueryWithNamedAndUnnamedArgumentsWithoutTraillingComma(): vo self::assertNull($result[2]->phonenumbers); } - public function testQueryWithUnnamedArgumentAfterNamedArgument(): void + public function testQueryWithNamedArgumentsMissingRequiredArguments(): void { + $dql = ' SELECT - new Doctrine\Tests\Models\CMS\CmsUserDTO( - address: a.city, - email: e.email, - u.name, + new ' . ClassWithTooMuchArgs::class . '( + bar: u.name, ) FROM Doctrine\Tests\Models\CMS\CmsUser u - JOIN - u.email e - JOIN - u.address a - ORDER BY - u.name'; + '; $query = $this->_em->createQuery($dql); $this->expectException(QueryException::class); - $this->expectExceptionMessage('[Syntax Error] Cannot specify unnamed arguments after named ones.'); + $this->expectExceptionMessage('Number of arguments does not match with "Doctrine\Tests\ORM\Functional\ClassWithTooMuchArgs" constructor declaration.'); + $result = $query->getResult(); - $query->getResult(); } }