{"record":{"id":"b7f0fd4c3814ad33","repo":"symfony/translation","slug":"circular-reference-detected-when-adding-a-fallback-catalogue","errorCode":null,"errorMessage":"Circular reference detected when adding a fallback catalogue for locale \"%s\".","messagePattern":"Circular reference detected when adding a fallback catalogue for locale \"(.+?)\"\\.","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"MessageCatalogue.php","lineNumber":176,"sourceCode":"\n        if ($catalogue instanceof MetadataAwareInterface) {\n            $metadata = $catalogue->getMetadata('', '');\n            $this->addMetadata($metadata);\n        }\n\n        if ($catalogue instanceof CatalogueMetadataAwareInterface) {\n            $catalogueMetadata = $catalogue->getCatalogueMetadata('', '');\n            $this->addCatalogueMetadata($catalogueMetadata);\n        }\n    }\n\n    public function addFallbackCatalogue(MessageCatalogueInterface $catalogue): void\n    {\n        // detect circular references\n        $c = $catalogue;\n        while ($c = $c->getFallbackCatalogue()) {\n            if ($c->getLocale() === $this->getLocale()) {\n                throw new LogicException(\\sprintf('Circular reference detected when adding a fallback catalogue for locale \"%s\".', $catalogue->getLocale()));\n            }\n        }\n\n        $c = $this;\n        do {\n            if ($c->getLocale() === $catalogue->getLocale()) {\n                throw new LogicException(\\sprintf('Circular reference detected when adding a fallback catalogue for locale \"%s\".', $catalogue->getLocale()));\n            }\n\n            foreach ($catalogue->getResources() as $resource) {\n                $c->addResource($resource);\n            }\n        } while ($c = $c->parent);\n\n        $catalogue->parent = $this;\n        $this->fallbackCatalogue = $catalogue;\n\n        foreach ($catalogue->getResources() as $resource) {","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/symfony/translation/blob/ae9e8a51bc29780d63c7f9efd6005d7c875e100b/MessageCatalogue.php#L158-L194","documentation":"addFallbackCatalogue links a fallback catalogue under this one. Before linking, it walks the incoming catalogue's existing fallback chain; if any catalogue in that chain has the same locale as $this, linking would create a circular fallback reference, so a LogicException is thrown.","triggerScenarios":"Calling $a->addFallbackCatalogue($b) where $b (or any of $b's fallback ancestors) has the same locale as $a, e.g. $a('en') -> addFallbackCatalogue($b('fr')) when $b already falls back to an 'en' catalogue.","commonSituations":"Programmatically wiring fallback chains from config where locales cycle (en -> fr -> en); reusing the same catalogue object twice in a chain; tests replicating the translator's circular-reference detection.","solutions":["Break the cycle: remove the fallback link that points back to the ancestor locale before adding.","Only chain strictly distinct locales (en -> fr -> de) when assembling fallback catalogues manually.","Build a fresh fallback chain from configuration instead of mutating an existing catalogue graph.","If using the Translator, let it compute fallback locales via fallbackLocales rather than wiring catalogues by hand."],"exampleFix":"// before: en falls back to fr, which falls back to en\n$fr->addFallbackCatalogue($en);\n$en->addFallbackCatalogue($fr); // throws\n\n// after: one-directional chain\n$fr->addFallbackCatalogue($en);","handlingStrategy":"validation","validationCode":"$seen = [];\nfor ($c = $catalogue; null !== $c; $c = $c->getFallbackCatalogue()) {\n    $seen[$c->getLocale()] = true;\n}\nif (isset($seen[$this->getLocale()])) { /* cycle detected */ }","typeGuard":null,"tryCatchPattern":"try {\n    $catalogue->addFallbackCatalogue($fallback);\n} catch (\\LogicException $e) {\n    // rebuild the fallback chain without the cycle\n}","preventionTips":["Keep a set of locales already in the chain and refuse duplicates when wiring.","Derive fallback chains from a flat config list instead of ad-hoc mutation.","Prefer Translator::setFallbackLocales over manual catalogue linking."],"tags":["translation","catalogue","circular-reference","logic-error"],"backgroundTag":"invalid-state-transition","analyzedSha":"ae9e8a51bc29780d63c7f9efd6005d7c875e100b","analyzedAt":"2026-09-15T22:29:15.305Z","contentChangedAt":"2026-09-15T22:29:15.305Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}