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

add capability to use allfields sql notation #11846

Open
wants to merge 1 commit into
base: 3.4.x
Choose a base branch
from
Open
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
14 changes: 13 additions & 1 deletion docs/en/reference/dql-doctrine-query-language.rst
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,17 @@ The ``NAMED`` keyword must precede all DTO you want to instantiate :
If two arguments have the same name, a ``DuplicateFieldException`` is thrown.
If a field cannot be matched with a property name, a ``NoMatchingPropertyException`` is thrown. This typically happens when using functions without aliasing them.

In a Dto, if you want add all fields of an entity, you can use AllFields notation :
.. code-block:: php

<?php
$query = $em->createQuery('SELECT NEW NAMED CustomerDTO(c.name, NEW NAMED AddressDTO(a.*) AS address) FROM Customer c JOIN c.address a');
$users = $query->getResult(); // array of CustomerDTO

// CustomerDTO => {name : 'DOE', email: null, city: null, address: {id: 18, city: 'New York', zip: '10011'}}

It's recommended to use named arguments Dto with AllFields notation because argument order is not guaranteed.

Using INDEX BY
~~~~~~~~~~~~~~

Expand Down Expand Up @@ -1702,7 +1713,8 @@ Select Expressions
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
NewObjectArg ::= (ScalarExpression | "(" Subselect ")" | NewObjectExpression) ["AS" AliasResultVariable]
NewObjectArg ::= ((ScalarExpression | "(" Subselect ")" | NewObjectExpression) ["AS" AliasResultVariable]) | AllFieldsExpression
AllFieldsExpression ::= IdentificationVariable ".*"

Conditional Expressions
~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
27 changes: 27 additions & 0 deletions src/Query/AST/AllFieldsExpression.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Query\AST;

use Doctrine\ORM\Query\SqlWalker;

/**
* AllFieldsExpression ::= u.*
*
* @link www.doctrine-project.org
*/
class AllFieldsExpression extends Node
{
public string $field = 'fakefield';

public function __construct(
public string|null $identificationVariable,
) {
}

public function dispatch(SqlWalker $walker, int|string $parent = '', int|string $argIndex = '', int|null &$aliasGap = null): string
{
return $walker->walkAllEntityFieldsExpression($this, $parent, $argIndex, $aliasGap);
}
}
15 changes: 13 additions & 2 deletions src/Query/AST/NewObjectExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,27 @@

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 */
public function __construct(public string $className, public array $args)
public bool $hasNamedArgs = false;

/**
* @param class-string $className
* @param mixed[] $args
*/
public function __construct(public string $className, public array $args /*, public bool $hasNamedArgs = false */)
{
if (func_num_args() > 2) {
$this->hasNamedArgs = func_get_arg(2);
}
}

public function dispatch(SqlWalker $walker): string
Expand Down
44 changes: 34 additions & 10 deletions src/Query/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -1036,14 +1036,20 @@
assert($this->lexer->token !== null);
if ($this->lexer->isNextToken(TokenType::T_DOT)) {
$this->match(TokenType::T_DOT);
$this->match(TokenType::T_IDENTIFIER);

$field = $this->lexer->token->value;

while ($this->lexer->isNextToken(TokenType::T_DOT)) {
$this->match(TokenType::T_DOT);
if ($this->lexer->isNextToken(TokenType::T_MULTIPLY)) {
$this->match(TokenType::T_MULTIPLY);
$field = $this->lexer->token->value;
} else {
$this->match(TokenType::T_IDENTIFIER);
$field .= '.' . $this->lexer->token->value;

$field = $this->lexer->token->value;

while ($this->lexer->isNextToken(TokenType::T_DOT)) {
$this->match(TokenType::T_DOT);
$this->match(TokenType::T_IDENTIFIER);
$field .= '.' . $this->lexer->token->value;
}
}
}

Expand Down Expand Up @@ -1106,6 +1112,20 @@
return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
}

/**
* AllFieldsExpression ::= IdentificationVariable
*/
public function AllFieldsExpression(): AST\AllFieldsExpression
{
$identVariable = $this->IdentificationVariable();
assert($this->lexer->token !== null);

$this->match(TokenType::T_DOT);
$this->match(TokenType::T_MULTIPLY);

return new AST\AllFieldsExpression($identVariable);
}

/**
* SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
*/
Expand Down Expand Up @@ -1781,7 +1801,7 @@

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

$expression = new AST\NewObjectExpression($className, $args);
$expression = new AST\NewObjectExpression($className, $args, $useNamedArguments);

Check failure on line 1804 in src/Query/Parser.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (default, phpstan.neon)

Parameter #1 $className of class Doctrine\ORM\Query\AST\NewObjectExpression constructor expects class-string, string given.

Check failure on line 1804 in src/Query/Parser.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (3.8.2, phpstan-dbal3.neon)

Parameter #1 $className of class Doctrine\ORM\Query\AST\NewObjectExpression constructor expects class-string, string given.

// Defer NewObjectExpression validation
$this->deferredNewObjectExpressions[] = [
Expand Down Expand Up @@ -1828,7 +1848,7 @@
}

