{"record":{"id":"c17e826959a58169","repo":"twigphp/Twig","slug":"circular-template-inheritance-detected-while-building-a","errorCode":null,"errorMessage":"Circular template inheritance detected while building a block chain from \"%s\".","messagePattern":"Circular template inheritance detected while building a block chain from \"(.+?)\"\\.","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"src/BlockChain.php","lineNumber":168,"sourceCode":"\n        return $this->blocks = $blocks;\n    }\n\n    /**\n     * @param array<string, mixed> $context\n     *\n     * @return array{list<Template>, bool}\n     */\n    private function resolveLineage(array $context): array\n    {\n        $lineage = [];\n        $fixed = true;\n\n        foreach ($this->templates as $template) {\n            $seen = [];\n            do {\n                if (isset($seen[$id = spl_object_id($template)])) {\n                    throw new \\LogicException(\\sprintf('Circular template inheritance detected while building a block chain from \"%s\".', $template->getTemplateName()));\n                }\n                $seen[$id] = true;\n                $lineage[] = $template;\n\n                $parent = $template->getParent($context);\n                $fixed = $fixed && $template->hasFixedParent();\n\n                // a dynamic parent expression can evaluate to a template from another environment\n                $template = $parent instanceof TemplateWrapper ? $parent->unwrap() : $parent;\n                if (false !== $template && !$template->isOwnedBy($this->env)) {\n                    throw new \\LogicException('A block chain cannot contain templates from different Twig environments.');\n                }\n            } while (false !== $template);\n        }\n\n        return [$lineage, $fixed];\n    }\n","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/twigphp/Twig/blob/a414c3a491defb5a60f2fc88ef79ff37c90010cd/src/BlockChain.php#L150-L186","documentation":"Twig throws this LogicException when resolving the parent chain of templates (a 'block chain') encounters a cycle, i.e. a template whose getParent() eventually returns a template already seen in the chain. Template inheritance must form a finite linear chain; a cycle would loop forever, so Twig aborts eagerly in resolveLineage.","triggerScenarios":"A template's parent expression resolves back to itself or an ancestor — e.g. {% extends self %}-style dynamic parents, a dynamic parent like {% extends someVar %} where someVar names the same or an ancestor template, or two templates extending each other. Triggered when a template using blocks is loaded via resolveBlocks -> resolveLineage.","commonSituations":"Dynamic parent expressions built from variables or config whose values were computed incorrectly; refactoring that accidentally makes template A extend B while B extends A; programmatic template loading where the 'parent' name is built from user data that points back to the same template.","solutions":["Inspect the template named in the message and trace its {% extends %} chain; remove the cycle so each template extends a strict ancestor-free parent.","If the parent is dynamic, add a guard so the evaluated parent never equals or extends the current template.","Log or dump the resolved parent value to find where the variable pointing back to the template comes from.","Restructure shared blocks into a common base template instead of cross-extending templates."],"exampleFix":"// before\n{# base.html.twig #}\n{% extends dynamic_parent %} {# dynamic_parent resolves to 'base.html.twig' itself #}\n\n// after\n{% if dynamic_parent != 'base.html.twig' %}\n{% extends dynamic_parent %}\n{% endif %}","handlingStrategy":"validation","validationCode":"function validatesLineage(\\Twig\\Environment $env, string $name): bool {\n    $visited = [];\n    $tpl = $env->load($name)->unwrap();\n    while (false !== $tpl) {\n        $id = spl_object_id($tpl);\n        if (isset($visited[$id])) { return false; }\n        $visited[$id] = true;\n        $tpl = $tpl->getParent([]);\n    }\n    return true;\n}","typeGuard":null,"tryCatchPattern":"try { $twig->render($template, $vars); } catch (\\LogicException $e) { if (str_contains($e->getMessage(), 'Circular template inheritance')) { log('cycle in extends chain', ['template' => $template]); } throw $e; }","preventionTips":["Never let dynamic {% extends %} variables resolve to the current template or its ancestors.","Validate parent names against a whitelist that excludes the template itself.","Keep inheritance strictly hierarchical and one-directional across templates.","Add a startup smoke-test that loads every base template to surface cycles early."],"tags":["twig","template-inheritance","infinite-loop","logic-error"],"backgroundTag":"internal-invariant-violation","analyzedSha":"a414c3a491defb5a60f2fc88ef79ff37c90010cd","analyzedAt":"2026-09-13T15:10:46.849Z","contentChangedAt":"2026-09-13T15:10:46.849Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}