doctrine/orm · error · RuntimeException

Cannot count query which selects two FROM components, cannot

Error message

Cannot count query which selects two FROM components, cannot make distinction

What it means

WhereInWalker is the Paginator's tree walker that rewrites the paginated query into 'SELECT ... WHERE root.id IN (:dpid)'. It derives the root entity from the first FROM declaration; with two or more identification variable declarations there is no single root to filter on, so it refuses. The identical guard exists in LimitSubqueryOutputWalker::getSQLIdentifier (line 517), so switching to output walkers does not make a multi-root query paginable - the query must have one root.

Source

Thrown at src/Tools/Pagination/WhereInWalker.php:51

class WhereInWalker extends TreeWalkerAdapter
{
    /**
     * ID Count hint name.
     */
    public const HINT_PAGINATOR_HAS_IDS = 'doctrine.paginator_has_ids';

    /**
     * Primary key alias for query.
     */
    public const PAGINATOR_ID_ALIAS = 'dpid';

    public function walkSelectStatement(SelectStatement $selectStatement): void
    {
        // Get the root entity and alias from the AST fromClause
        $from = $selectStatement->fromClause->identificationVariableDeclarations;

        if (count($from) > 1) {
            throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction');
        }

        $fromRoot            = reset($from);
        $rootAlias           = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
        $rootClass           = $this->getMetadataForDqlAlias($rootAlias);
        $identifierFieldName = $rootClass->getSingleIdentifierFieldName();

        $pathType = PathExpression::TYPE_STATE_FIELD;
        if (isset($rootClass->associationMappings[$identifierFieldName])) {
            $pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
        }

        $pathExpression       = new PathExpression(PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $rootAlias, $identifierFieldName);
        $pathExpression->type = $pathType;

        $hasIds = $this->_getQuery()->getHint(self::HINT_PAGINATOR_HAS_IDS);

        if ($hasIds) {

View on GitHub (pinned to d9b9ff7301)

Solutions

  1. Reduce the query to a single FROM component by converting the second root into an explicit JOIN ('JOIN App\Entity\Address a WITH a.user = u') or a WHERE EXISTS subquery.
  2. Paginate the primary entity only, then load the secondary data for the page in a follow-up query.
  3. Compute the total with a dedicated single-root COUNT query and fetch pages manually via setFirstResult/setMaxResults.

Example fix

// before
$dql = 'SELECT u, a FROM App\Entity\User u, App\Entity\Address a WHERE u.id = a.userId';
count(new Paginator($em->createQuery($dql))); // RuntimeException

// after
$dql = 'SELECT u FROM App\Entity\User u JOIN u.address a';
count(new Paginator($em->createQuery($dql)));
Defensive patterns

Strategy: validation

Validate before calling

// Both count() and getIterator() require a single root; check before using Paginator
$ast  = $query->getAST();
$fromCount = count($ast->fromClause->identificationVariableDeclarations);
if ($fromCount !== 1) {
    throw new InvalidArgumentException(sprintf('Cannot paginate a query with %d FROM components', $fromCount));
}

Prevention

When it happens

Trigger: Paginator count() or iteration (tree-walker path: setUseOutputWalkers(false) or a query-level custom output walker hint) over a DQL with more than one FROM component such as 'SELECT u, a FROM App\Entity\User u, App\Entity\Address a WHERE ...'. In output-walker mode the same multi-root query throws the identical message from LimitSubqueryOutputWalker.

Common situations: Theta-style comma joins left over from legacy DQL; repository queries cross-joining an unrelated entity for filtering; attempts to paginate queries that select two entity types at once.

Related errors


AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21). Data as JSON: /api/errors/266249181674e579. Report an issue: GitHub.