Repository navigation
Conversation
|
@seb-jean please review |
| $from = $selectStatement->fromClause->identificationVariableDeclarations; | ||
| $rootAlias = reset($from)->rangeVariableDeclaration->aliasIdentificationVariable; |
There was a problem hiding this comment.
This takes the first FROM alias as the alias of the result items, but with queryProducesDuplicates: false the items can come from another alias: an entity join or a second FROM root. getAssociationPath() then returns null for the items' own alias, so building a cursor throws, even when the query is only ordered by fields of the returned entity:
$query = $em->createQuery(
'SELECT u FROM CmsEmail e JOIN CmsUser u ON u.email = e ORDER BY u.username ASC, u.id ASC',
);
$page = (new CursorPaginator(4, false))->paginate($query);
$page->getNextCursor();
// LogicException: Cannot build a cursor for the ORDER BY item "u.username": its alias must be
// the root alias, or be joined from it through single-valued associations only.SELECT u FROM CmsEmail e, CmsUser u WHERE u.email = e ... throws the same way. Before this PR, both queries paginate correctly, because every value was read from the item itself. (With queryProducesDuplicates: true, they were already unsupported.)
Taking the root alias from the SELECT clause fixes both, and the existing pagination tests still pass with it: replace these two lines with $rootAlias = $this->getRootAlias($selectStatement); and add (with use function is_string;):
/**
* Returns the alias of the entities the query returns: the first selected
* alias that is not fetch joined, or else the first alias of the FROM clause.
*/
private function getRootAlias(SelectStatement $selectStatement): string
{
$queryComponents = $this->getQueryComponents();
foreach ($selectStatement->selectClause->selectExpressions as $selectExpression) {
if (is_string($selectExpression->expression) && ! isset($queryComponents[$selectExpression->expression]['parent'])) {
return $selectExpression->expression;
}
}
$from = $selectStatement->fromClause->identificationVariableDeclarations;
return reset($from)->rangeVariableDeclaration->aliasIdentificationVariable;
}Unbind the parameters only used in the SELECT clause when the id query is built with the LimitSubqueryWalker, which replaces the select clause, like the other paginators do (doctrine#12669). Read the cursor values of ORDER BY items on a joined alias by following the single-valued associations from the root entity, initializing the proxies along the way, and throw a clear exception when the alias cannot be reached that way (doctrine#12671).
Read the cursor values of ORDER BY items on a joined alias by following the single-valued associations from the root entity, initializing the proxies along the way, and keep the property fallback for results that are not entities. Throw a clear exception when the alias cannot be reached that way. See doctrine#12671
getParametersForItem() read every ORDER BY value from the result item, even when the ORDER BY item was on a joined alias, so building a cursor crashed with "Call to a member function getValue() on null". CursorWalker now records the single-valued associations that lead from the root alias to the item's alias, and CursorPaginator follows them to read the value. Proxies are initialized, and an alias reached through a collection is rejected with a LogicException. Results that are not entities keep reading the property named after the field. Tests cover a selected joined entity, an unselected one behind a proxy, a DTO result, a collection join, and a nested association path.
7c5554c to
1b9c127
Compare
|
Rebased the branch on current 3.7.x and squashed it into a single commit. |
|
Thank you @guillaume-sainthillier |
Fixes #12670
CursorPaginator::getParametersForItem()read every ORDER BY value from the result item, even when the ORDER BY item is on a joined alias, so building a cursor crashed withCall to a member function getValue() on null.CursorWalkernow records, for each ORDER BY item, the single-valued associations that lead from the root alias to the item's alias, using the query components (parent/relation).CursorPaginatorfollows them from the root entity to read the value:Results that are not entities (e.g.
SELECT NEWDTOs) keep reading the value from the property named after the field, as before.When the alias cannot be reached that way (joined through a collection, or not joined from the root alias), building the cursor throws a
LogicExceptionthat names the ORDER BY item. There is no single value to take for an entity in that case. The exception is thrown when the cursor is built, not when the query is walked, so first pages that work today keep working.Scalar and mixed results, mentioned in the issue, are not covered here.