Skip to content

Commit affeaba

Browse files
committed
replace all fileds fonctionnality
replace u.* by ALLFIELDS(u) and add an exception when not use named arguments dto add enum properties too
1 parent 5301b99 commit affeaba

File tree

9 files changed

+457
-76
lines changed

9 files changed

+457
-76
lines changed

docs/en/reference/dql-doctrine-query-language.rst

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,18 @@ You can hydrate an entity nested in a DTO :
684684
685685
// CustomerDTO => {name : 'DOE', email: null, address : {city: 'New York', zip: '10011', address: 'Abbey Road'}
686686
687+
In a DTO, if you want to add all fields of an entity, you can use ``ALLFIELDS`` :
688+
689+
.. code-block:: php
690+
691+
<?php
692+
$query = $em->createQuery('SELECT NEW NAMED CustomerDTO(c.name, NEW NAMED AddressDTO(ALLFIELDS(a)) AS address) FROM Customer c JOIN c.address a');
693+
$users = $query->getResult(); // array of CustomerDTO
694+
695+
// CustomerDTO => {name : 'DOE', email: null, city: null, address: {id: 18, city: 'New York', zip: '10011'}}
696+
697+
It's mandatory to use named arguments DTOs with the ``ALLFIELDS`` notation because argument order is not guaranteed, otherwise an exception will be thrown.
698+
687699
Using INDEX BY
688700
~~~~~~~~~~~~~~
689701

@@ -1707,13 +1719,14 @@ Select Expressions
17071719

17081720
.. code-block:: php
17091721
1710-
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
1711-
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
1722+
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
1723+
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
17121724
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
17131725
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
17141726
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
1715-
NewObjectArg ::= (ScalarExpression | "(" Subselect ")" | NewObjectExpression | EntityAsDtoArgumentExpression) ["AS" AliasResultVariable]
1727+
NewObjectArg ::= ((ScalarExpression | "(" Subselect ")" | NewObjectExpression | EntityAsDtoArgumentExpression) ["AS" AliasResultVariable]) | AllFieldsExpression
17161728
EntityAsDtoArgumentExpression ::= IdentificationVariable
1729+
AllFieldsExpression ::= "ALLFIELDS(" IdentificationVariable ")"
17171730
17181731
Conditional Expressions
17191732
~~~~~~~~~~~~~~~~~~~~~~~

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2811,7 +2811,7 @@ parameters:
28112811
-
28122812
message: '#^Cannot assign new offset to list\<string\>\|string\.$#'
28132813
identifier: offsetAssign.dimType
2814-
count: 2
2814+
count: 3
28152815
path: src/Query/SqlWalker.php
28162816

28172817
-
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ORM\Query\AST;
6+
7+
use Doctrine\ORM\Query\SqlWalker;
8+
9+
/**
10+
* AllFieldsExpression ::= u.*
11+
*
12+
* @link www.doctrine-project.org
13+
*/
14+
class AllFieldsExpression extends Node
15+
{
16+
public string $field = '';
17+
18+
public function __construct(
19+
public string|null $identificationVariable,
20+
) {
21+
$this->field = $this->identificationVariable . '.*';
22+
}
23+
24+
public function dispatch(SqlWalker $walker, int|string $parent = '', int|string $argIndex = '', int|null &$aliasGap = null): string
25+
{
26+
return $walker->walkAllEntityFieldsExpression($this, $parent, $argIndex, $aliasGap);
27+
}
28+
}

src/Query/AST/NewObjectExpression.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class NewObjectExpression extends Node
2020
* @param class-string $className
2121
* @param mixed[] $args
2222
*/
23-
public function __construct(public string $className, public array $args)
23+
public function __construct(public string $className, public array $args, public bool $hasNamedArgs = false)
2424
{
2525
}
2626

src/Query/Parser.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,21 @@ public function EntityAsDtoArgumentExpression(): AST\EntityAsDtoArgumentExpressi
11501150
return new AST\EntityAsDtoArgumentExpression($expression, $identVariable);
11511151
}
11521152

