doctrine/orm · error · RuntimeException
Can only process queries that select only one FROM component
Error message
Can only process queries that select only one FROM component
What it means
RootTypeWalker is an internal walker the Paginator attaches (Paginator::convertWhereInIdentifiersToDatabaseValues) to resolve the DBAL type of the root entity's identifier before binding the WHERE id IN (...) parameters. It reads only the first identification variable declaration of the FROM clause; a DQL with two or more FROM components (theta join style) has no single root entity, so the type cannot be resolved and it throws.
Source
Thrown at src/Tools/Pagination/RootTypeWalker.php:36
/**
* Infers the DBAL type of the #Id (identifier) column of the given query's root entity, and
* returns it in place of a real SQL statement.
*
* Obtaining this type is a necessary intermediate step for \Doctrine\ORM\Tools\Pagination\Paginator.
* We can best do this from a tree walker because it gives us access to the AST.
*
* Returning the type instead of a "real" SQL statement is a slight hack. However, it has the
* benefit that the DQL -> root entity id type resolution can be cached in the query cache.
*/
final class RootTypeWalker extends SqlOutputWalker
{
public function walkSelectStatement(AST\SelectStatement $selectStatement): string
{
// Get the root entity and alias from the AST fromClause
$from = $selectStatement->fromClause->identificationVariableDeclarations;
if (count($from) > 1) {
throw new RuntimeException('Can only process queries that select only one FROM component');
}
$fromRoot = reset($from);
$rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
$rootClass = $this->getMetadataForDqlAlias($rootAlias);
$identifierFieldName = $rootClass->getSingleIdentifierFieldName();
return PersisterHelper::getTypeOfField(
$identifierFieldName,
$rootClass,
$this->getQuery()
->getEntityManager(),
)[0];
}
public function getFinalizer(AST\DeleteStatement|AST\UpdateStatement|AST\SelectStatement $AST): SqlFinalizer
{
if (! $AST instanceof AST\SelectStatement) {View on GitHub (pinned to d9b9ff7301)
Solutions
- Rewrite the DQL with a single root and explicit joins: 'FROM App\Entity\User u JOIN u.groups g' or 'JOIN App\Entity\Group g WITH g.user = u'.
- Paginate only the primary entity and hydrate secondary entities in a separate query keyed by the page ids.
- If the second FROM entity is only used as a filter, replace it with a WHERE EXISTS (SELECT ... ) subquery.
Example fix
// before $dql = 'SELECT u, g FROM App\Entity\User u, App\Entity\Group g WHERE u.groupId = g.id'; $paginator = new Paginator($em->createQuery($dql)->setMaxResults(20)); // after $dql = 'SELECT u FROM App\Entity\User u JOIN u.group g'; $paginator = new Paginator($em->createQuery($dql)->setMaxResults(20));
Defensive patterns
Strategy: validation
Validate before calling
// Reject multi-root queries before handing them to the Paginator
$ast = $query->getAST();
$from = $ast->fromClause->identificationVariableDeclarations;
if (count($from) > 1) {
throw new InvalidArgumentException('Paginator queries must have exactly one FROM component, got ' . count($from));
} Prevention
- Forbid comma-separated FROM in DQL via code review or a lint rule
- Express cross-entity conditions with JOIN ... WITH or WHERE EXISTS
- Cover paginated endpoints with tests so a multi-root regression surfaces immediately
When it happens
Trigger: Calling getIterator()/foreach on a Paginator whose query has multiple FROM components (e.g. 'SELECT u, g FROM App\Entity\User u, App\Entity\Group g WHERE ...') while fetchJoinCollection is enabled (constructor default true) and maxResults is set - the Paginator internally runs RootTypeWalker before hydrating the page.
Common situations: Legacy Doctrine 1-style DQL using comma-separated FROM instead of explicit JOIN; repository methods joining unrelated entities via FROM; queries migrated between versions where the second FROM was never converted.
Related errors
- Cannot count query which selects two FROM components, cannot
- Not all identifier properties can be found in the ResultSetM
- Paginating an entity with foreign key as identifier only wor
- Cannot select distinct identifiers from query with LIMIT and
- {argAlias} does not exist
AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21).
Data as JSON: /api/errors/43698a9c3aff37db.
Report an issue: GitHub.