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.
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)
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
- {argAlias} does not exist
- The attribute "%s" is repeatable. Call getPropertyAttributeC
- The attribute "%s" is not repeatable. Call getPropertyAttrib
- %s::$%s must be readonly property
- %s::$%s must have a type when used with TypedNoDefaultProper
AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21).
Data as JSON: /api/errors/7844ffe94156c425.
Report an issue: GitHub.