1153+
/**
1154+
* AllFieldsExpression ::= "ALLFIELDS(" IdentificationVariable ")"
1155+
*/
1156+
public function AllFieldsExpression(): AST\AllFieldsExpression
1157+
{
1158+
assert($this->lexer->token !== null);
1159+
1160+
$this->match(TokenType::T_ALLFIELDS);
1161+
$this->match(TokenType::T_OPEN_PARENTHESIS);
1162+
$identVariable = $this->IdentificationVariable();
1163+
$this->match(TokenType::T_CLOSE_PARENTHESIS);
1164+
1165+
return new AST\AllFieldsExpression($identVariable);
1166+
}
1167+
11531168
/**
11541169
* SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
11551170
*/
@@ -1826,7 +1841,7 @@ public function NewObjectExpression(): AST\NewObjectExpression
18261841

18271842
$this->match(TokenType::T_CLOSE_PARENTHESIS);
18281843

1829-
$expression = new AST\NewObjectExpression($className, $args);
1844+
$expression = new AST\NewObjectExpression($className, $args, $useNamedArguments);
18301845

18311846
// Defer NewObjectExpression validation
18321847
$this->deferredNewObjectExpressions[] = [
@@ -1873,7 +1888,7 @@ public function addArgument(array &$args, bool $useNamedArguments): void
18731888
}
18741889

