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

  1. Remove the manual RootTypeWalker hint - it is internal to Paginator and must not be set by user code.
  2. If you need the root identifier type, call PersisterHelper::getTypeOfField($class->getSingleIdentifierFieldName(), $class, $em) directly.
  3. 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

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


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