doctrine/orm · error · RuntimeException
RootTypeWalker is to be used on SelectStatements only
Error message
RootTypeWalker is to be used on SelectStatements only
What it means
RootTypeWalker resolves the root entity's identifier type and only makes sense for SELECT statements; its getFinalizer() (the ORM 3 SQL-generation API called after walking) guards that the AST is a SelectStatement and throws otherwise. Doctrine's Paginator only attaches RootTypeWalker to SELECT queries, so in practice this fires when application code manually sets Query::HINT_CUSTOM_OUTPUT_WALKER to RootTypeWalker (an internal class) on an UPDATE or DELETE DQL statement.
Solutions
- Remove the manual RootTypeWalker hint - it is internal to Paginator and must not be set by user code.
- If you need the root identifier type, call PersisterHelper::getTypeOfField($class->getSingleIdentifierFieldName(), $class, $em) directly.
- If you set HINT_CUSTOM_OUTPUT_WALKER generically, first verify the statement is a SELECT via $query->getAST().
Example fix
// before use Doctrine\ORM\Tools\Pagination\RootTypeWalker; $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, RootTypeWalker::class); $query->execute(); // UPDATE statement -> RuntimeException // after use Doctrine\ORM\Utility\PersisterHelper; $type = PersisterHelper::getTypeOfField($class->getSingleIdentifierFieldName(), $class, $em)[0];
Defensive patterns
Strategy: validation
Validate before calling
// Only attach a custom output walker to SELECT statements
use Doctrine\ORM\Query\AST\SelectStatement;
if ($query->getAST() instanceof SelectStatement) {
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, $walkerClass);
} else {
throw new InvalidArgumentException('Custom output walkers apply to SELECT queries only');
} Prevention
- Never reference internal walker classes (RootTypeWalker) from application code
- Do not reuse/cloning query objects across different statement types with hints attached
- Resolve field types with the public PersisterHelper API instead of walker hacks
When it happens
Trigger: $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, RootTypeWalker::class) followed by execute()/executeUpdate() on a DQL UPDATE or DELETE; or reusing/cloning a query object that still carries the Paginator's RootTypeWalker hint and changing its DQL to an update/delete.
Common situations: Copying Paginator internals into application code; a shared/cloned query object reused for different statement types; generic listener code that sets output-walker hints on every query it sees.
Related errors
- does not exist
- Can only process queries that select only one FROM component
- Cannot count query which selects two FROM components…
- Given property is not readonly.
- No alias was set before invoking getRootAlias().
AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21).
Data as JSON: /api/errors/7844ffe94156c425.
Report an issue: GitHub.
Appendix: source
Thrown at src/Tools/Pagination/RootTypeWalker.php:55
}
$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) {
throw new RuntimeException(self::class . ' is to be used on SelectStatements only');
}
return new PreparedExecutorFinalizer(new FinalizedSelectExecutor($this->walkSelectStatement($AST)));
}
}
View on GitHub (pinned to d9b9ff7301)