/**
* NewObjectArg ::= (ScalarExpression | "(" Subselect ")" | NewObjectExpression) ["AS" AliasResultVariable]
* NewObjectArg ::= ((ScalarExpression | "(" Subselect ")" | NewObjectExpression) ["AS" AliasResultVariable]) | AllFieldsExpression
*/
public function NewObjectArg(string|null &$fieldAlias = null): mixed
{
Expand Down Expand Up @@ -1934,10 +1954,14 @@
// it is no function, so it must be a field path
case $lookahead === TokenType::T_IDENTIFIER:
$this->lexer->peek(); // lookahead => '.'
$this->lexer->peek(); // lookahead => token after '.'
$peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
$token = $this->lexer->peek(); // lookahead => token after '.'
$peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
$this->lexer->resetPeek();

if ($token->value === '*') {
return $this->AllFieldsExpression();
}

if ($this->isMathOperator($peek)) {
return $this->SimpleArithmeticExpression();
}
Expand Down
71 changes: 60 additions & 11 deletions src/Query/SqlWalker.php
Original file line number Diff line number Diff line change
Expand Up @@ -1399,7 +1399,7 @@

$sqlParts[] = $col . ' AS ' . $columnAlias;

$this->scalarResultAliasMap[$resultAlias][] = $columnAlias;

Check failure on line 1402 in src/Query/SqlWalker.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (default, phpstan.neon)

Ignored error pattern #^Cannot assign new offset to list\<string\>\|string\.$# (offsetAssign.dimType) in path /home/runner/work/orm/orm/src/Query/SqlWalker.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 1402 in src/Query/SqlWalker.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (3.8.2, phpstan-dbal3.neon)

Ignored error pattern #^Cannot assign new offset to list\<string\>\|string\.$# (offsetAssign.dimType) in path /home/runner/work/orm/orm/src/Query/SqlWalker.php is expected to occur 2 times, but occurred 3 times.

$this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);

Expand Down Expand Up @@ -1507,7 +1507,8 @@
public function walkNewObject(AST\NewObjectExpression $newObjectExpression, string|null $newObjectResultAlias = null): string
{
$sqlSelectExpressions = [];
$objOwner = $objOwnerIdx = null;
$objIndex = $newObjectResultAlias ?: $this->newObjectCounter++;
$aliasGap = $newObjectExpression->hasNamedArgs ? null : 0;

if ($this->newObjectStack !== []) {
[$objOwner, $objOwnerIdx] = end($this->newObjectStack);
Expand All @@ -1517,9 +1518,14 @@
}

foreach ($newObjectExpression->args as $argIndex => $e) {
$resultAlias = $this->scalarResultCounter++;
$columnAlias = $this->getSQLColumnAlias('sclr');
$fieldType = 'string';
if (! $newObjectExpression->hasNamedArgs) {
$argIndex += $aliasGap;
}

$resultAlias = $this->scalarResultCounter++;
$columnAlias = $this->getSQLColumnAlias('sclr');
$fieldType = 'string';
$isScalarResult = true;

switch (true) {
case $e instanceof AST\NewObjectExpression:
Expand Down Expand Up @@ -1567,19 +1573,26 @@
$sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
break;

case $e instanceof AST\AllFieldsExpression:
$isScalarResult = false;
$sqlSelectExpressions[] = $e->dispatch($this, $objIndex, $argIndex, $aliasGap);
break;

default:
$sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
break;
}

$this->scalarResultAliasMap[$resultAlias] = $columnAlias;
$this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
if ($isScalarResult) {
$this->scalarResultAliasMap[$resultAlias] = $columnAlias;
$this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);

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

return implode(', ', $sqlSelectExpressions);
Expand Down Expand Up @@ -2282,6 +2295,42 @@
return $resultAlias;
}

public function walkAllEntityFieldsExpression(AST\AllFieldsExpression $expression, int|string $objIndex, int|string $argIndex, int|null &$aliasGap): string
{
$dqlAlias = $expression->identificationVariable;
$class = $this->getMetadataForDqlAlias($expression->identificationVariable);

$sqlParts = [];
// Select all fields from the queried class
foreach ($class->fieldMappings as $fieldName => $mapping) {
$tableName = isset($mapping->inherited)
? $this->em->getClassMetadata($mapping->inherited)->getTableName()
: $class->getTableName();

$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
$columnAlias = $this->getSQLColumnAlias($mapping->columnName);
$quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);

$col = $sqlTableAlias . '.' . $quotedColumnName;

$type = Type::getType($mapping->type);
$col = $type->convertToPHPValueSQL($col, $this->platform);

$sqlParts[] = $col . ' AS ' . $columnAlias;

$this->scalarResultAliasMap[$objIndex][] = $columnAlias;

Check failure on line 2321 in src/Query/SqlWalker.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (default, phpstan.neon)

Cannot assign new offset to list<string>|string.

Check failure on line 2321 in src/Query/SqlWalker.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (3.8.2, phpstan-dbal3.neon)

Cannot assign new offset to list<string>|string.

$this->rsm->addScalarResult($columnAlias, $objIndex, $mapping->type);

$this->rsm->newObjectMappings[$columnAlias] = [
'objIndex' => $objIndex,
'argIndex' => $aliasGap === null ? $fieldName : $argIndex + $aliasGap++,

Check failure on line 2327 in src/Query/SqlWalker.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (default, phpstan.neon)

Binary operation "+" between int|string and int results in an error.

Check failure on line 2327 in src/Query/SqlWalker.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (3.8.2, phpstan-dbal3.neon)

Binary operation "+" between int|string and int results in an error.
];
}

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

/**
* @return string The list in parentheses of valid child discriminators from the given class
*
Expand Down
22 changes: 22 additions & 0 deletions tests/Tests/Models/CMS/CmsDumbVariadicDTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\CMS;

class CmsDumbVariadicDTO
{
private array $values = [];

public function __construct(...$args)
{
foreach ($args as $key => $val) {
$this->values[$key] = $val;
}
}

public function __get(string $key): mixed
{
return $this->values[$key] ?? null;
}
}
Loading
Loading