Skip to content

Commit

Permalink
doctrine#4687 Support for nested "new" operators
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey Protko committed Sep 17, 2017
1 parent 8b81691 commit b48e6a4
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 10 deletions.
24 changes: 24 additions & 0 deletions lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ protected function gatherRowData(array $data, array &$id, array &$nonemptyCompon

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

break;

case (isset($cacheKeyInfo['isScalar'])):
Expand Down Expand Up @@ -324,6 +325,29 @@ protected function gatherRowData(array $data, array &$id, array &$nonemptyCompon
}
}

foreach ($this->_rsm->nestedNewObjectArguments as $objIndex => ['ownerIndex' => $ownerIndex, 'argIndex' => $argIndex]) {
$newObject = $rowData['newObjects'][$objIndex];
unset($rowData['newObjects'][$objIndex]);

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

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


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

$rowData['newObjects'][$objIndex]['obj'] = $obj;
}
}


return $rowData;
}

Expand Down
3 changes: 1 addition & 2 deletions lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,8 @@ protected function hydrateRowData(array $row, array &$result)
$scalarCount = (isset($rowData['scalars'])? count($rowData['scalars']): 0);

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

if (count($args) == $scalarCount || ($scalarCount == 0 && count($rowData['newObjects']) == 1)) {
$result[$resultKey] = $obj;
Expand Down
5 changes: 1 addition & 4 deletions lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -555,13 +555,10 @@ protected function hydrateRowData(array $row, array &$result)
$resultKey = $this->resultCounter - 1;
}


$scalarCount = (isset($rowData['scalars'])? count($rowData['scalars']): 0);

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

if ($scalarCount == 0 && count($rowData['newObjects']) == 1 ) {
$result[$resultKey] = $obj;
Expand Down
6 changes: 6 additions & 0 deletions lib/Doctrine/ORM/Query/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -1897,6 +1897,12 @@ public function NewObjectArg()
return $expression;
}

if ($token['type'] === Lexer::T_NEW) {
$expression = $this->NewObjectExpression();

return $expression;
}

return $this->ScalarExpression();
}

Expand Down
26 changes: 26 additions & 0 deletions lib/Doctrine/ORM/Query/ResultSetMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ class ResultSetMapping
*/
public $newObjectMappings = [];

/**
* Maps last argument for new objects in order to initiate object construction
*
* @var array
*/
public $nestedNewObjectArguments = [];

/**
* Maps metadata parameter names to the metadata attribute.
*
Expand Down Expand Up @@ -583,5 +590,24 @@ public function addMetaResult($alias, $columnName, $fieldName, $isIdentifierColu

return $this;
}

public function addNewObjectAsArgument($alias, $objOwner, $objOwnerIdx)
{
$owner = [
'ownerIndex' => $objOwner,
'argIndex' => $objOwnerIdx,
];

if (!isset($this->nestedNewObjectArguments[$owner['ownerIndex']])) {
$this->nestedNewObjectArguments[$alias] = $owner;

return;
}

$this->nestedNewObjectArguments = array_merge(
[$alias => $owner],
$this->nestedNewObjectArguments
);
}
}

29 changes: 25 additions & 4 deletions lib/Doctrine/ORM/Query/SqlWalker.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ class SqlWalker implements TreeWalker
*/
private $newObjectCounter = 0;

/**
* Contains nesting levels of new objects arguments
*
* @var array of newObject indexes
*/
private $newObjectStack = [];

/**
* @var ParserResult
*/
Expand Down Expand Up @@ -1528,7 +1535,14 @@ public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesis
public function walkNewObject($newObjectExpression, $newObjectResultAlias=null)
{
$sqlSelectExpressions = [];
$objIndex = $newObjectResultAlias?:$this->newObjectCounter++;

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

foreach ($newObjectExpression->args as $argIndex => $e) {
$resultAlias = $this->scalarResultCounter++;
Expand All @@ -1537,7 +1551,10 @@ public function walkNewObject($newObjectExpression, $newObjectResultAlias=null)

switch (true) {
case ($e instanceof AST\NewObjectExpression):
array_push($this->newObjectStack, [$objIndex, $argIndex]);
$sqlSelectExpressions[] = $e->dispatch($this);
array_pop($this->newObjectStack);

break;

case ($e instanceof AST\Subselect):
Expand Down Expand Up @@ -1576,10 +1593,14 @@ public function walkNewObject($newObjectExpression, $newObjectResultAlias=null)
$this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);

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

if ($objOwner) {
$this->rsm->addNewObjectAsArgument($objIndex, $objOwner, $objOwnerIdx);
}
}

return implode(', ', $sqlSelectExpressions);
Expand Down
73 changes: 73 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/NewOperatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,79 @@ public function testClassCantBeInstantiatedException()
$dql = "SELECT new Doctrine\Tests\ORM\Functional\ClassWithPrivateConstructor(u.name) FROM Doctrine\Tests\Models\CMS\CmsUser u";
$this->_em->createQuery($dql)->getResult();
}

