Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX - nested dto's doesn't have arguments in good order and Dto with only objects bug #11825

Merged
merged 1 commit into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 20 additions & 13 deletions src/Internal/Hydration/AbstractHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
use LogicException;
use ReflectionClass;

use function array_key_exists;
use function array_map;
use function array_merge;
use function count;
use function end;
use function in_array;
use function is_array;
use function ksort;

/**
* Base class for all hydrators. A hydrator is a class that provides some form
Expand Down Expand Up @@ -263,6 +265,17 @@ protected function gatherRowData(array $data, array &$id, array &$nonemptyCompon
{
$rowData = ['data' => [], 'newObjects' => []];

foreach ($this->rsm->newObjectMappings as $mapping) {
if (! array_key_exists($mapping['objIndex'], $this->rsm->newObject)) {
$this->rsm->newObject[$mapping['objIndex']] = $mapping['className'];
}
}

foreach ($this->rsm->newObject as $objIndex => $newObject) {
$rowData['newObjects'][$objIndex]['class'] = new ReflectionClass($newObject);
$rowData['newObjects'][$objIndex]['args'] = [];
}

foreach ($data as $key => $value) {
$cacheKeyInfo = $this->hydrateColumnInfo($key);
if ($cacheKeyInfo === null) {
Expand All @@ -282,7 +295,6 @@ protected function gatherRowData(array $data, array &$id, array &$nonemptyCompon
$value = $this->buildEnum($value, $cacheKeyInfo['enumType']);
}

$rowData['newObjects'][$objIndex]['class'] = $cacheKeyInfo['class'];
$rowData['newObjects'][$objIndex]['args'][$argIndex] = $value;
break;

Expand Down Expand Up @@ -336,21 +348,17 @@ protected function gatherRowData(array $data, array &$id, array &$nonemptyCompon
}
}

foreach ($this->resultSetMapping()->nestedNewObjectArguments as $objIndex => ['ownerIndex' => $ownerIndex, 'argIndex' => $argIndex]) {
if (! isset($rowData['newObjects'][$ownerIndex . ':' . $argIndex])) {
continue;
foreach ($this->resultSetMapping()->nestedNewObjectArguments as ['ownerIndex' => $ownerIndex, 'argIndex' => $argIndex, 'argAlias' => $argAlias]) {
if (array_key_exists($argAlias, $rowData['newObjects'])) {
ksort($rowData['newObjects'][$argAlias]['args']);
$rowData['newObjects'][$ownerIndex]['args'][$argIndex] = $rowData['newObjects'][$argAlias]['class']->newInstanceArgs($rowData['newObjects'][$argAlias]['args']);
unset($rowData['newObjects'][$argAlias]);
}

$newObject = $rowData['newObjects'][$ownerIndex . ':' . $argIndex];
unset($rowData['newObjects'][$ownerIndex . ':' . $argIndex]);

$obj = $newObject['class']->newInstanceArgs($newObject['args']);

$rowData['newObjects'][$ownerIndex]['args'][$argIndex] = $obj;
}

foreach ($rowData['newObjects'] as $objIndex => $newObject) {
$obj = $newObject['class']->newInstanceArgs($newObject['args']);
ksort($rowData['newObjects'][$objIndex]['args']);
$obj = $rowData['newObjects'][$objIndex]['class']->newInstanceArgs($rowData['newObjects'][$objIndex]['args']);

$rowData['newObjects'][$objIndex]['obj'] = $obj;
}
Expand Down Expand Up @@ -454,7 +462,6 @@ protected function hydrateColumnInfo(string $key): array|null
'type' => Type::getType($this->rsm->typeMappings[$key]),
'argIndex' => $mapping['argIndex'],
'objIndex' => $mapping['objIndex'],
'class' => new ReflectionClass($mapping['className']),
'enumType' => $this->rsm->enumMappings[$key] ?? null,
];

Expand Down
14 changes: 11 additions & 3 deletions src/Query/AST/NewObjectExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,28 @@

use Doctrine\ORM\Query\SqlWalker;

use function func_get_arg;
use function func_num_args;

/**
* NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")"
*
* @link www.doctrine-project.org
*/
class NewObjectExpression extends Node
{
/** @param mixed[] $args */
/**
* @param class-string $className
* @param mixed[] $args
*/
public function __construct(public string $className, public array $args)
{
}

public function dispatch(SqlWalker $walker): string
public function dispatch(SqlWalker $walker /*, string|null $parentAlias = null */): string
{
return $walker->walkNewObject($this);
$parentAlias = func_num_args() > 1 ? func_get_arg(1) : null;

return $walker->walkNewObject($this, $parentAlias);
}
}
7 changes: 6 additions & 1 deletion src/Query/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -1767,6 +1767,7 @@ public function NewObjectExpression(): AST\NewObjectExpression
$useNamedArguments = true;
}

