Skip to content

Commit

Permalink
Document QueryComponent array shape
Browse files Browse the repository at this point in the history
  • Loading branch information
derrabus committed Feb 20, 2022
1 parent 5a55772 commit 76f3ee1
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 158 deletions.
17 changes: 9 additions & 8 deletions lib/Doctrine/ORM/Query/AST/Functions/IdentityFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\SqlWalker;

use function assert;
use function reset;
use function sprintf;

Expand All @@ -23,22 +24,22 @@ class IdentityFunction extends FunctionNode
/** @var PathExpression */
public $pathExpression;

/** @var string */
/** @var string|null */
public $fieldMapping;

/**
* {@inheritdoc}
*/
public function getSql(SqlWalker $sqlWalker)
{
$platform = $sqlWalker->getEntityManager()->getConnection()->getDatabasePlatform();
$quoteStrategy = $sqlWalker->getEntityManager()->getConfiguration()->getQuoteStrategy();
assert($this->pathExpression->field !== null);
$entityManager = $sqlWalker->getEntityManager();
$platform = $entityManager->getConnection()->getDatabasePlatform();
$quoteStrategy = $entityManager->getConfiguration()->getQuoteStrategy();
$dqlAlias = $this->pathExpression->identificationVariable;
$assocField = $this->pathExpression->field;
$qComp = $sqlWalker->getQueryComponent($dqlAlias);
$class = $qComp['metadata'];
$assoc = $class->associationMappings[$assocField];
$targetEntity = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
$assoc = $sqlWalker->getMetadataForDqlAlias($dqlAlias)->associationMappings[$assocField];
$targetEntity = $entityManager->getClassMetadata($assoc['targetEntity']);
$joinColumn = reset($assoc['joinColumns']);

if ($this->fieldMapping !== null) {
Expand All @@ -63,7 +64,7 @@ public function getSql(SqlWalker $sqlWalker)
}

// The table with the relation may be a subclass, so get the table name from the association definition
$tableName = $sqlWalker->getEntityManager()->getClassMetadata($assoc['sourceEntity'])->getTableName();
$tableName = $entityManager->getClassMetadata($assoc['sourceEntity'])->getTableName();

$tableAlias = $sqlWalker->getSQLTableAlias($tableName, $dqlAlias);
$columnName = $quoteStrategy->getJoinColumnName($joinColumn, $targetEntity, $platform);
Expand Down
15 changes: 9 additions & 6 deletions lib/Doctrine/ORM/Query/AST/Functions/SizeFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;

use function assert;

/**
* "SIZE" "(" CollectionValuedPathExpression ")"
*
Expand All @@ -27,18 +29,19 @@ class SizeFunction extends FunctionNode
*/
public function getSql(SqlWalker $sqlWalker)
{
$platform = $sqlWalker->getEntityManager()->getConnection()->getDatabasePlatform();
$quoteStrategy = $sqlWalker->getEntityManager()->getConfiguration()->getQuoteStrategy();
assert($this->collectionPathExpression->field !== null);
$entityManager = $sqlWalker->getEntityManager();
$platform = $entityManager->getConnection()->getDatabasePlatform();
$quoteStrategy = $entityManager->getConfiguration()->getQuoteStrategy();
$dqlAlias = $this->collectionPathExpression->identificationVariable;
$assocField = $this->collectionPathExpression->field;

$qComp = $sqlWalker->getQueryComponent($dqlAlias);
$class = $qComp['metadata'];
$class = $sqlWalker->getMetadataForDqlAlias($dqlAlias);
$assoc = $class->associationMappings[$assocField];
$sql = 'SELECT COUNT(*) FROM ';

if ($assoc['type'] === ClassMetadata::ONE_TO_MANY) {
$targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
$targetClass = $entityManager->getClassMetadata($assoc['targetEntity']);
$targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->getTableName());
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias);

Expand All @@ -60,7 +63,7 @@ public function getSql(SqlWalker $sqlWalker)
. $sourceTableAlias . '.' . $quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $platform);
}
} else { // many-to-many
$targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
$targetClass = $entityManager->getClassMetadata($assoc['targetEntity']);

$owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
$joinTable = $owningAssoc['joinTable'];
Expand Down
51 changes: 40 additions & 11 deletions lib/Doctrine/ORM/Query/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
use Doctrine\ORM\Query\AST\UpdateStatement;
use Doctrine\ORM\Query\AST\WhenClause;
use Doctrine\ORM\Query\AST\WhereClause;
use LogicException;
use ReflectionClass;

use function array_intersect;
Expand All @@ -84,6 +85,16 @@
/**
* An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
* Parses a DQL query, reports any errors in it, and generates an AST.
*
* @psalm-type QueryComponent = array{
* metadata?: ClassMetadata<object>,
* parent?: string|null,
* relation?: mixed[]|null,
* map?: string|null,
* resultVariable?: AST\Node|string,
* nestingLevel: int,
* token: array
* }
*/
class Parser
{
Expand Down Expand Up @@ -143,16 +154,16 @@ class Parser
/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
private $deferredIdentificationVariables = [];

/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
/** @psalm-var list<array{token: mixed, expression: AST\PartialObjectExpression, nestingLevel: int}> */
private $deferredPartialObjectExpressions = [];

/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
/** @psalm-var list<array{token: mixed, expression: AST\PathExpression, nestingLevel: int}> */
private $deferredPathExpressions = [];

/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
private $deferredResultVariables = [];

/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
/** @psalm-var list<array{token: mixed, expression: AST\NewObjectExpression, nestingLevel: int}> */
private $deferredNewObjectExpressions = [];

/**
Expand Down Expand Up @@ -186,7 +197,7 @@ class Parser
/**
* Map of declared query components in the parsed query.
*
* @psalm-var array<string, array<string, mixed>>
* @psalm-var array<string, QueryComponent>
*/
private $queryComponents = [];

Expand Down Expand Up @@ -503,6 +514,7 @@ public function syntaxError($expected = '', $token = null)
* @psalm-param array<string, mixed>|null $token
*
* @return void
* @psalm-return no-return
*
* @throws QueryException
*/
Expand Down Expand Up @@ -709,7 +721,7 @@ private function processDeferredPartialObjectExpressions(): void
{
foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
$expr = $deferredItem['expression'];
$class = $this->queryComponents[$expr->identificationVariable]['metadata'];
$class = $this->getMetadataForDqlAlias($expr->identificationVariable);

foreach ($expr->partialFieldSet as $field) {
if (isset($class->fieldMappings[$field])) {
Expand Down Expand Up @@ -791,8 +803,7 @@ private function processDeferredPathExpressions(): void
foreach ($this->deferredPathExpressions as $deferredItem) {
$pathExpression = $deferredItem['expression'];

$qComp = $this->queryComponents[$pathExpression->identificationVariable];
$class = $qComp['metadata'];
$class = $this->getMetadataForDqlAlias($pathExpression->identificationVariable);

$field = $pathExpression->field;
if ($field === null) {
Expand Down Expand Up @@ -860,7 +871,7 @@ private function processRootEntityAliasSelected(): void
}

foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
if (isset($this->queryComponents[$dqlAlias]) && ! isset($this->queryComponents[$dqlAlias]['parent'])) {
return;
}
}
Expand Down Expand Up @@ -1096,11 +1107,11 @@ public function JoinAssociationPathExpression()
$this->match(Lexer::T_DOT);
$this->match(Lexer::T_IDENTIFIER);

assert($this->lexer->token !== null);
$field = $this->lexer->token['value'];

// Validate association field
$qComp = $this->queryComponents[$identVariable];
$class = $qComp['metadata'];
$class = $this->getMetadataForDqlAlias($identVariable);

if (! $class->hasAssociation($field)) {
$this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
Expand Down Expand Up @@ -1263,6 +1274,7 @@ public function SimpleSelectClause()
public function UpdateClause()
{
$this->match(Lexer::T_UPDATE);
assert($this->lexer->lookahead !== null);

$token = $this->lexer->lookahead;
$abstractSchemaName = $this->AbstractSchemaName();
Expand Down Expand Up @@ -1319,6 +1331,7 @@ public function DeleteClause()
$this->match(Lexer::T_FROM);
}

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

Expand Down Expand Up @@ -1787,6 +1800,7 @@ public function RangeVariableDeclaration()
$this->match(Lexer::T_AS);
}

assert($this->lexer->lookahead !== null);
$token = $this->lexer->lookahead;
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
$classMetadata = $this->em->getClassMetadata($abstractSchemaName);
Expand Down Expand Up @@ -1819,13 +1833,15 @@ public function JoinAssociationDeclaration()
$this->match(Lexer::T_AS);
}

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

$aliasIdentificationVariable = $this->AliasIdentificationVariable();
$indexBy = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;

$identificationVariable = $joinAssociationPathExpression->identificationVariable;
$field = $joinAssociationPathExpression->associationField;

$class = $this->queryComponents[$identificationVariable]['metadata'];
$class = $this->getMetadataForDqlAlias($identificationVariable);
$targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);

// Building queryComponent
Expand Down Expand Up @@ -1897,6 +1913,7 @@ public function PartialObjectExpression()

$partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);

assert($this->lexer->token !== null);
// Defer PartialObjectExpression validation
$this->deferredPartialObjectExpressions[] = [
'expression' => $partialObjectExpression,
Expand Down Expand Up @@ -2330,6 +2347,8 @@ public function SelectExpression()
$aliasResultVariable = null;

if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
assert($this->lexer->lookahead !== null);
assert($expression instanceof AST\Node || is_string($expression));
$token = $this->lexer->lookahead;
$aliasResultVariable = $this->AliasResultVariable();

Expand Down Expand Up @@ -2426,6 +2445,7 @@ public function SimpleSelectExpression()
}

if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
assert($this->lexer->lookahead !== null);
$token = $this->lexer->lookahead;
$resultVariable = $this->AliasResultVariable();
$expr->fieldIdentificationVariable = $resultVariable;
Expand Down Expand Up @@ -3621,4 +3641,13 @@ public function CustomFunctionsReturningStrings()

return $function;
}

private function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata
{
if (! isset($this->queryComponents[$dqlAlias]['metadata'])) {
throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias));
}

return $this->queryComponents[$dqlAlias]['metadata'];
}
}
Loading

0 comments on commit 76f3ee1

Please sign in to comment.