18751890
/**
1876-
* NewObjectArg ::= (ScalarExpression | "(" Subselect ")" | NewObjectExpression) ["AS" AliasResultVariable]
1891+
* NewObjectArg ::= ((ScalarExpression | "(" Subselect ")" | NewObjectExpression) ["AS" AliasResultVariable]) | AllFieldsExpression
18771892
*/
18781893
public function NewObjectArg(string|null &$fieldAlias = null): mixed
18791894
{
@@ -1893,6 +1908,8 @@ public function NewObjectArg(string|null &$fieldAlias = null): mixed
18931908
$this->match(TokenType::T_CLOSE_PARENTHESIS);
18941909
} elseif ($token->type === TokenType::T_NEW) {
18951910
$expression = $this->NewObjectExpression();
1911+
} elseif ($token->type === TokenType::T_ALLFIELDS) {
1912+
$expression = $this->AllFieldsExpression();
18961913
} elseif ($token->type === TokenType::T_IDENTIFIER && $peek->type !== TokenType::T_DOT && $peek->type !== TokenType::T_OPEN_PARENTHESIS) {
18971914
$expression = $this->EntityAsDtoArgumentExpression();
18981915
} else {
@@ -1985,8 +2002,8 @@ public function ScalarExpression(): mixed
19852002
// it is no function, so it must be a field path
19862003
case $lookahead === TokenType::T_IDENTIFIER:
19872004
$this->lexer->peek(); // lookahead => '.'
1988-
$this->lexer->peek(); // lookahead => token after '.'
1989-
$peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
2005+
$token = $this->lexer->peek(); // lookahead => token after '.'
2006+
$peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
19902007
$this->lexer->resetPeek();
19912008

19922009
if ($this->isMathOperator($peek)) {

src/Query/SqlWalker.php

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,9 @@ public function walkSelectExpression(AST\SelectExpression $selectExpression): st
13521352
$sql .= $this->walkNewObject($expr, $selectExpression->fieldIdentificationVariable);
13531353
break;
13541354

1355+
case $expr instanceof AST\AllFieldsExpression:
1356+
throw new LogicException('All fields Expression are only supported in DTO.');
1357+
13551358
default:
13561359
// IdentificationVariable or PartialObjectExpression
13571360
if ($expr instanceof AST\PartialObjectExpression) {
@@ -1518,11 +1521,17 @@ public function walkNewObject(AST\NewObjectExpression $newObjectExpression, stri
15181521
{
15191522
$sqlSelectExpressions = [];
15201523
$objIndex = $newObjectResultAlias ?: $this->newObjectCounter++;
1524+
$aliasGap = $newObjectExpression->hasNamedArgs ? null : 0;
15211525

15221526
foreach ($newObjectExpression->args as $argIndex => $e) {
1523-
$resultAlias = $this->scalarResultCounter++;
1524-
$columnAlias = $this->getSQLColumnAlias('sclr');
1525-
$fieldType = 'string';
1527+
if (! $newObjectExpression->hasNamedArgs) {
1528+
$argIndex += $aliasGap;
1529+
}
1530+
1531+
$resultAlias = $this->scalarResultCounter++;
1532+
$columnAlias = $this->getSQLColumnAlias('sclr');
1533+
$fieldType = 'string';
1534+
$isScalarResult = true;
15261535

15271536
switch (true) {
15281537
case $e instanceof AST\NewObjectExpression:
@@ -1576,18 +1585,30 @@ public function walkNewObject(AST\NewObjectExpression $newObjectExpression, stri
15761585
$sqlSelectExpressions[] = trim($e->dispatch($this));
15771586
break;
15781587

1588+
case $e instanceof AST\AllFieldsExpression:
1589+
if (! $newObjectExpression->hasNamedArgs) {
1590+
throw new LogicException('All fields Expression must be used with named arguments DTO constructor.');
1591+
}
1592+
1593+
$isScalarResult = false;
1594+
$sqlSelectExpressions[] = $e->dispatch($this, $objIndex, $argIndex, $aliasGap);
1595+
break;
1596+
15791597
default:
15801598
$sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
15811599
break;
15821600
}
15831601

1584-
$this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1585-
$this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1602+
if ($isScalarResult) {
1603+
$this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1604+
$this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
15861605

1587-
$this->rsm->newObjectMappings[$columnAlias] = [
1588-
'objIndex' => $objIndex,
1589-
'argIndex' => $argIndex,
1590-
];
1606+
$this->rsm->newObjectMappings[$columnAlias] = [
1607+
'className' => $newObjectExpression->className,
1608+
'objIndex' => $objIndex,
1609+
'argIndex' => $argIndex,
1610+
];
1611+
}
15911612
}
15921613

15931614
$this->rsm->newObject[$objIndex] = $newObjectExpression->className;
@@ -2292,6 +2313,46 @@ public function walkResultVariable(string $resultVariable): string
22922313
return $resultAlias;
22932314
}
22942315

2316+
public function walkAllEntityFieldsExpression(AST\AllFieldsExpression $expression, int|string $objIndex, int|string $argIndex, int|null &$aliasGap): string
2317+
{
2318+
$dqlAlias = $expression->identificationVariable;
2319+
$class = $this->getMetadataForDqlAlias($expression->identificationVariable);
2320+
2321+
$sqlParts = [];
2322+
// Select all fields from the queried class
2323+
foreach ($class->fieldMappings as $fieldName => $mapping) {
2324+
$tableName = isset($mapping->inherited)
2325+
? $this->em->getClassMetadata($mapping->inherited)->getTableName()
2326+
: $class->getTableName();
2327+
2328+
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
2329+
$columnAlias = $this->getSQLColumnAlias($mapping->columnName);
2330+
$quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
2331+
2332+
$col = $sqlTableAlias . '.' . $quotedColumnName;
2333+
2334+
$type = Type::getType($mapping->type);
2335+
$col = $type->convertToPHPValueSQL($col, $this->platform);
2336+
2337+
$sqlParts[] = $col . ' AS ' . $columnAlias;
2338+
2339+
$this->scalarResultAliasMap[$objIndex][] = $columnAlias;
2340+
2341+
$this->rsm->addScalarResult($columnAlias, $objIndex, $mapping->type);
2342+
2343+
if ($mapping->enumType !== null) {
2344+
$this->rsm->addEnumResult($columnAlias, $mapping->enumType);
2345+
}
2346+
2347+
$this->rsm->newObjectMappings[$columnAlias] = [
2348+
'objIndex' => $objIndex,
2349+
'argIndex' => $aliasGap === null ? $fieldName : (int) $argIndex + $aliasGap++,
2350+
];
2351+
}
2352+
2353+
return implode(', ', $sqlParts);
2354+
}
2355+
22952356
/**
22962357
* @return string The list in parentheses of valid child discriminators from the given class
22972358
*

src/Query/TokenType.php

Lines changed: 59 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -32,62 +32,63 @@ enum TokenType: int
3232
case T_IDENTIFIER = 102;
3333

3434
// All keyword tokens should be >= 200
35-
case T_ALL = 200;
36-
case T_AND = 201;
37-
case T_ANY = 202;
38-
case T_AS = 203;
39-
case T_ASC = 204;
40-
case T_AVG = 205;
41-
case T_BETWEEN = 206;
42-
case T_BOTH = 207;
43-
case T_BY = 208;
44-
case T_CASE = 209;
45-
case T_COALESCE = 210;
46-
case T_COUNT = 211;
47-
case T_DELETE = 212;
48-
case T_DESC = 213;
49-
case T_DISTINCT = 214;
50-
case T_ELSE = 215;
51-
case T_EMPTY = 216;
52-
case T_END = 217;
53-
case T_ESCAPE = 218;
54-
case T_EXISTS = 219;
55-
case T_FALSE = 220;
56-
case T_FROM = 221;
57-
case T_GROUP = 222;
58-
case T_HAVING = 223;
59-
case T_HIDDEN = 224;
60-
case T_IN = 225;
61-
case T_INDEX = 226;
62-
case T_INNER = 227;
63-
case T_INSTANCE = 228;
64-
case T_IS = 229;
65-
case T_JOIN = 230;
66-
case T_LEADING = 231;
67-
case T_LEFT = 232;
68-
case T_LIKE = 233;
69-
case T_MAX = 234;
70-
case T_MEMBER = 235;
71-
case T_MIN = 236;
72-
case T_NEW = 237;
73-
case T_NOT = 238;
74-
case T_NULL = 239;
75-
case T_NULLIF = 240;
76-
case T_OF = 241;
77-
case T_OR = 242;
78-
case T_ORDER = 243;
79-
case T_OUTER = 244;
80-
case T_PARTIAL = 245;
81-
case T_SELECT = 246;
82-
case T_SET = 247;
83-
case T_SOME = 248;
84-
case T_SUM = 249;
85-
case T_THEN = 250;
86-
case T_TRAILING = 251;
87-
case T_TRUE = 252;
88-
case T_UPDATE = 253;
89-
case T_WHEN = 254;
90-
case T_WHERE = 255;
91-
case T_WITH = 256;
92-
case T_NAMED = 257;
35+
case T_ALL = 200;
36+
case T_AND = 201;
37+
case T_ANY = 202;
38+
case T_AS = 203;
39+
case T_ASC = 204;
40+
case T_AVG = 205;
41+
case T_BETWEEN = 206;
42+
case T_BOTH = 207;
43+
case T_BY = 208;
44+
case T_CASE = 209;
45+
case T_COALESCE = 210;
46+
case T_COUNT = 211;
47+
case T_DELETE = 212;
48+
case T_DESC = 213;
49+
case T_DISTINCT = 214;
50+
case T_ELSE = 215;
51+
case T_EMPTY = 216;
52+
case T_END = 217;
53+
case T_ESCAPE = 218;
54+
case T_EXISTS = 219;
55+
case T_FALSE = 220;
56+
case T_FROM = 221;
57+
case T_GROUP = 222;
58+
case T_HAVING = 223;
59+
case T_HIDDEN = 224;
60+
case T_IN = 225;
61+
case T_INDEX = 226;
62+
case T_INNER = 227;
63+
case T_INSTANCE = 228;
64+
case T_IS = 229;
65+
case T_JOIN = 230;
66+
case T_LEADING = 231;
67+
case T_LEFT = 232;
68+
case T_LIKE = 233;
69+
case T_MAX = 234;
70+
case T_MEMBER = 235;
71+
case T_MIN = 236;
72+
case T_NEW = 237;
73+
case T_NOT = 238;
74+
case T_NULL = 239;
75+
case T_NULLIF = 240;
76+
case T_OF = 241;
77+
case T_OR = 242;
78+
case T_ORDER = 243;
79+
case T_OUTER = 244;
80+
case T_PARTIAL = 245;
81+
case T_SELECT = 246;
82+
case T_SET = 247;
83+
case T_SOME = 248;
84+
case T_SUM = 249;
85+
case T_THEN = 250;
86+
case T_TRAILING = 251;
87+
case T_TRUE = 252;
88+
case T_UPDATE = 253;
89+
case T_WHEN = 254;
90+
case T_WHERE = 255;
91+
case T_WITH = 256;
92+
case T_NAMED = 257;
93+
case T_ALLFIELDS = 258;
9394
}

0 commit comments

Comments
 (0)