/** @var class-string $className */
$className = $this->AbstractSchemaName(); // note that this is not yet validated
$token = $this->lexer->token;

Expand Down Expand Up @@ -1854,7 +1855,11 @@ public function NewObjectArg(string|null &$fieldAlias = null): mixed

if ($this->lexer->isNextToken(TokenType::T_AS)) {
$this->match(TokenType::T_AS);
$fieldAlias = $this->AliasIdentificationVariable();
$this->match(TokenType::T_IDENTIFIER);

assert($this->lexer->token !== null);

$fieldAlias = $this->lexer->token->value;
}

return $expression;
Expand Down
7 changes: 7 additions & 0 deletions src/Query/ResultSetMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ class ResultSetMapping
*/
public array $newObjectMappings = [];

/**
* Maps object Ids in the result set to classnames.
*
* @phpstan-var array<string|int, class-string>
*/
public array $newObject = [];

/**
* Maps last argument for new objects in order to initiate object construction
*
Expand Down
27 changes: 5 additions & 22 deletions src/Query/SqlWalker.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@
use function array_keys;
use function array_map;
use function array_merge;
use function array_pop;
use function assert;
use function count;
use function end;
use function implode;
use function in_array;
use function is_array;
Expand Down Expand Up @@ -84,13 +82,6 @@ class SqlWalker
*/
private int $newObjectCounter = 0;

/**
* Contains nesting levels of new objects arguments
*
* @phpstan-var array<int, array{0: string|int, 1: int}>
*/
private array $newObjectStack = [];

private readonly EntityManagerInterface $em;
private readonly Connection $conn;

Expand Down Expand Up @@ -1507,14 +1498,7 @@ public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesis
public function walkNewObject(AST\NewObjectExpression $newObjectExpression, string|null $newObjectResultAlias = null): string
{
$sqlSelectExpressions = [];
$objOwner = $objOwnerIdx = null;

if ($this->newObjectStack !== []) {
[$objOwner, $objOwnerIdx] = end($this->newObjectStack);
$objIndex = $objOwner . ':' . $objOwnerIdx;
} else {
$objIndex = $newObjectResultAlias ?: $this->newObjectCounter++;
}
$objIndex = $newObjectResultAlias ?: $this->newObjectCounter++;

foreach ($newObjectExpression->args as $argIndex => $e) {
$resultAlias = $this->scalarResultCounter++;
Expand All @@ -1523,10 +1507,8 @@ public function walkNewObject(AST\NewObjectExpression $newObjectExpression, stri

switch (true) {
case $e instanceof AST\NewObjectExpression:
$this->newObjectStack[] = [$objIndex, $argIndex];
$sqlSelectExpressions[] = $e->dispatch($this);
array_pop($this->newObjectStack);
$this->rsm->nestedNewObjectArguments[$columnAlias] = ['ownerIndex' => $objIndex, 'argIndex' => $argIndex];
$sqlSelectExpressions[] = $e->dispatch($this, $columnAlias);
$this->rsm->nestedNewObjectArguments[$columnAlias] = ['ownerIndex' => $objIndex, 'argIndex' => $argIndex, 'argAlias' => $columnAlias];
break;

case $e instanceof AST\Subselect:
Expand Down Expand Up @@ -1576,12 +1558,13 @@ public function walkNewObject(AST\NewObjectExpression $newObjectExpression, stri
$this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);

$this->rsm->newObjectMappings[$columnAlias] = [
'className' => $newObjectExpression->className,
'objIndex' => $objIndex,
'argIndex' => $argIndex,
];
}

$this->rsm->newObject[$objIndex] = $newObjectExpression->className;

return implode(', ', $sqlSelectExpressions);
}

Expand Down
2 changes: 1 addition & 1 deletion tests/Tests/Models/CMS/CmsAddressDTO.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class CmsAddressDTO
{
public function __construct(public string|null $country = null, public string|null $city = null, public string|null $zip = null, public CmsAddressDTO|string|null $address = null)
public function __construct(public string|null $country = null, public string|null $city = null, public string|null $zip = null, public string|null $address = null, public CmsDumbDTO|null $other = null)
{
}
}
17 changes: 17 additions & 0 deletions tests/Tests/Models/CMS/CmsDumbDTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\CMS;

class CmsDumbDTO
{
public function __construct(
public mixed $val1 = null,
public mixed $val2 = null,
public mixed $val3 = null,
public mixed $val4 = null,
public mixed $val5 = null,
) {
}
}
Loading