{"record":{"id":"7be8c6c86b02a979","repo":"doctrine/orm","slug":"paginating-an-entity-with-foreign-key-as-identifie","errorCode":null,"errorMessage":"Paginating an entity with foreign key as identifier only works when using the Output Walkers. Call Paginator#setUseOutputWalkers(true) before iterating the paginator.","messagePattern":"Paginating an entity with foreign key as identifier only works when using the Output Walkers\\. Call Paginator#setUseOutputWalkers\\(true\\) before iterating the paginator\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/Tools/Pagination/LimitSubqueryWalker.php","lineNumber":47,"sourceCode":"\n    /**\n     * Counter for generating unique order column aliases.\n     */\n    private int $aliasCounter = 0;\n\n    public function walkSelectStatement(SelectStatement $selectStatement): void\n    {\n        // Get the root entity and alias from the AST fromClause\n        $from      = $selectStatement->fromClause->identificationVariableDeclarations;\n        $fromRoot  = reset($from);\n        $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;\n        $rootClass = $this->getMetadataForDqlAlias($rootAlias);\n\n        $this->validate($selectStatement);\n        $identifier = $rootClass->getSingleIdentifierFieldName();\n\n        if (isset($rootClass->associationMappings[$identifier])) {\n            throw new RuntimeException('Paginating an entity with foreign key as identifier only works when using the Output Walkers. Call Paginator#setUseOutputWalkers(true) before iterating the paginator.');\n        }\n\n        $query = $this->_getQuery();\n\n        $query->setHint(\n            self::IDENTIFIER_TYPE,\n            Type::getType($rootClass->fieldMappings[$identifier]->type),\n        );\n\n        $query->setHint(self::FORCE_DBAL_TYPE_CONVERSION, true);\n\n        $pathExpression = new PathExpression(\n            PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,\n            $rootAlias,\n            $identifier,\n        );\n\n        $pathExpression->type = PathExpression::TYPE_STATE_FIELD;","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/doctrine/orm/blob/d9b9ff73016bf598ae07515f97289ce8074e97a5/src/Tools/Pagination/LimitSubqueryWalker.php#L29-L65","documentation":"The Paginator can paginate either via an output walker (LimitSubqueryOutputWalker, which writes wrapping SQL) or via AST tree walkers (LimitSubqueryWalker, which rewrites the SELECT clause to the identifier path expression). When the root entity's identifier is itself an association (foreign key used as primary key, e.g. #[Id] #[ManyToOne]), the tree walker cannot express 'u.association-id' as a plain state field, so it aborts. The message states the remedy: let the Paginator use output walkers.","triggerScenarios":"Iterating a Paginator with fetchJoinCollection=true (the constructor default) and maxResults set, where (a) you called $paginator->setUseOutputWalkers(false), or (b) the query itself carries Query::HINT_CUSTOM_OUTPUT_WALKER - Paginator::useOutputWalker() returns false in that case and silently switches to tree walkers. The root entity's single identifier field resolves to an entry in associationMappings (association key / @Id @ManyToOne).","commonSituations":"Entities modelling join tables whose PK is a FK to another entity; older blog posts recommending setUseOutputWalkers(false) as a pagination speed-up copied into modern code; a query-level custom output walker hint accidentally forcing the paginator onto the tree-walker path.","solutions":["Call $paginator->setUseOutputWalkers(true) before iterating, or simply do not disable output walkers.","If the underlying query sets Query::HINT_CUSTOM_OUTPUT_WALKER for its own reasons, remove that hint before paginating - its presence forces tree-walker mode.","Longer term, replace the association-only primary key with a surrogate integer id plus a unique constraint on the FK column."],"exampleFix":"// before\n$paginator = new Paginator($query);\n$paginator->setUseOutputWalkers(false); // RuntimeException when identifier is a FK\nforeach ($paginator as $row) { /* ... */ }\n\n// after\n$paginator = new Paginator($query);\n$paginator->setUseOutputWalkers(true);\nforeach ($paginator as $row) { /* ... */ }","handlingStrategy":"validation","validationCode":"// Guard: entities whose id is an association require output walkers\n$class   = $em->getClassMetadata($rootEntityClass);\n$idField = $class->getSingleIdentifierFieldName();\n$needsOutputWalkers = isset($class->associationMappings[$idField]);\n\n$paginator = new Paginator($query);\nif ($needsOutputWalkers) {\n    $paginator->setUseOutputWalkers(true);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never call setUseOutputWalkers(false) for entities with @Id @ManyToOne identifiers","Remember a query-level HINT_CUSTOM_OUTPUT_WALKER silently forces the Paginator onto tree walkers","Prefer surrogate integer primary keys over FK-only identifiers"],"tags":["doctrine-orm","paginator","pagination","association-key","foreign-key-identifier","output-walker"],"backgroundTag":"orm-paginator-output-walkers","analyzedSha":"d9b9ff73016bf598ae07515f97289ce8074e97a5","analyzedAt":"2026-08-21T06:13:15.863Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}