public function testShouldSupportNestedNewOperators()
{
$dql = "
SELECT
new CmsUserDTO(
u.name,
e.email,
new CmsAddressDTO(
a.country,
a.city,
new CmsAddressDTO(
a.country,
a.city
)
)
) as user,
u.status,
u.username as cmsUserUsername
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();

$this->assertCount(3, $result);

$this->assertInstanceOf(CmsUserDTO::class, $result[0]['user']);
$this->assertInstanceOf(CmsUserDTO::class, $result[1]['user']);
$this->assertInstanceOf(CmsUserDTO::class, $result[2]['user']);

$this->assertInstanceOf(CmsAddressDTO::class, $result[0]['user']->address);
$this->assertInstanceOf(CmsAddressDTO::class, $result[1]['user']->address);
$this->assertInstanceOf(CmsAddressDTO::class, $result[2]['user']->address);

$this->assertEquals($this->fixtures[0]->name, $result[0]['user']->name);
$this->assertEquals($this->fixtures[1]->name, $result[1]['user']->name);
$this->assertEquals($this->fixtures[2]->name, $result[2]['user']->name);

$this->assertEquals($this->fixtures[0]->email->email, $result[0]['user']->email);
$this->assertEquals($this->fixtures[1]->email->email, $result[1]['user']->email);
$this->assertEquals($this->fixtures[2]->email->email, $result[2]['user']->email);

$this->assertEquals($this->fixtures[0]->address->city, $result[0]['user']->address->city);
$this->assertEquals($this->fixtures[1]->address->city, $result[1]['user']->address->city);
$this->assertEquals($this->fixtures[2]->address->city, $result[2]['user']->address->city);

$this->assertEquals($this->fixtures[0]->address->country, $result[0]['user']->address->country);
$this->assertEquals($this->fixtures[1]->address->country, $result[1]['user']->address->country);
$this->assertEquals($this->fixtures[2]->address->country, $result[2]['user']->address->country);

$this->assertEquals($this->fixtures[0]->status,$result[0]['status']);
$this->assertEquals($this->fixtures[1]->status,$result[1]['status']);
$this->assertEquals($this->fixtures[2]->status,$result[2]['status']);

$this->assertEquals($this->fixtures[0]->username,$result[0]['cmsUserUsername']);
$this->assertEquals($this->fixtures[1]->username,$result[1]['cmsUserUsername']);
$this->assertEquals($this->fixtures[2]->username,$result[2]['cmsUserUsername']);
}

private function dumpResultSetMapping(Query $query)
{
$rsm = (\Closure::bind(function ($q) {
return $q->getResultSetMapping();
}, null, Query::class))($query);
echo json_encode(get_object_vars($rsm), JSON_PRETTY_PRINT);
}
}

class ClassWithTooMuchArgs
Expand Down
17 changes: 17 additions & 0 deletions tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,5 +282,22 @@ public function testIndexByMetadataColumn()

$this->assertTrue($this->_rsm->hasIndexBy('lu'));
}

public function testNewObjectNestedArgumentsDeepestLeavesShouldComeFirst()
{
$this->_rsm->addNewObjectAsArgument('objALevel2', 'objALevel1', 0);
$this->_rsm->addNewObjectAsArgument('objALevel3', 'objALevel2', 1);
$this->_rsm->addNewObjectAsArgument('objBLevel3', 'objBLevel2', 0);
$this->_rsm->addNewObjectAsArgument('objBLevel2', 'objBLevel1', 1);

$expectedArgumentMapping = [
'objALevel3' => ['ownerIndex' => 'objALevel2', 'argIndex' => 1],
'objALevel2' => ['ownerIndex' => 'objALevel1', 'argIndex' => 0],
'objBLevel3' => ['ownerIndex' => 'objBLevel2', 'argIndex' => 0],
'objBLevel2' => ['ownerIndex' => 'objBLevel1', 'argIndex' => 1],
];

$this->assertEquals($expectedArgumentMapping, $this->_rsm->nestedNewObjectArguments);
}
}

0 comments on commit b48e6a4

Please sign in to comment.