{"record":{"id":"650015c5f6131aa1","repo":"twigphp/Twig","slug":"a-block-must-be-a-method-on-a-twig-template-instance","errorCode":null,"errorMessage":"A block must be a method on a \\Twig\\Template instance.","messagePattern":"A block must be a method on a \\\\Twig\\\\Template instance\\.","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"src/Template.php","lineNumber":470,"sourceCode":"     */\n    public function yieldBlock($name, array $context, array $blocks = [], $useBlocks = true, ?self $templateContext = null): iterable\n    {\n        if ($useBlocks && isset($blocks[$name])) {\n            $template = $blocks[$name][0];\n            $block = $blocks[$name][1];\n        } elseif (isset($this->blocks[$name])) {\n            $template = $this->blocks[$name][0];\n            $block = $this->blocks[$name][1];\n            // expose this template's own blocks so nested block() calls resolve against them when the block is rendered directly (e.g. block(name, template))\n            $blocks = array_merge($this->blocks, $blocks);\n        } else {\n            $template = null;\n            $block = null;\n        }\n\n        // avoid RCEs when sandbox is enabled\n        if (null !== $template && !$template instanceof self) {\n            throw new \\LogicException('A block must be a method on a \\Twig\\Template instance.');\n        }\n\n        if (null !== $template) {\n            try {\n                $template->ensureSecurityChecked();\n                yield from $template->$block($context, $blocks);\n            } catch (\\Throwable $e) {\n                $template->handleException($e);\n            }\n        } elseif ($parent = $this->getParent($context)) {\n            yield from $parent->unwrap()->yieldBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this);\n        } elseif (isset($blocks[$name])) {\n            throw new RuntimeError(\\sprintf('Block \"%s\" should not call parent() in \"%s\" as the block does not exist in the parent template \"%s\".', $name, $blocks[$name][0]->getTemplateName(), $this->getTemplateName()), -1, $blocks[$name][0]->getSourceContext());\n        } else {\n            throw new RuntimeError(\\sprintf('Block \"%s\" on template \"%s\" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext());\n        }\n    }\n","sourceCodeStart":452,"sourceCodeEnd":488,"githubUrl":"https://github.com/twigphp/Twig/blob/a414c3a491defb5a60f2fc88ef79ff37c90010cd/src/Template.php#L452-L488","documentation":"Template::yieldBlock() renders a named block, optionally delegating to a $template passed in the $arguments. As a security measure (mainly against RCE when the sandbox is enabled), the passed template must be an instance of Twig\\Template; anything else (string class name, callable, arbitrary object) is rejected with this LogicException.","triggerScenarios":"Calling $template->yieldBlock($name, $context, $blocks, $useExtends, ['template' => $notATemplate]) where the 'template' entry is a class-name string or non-Template object. Also reached via displayBlock()/renderBlock() with invalid arguments, e.g. {{ block('x', someObject) }} in templates where someObject is not a Template.","commonSituations":"Passing a template name string instead of a loaded Template object as the inheritance target to block(); custom template-wrapping code that stores templates as class strings; sandbox-escape exploit attempts (the check exists precisely for that).","solutions":["Load the template via $env->load($name) and pass the resulting Template instance, not the name string.","Ensure custom code invoking displayBlock/renderBlock passes null or a Template for the template argument.","In templates, pass a Template object (e.g. from the template's getParent()) to block(), not a raw value from context."],"exampleFix":"// before\n$tpl = 'partials/menu.html.twig';\n$template->renderBlock('content', $context, $blocks, false, ['template' => $tpl]); // throws\n\n// after\n$tpl = $env->load('partials/menu.html.twig');\n$template->renderBlock('content', $context, $blocks, false, ['template' => $tpl]);","handlingStrategy":"type-guard","validationCode":"if (!$target instanceof \\Twig\\Template) {\n    $target = $env->load($target); // resolve name strings to Template\n}","typeGuard":"function asTemplate(mixed $t, \\Twig\\Environment $env): \\Twig\\Template\n{\n    if ($t instanceof \\Twig\\Template) return $t;\n    if (is_string($t)) return $env->load($t);\n    throw new \\InvalidArgumentException('Expected Template or template name');\n}","tryCatchPattern":"try { $out = $template->renderBlock('content', $ctx, $blocks, false, ['template' => $tpl]); } catch (\\LogicException $e) { if (str_contains($e->getMessage(), 'A block must be a method on a')) { /* resolve $tpl to Template */ } throw $e; }","preventionTips":["Pass Template instances, never class-name strings, to block() arguments","Never feed user-controlled context values into block()/renderBlock() template arguments (sandbox RCE guard)","Type-hint internal APIs with Twig\\Template"],"tags":["twig","php","type-mismatch","security","template-engine"],"backgroundTag":"type-mismatch","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"}