twigphp/Twig · error · LogicException
A block chain cannot contain templates from different Twig…
Error message
A block chain cannot contain templates from different Twig environments.
What it means
All templates in a BlockChain must belong to the same Environment so block inheritance resolution stays consistent. After unwrapping each TemplateWrapper, the constructor checks Template::isOwnedBy($env) and throws this LogicException when a template was loaded from a different Environment instance.
Solutions
- Load every template in the chain from the same Environment instance that is passed as $env.
- Create a fresh BlockChain per Environment instead of sharing chains across them.
- Trace where the mismatched wrapper came from (cache, container, static) and load it from the current Environment instead.
Example fix
// before
$chain = new \Twig\BlockChain($envA, [$envA->load('a.twig'), $envB->load('b.twig')]);
// after
$chain = new \Twig\BlockChain($envA, [$envA->load('a.twig'), $envA->load('b.twig')]); Defensive patterns
Strategy: validation
Validate before calling
foreach ($templates as $t) {
$wrapper = is_string($t) ? $env->load($t) : $t;
if (!$wrapper->unwrap()->isOwnedBy($env)) {
throw new \LogicException('Template loaded from a different Environment.');
}
} Type guard
function ownedByEnv(\Twig\TemplateWrapper $w, \Twig\Environment $env): bool {
return $w->unwrap()->isOwnedBy($env);
} Try / catch
try {
$chain = new \Twig\BlockChain($env, $templates);
} catch (\LogicException $e) {
// reload all entries from $env by name before retrying
} Prevention
- Keep one Environment per app/tenant and load all templates from it.
- Avoid caching TemplateWrapper instances across Environment instances.
- In tests, use the same Environment instance as production code paths.
When it happens
Trigger: Constructing BlockChain with templates loaded via $envA->load('a.html.twig') and $envB->load('b.html.twig'), or a cached/shared template wrapper reused across two Environment instances.
Common situations: Multi-tenant or multi-app setups with several Twig environments sharing template files; test code mixing an isolated test Environment with the app's Environment; frameworks that register template wrappers globally.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Unable to convert encoding: required function iconv() does…
- The "deprecation_info" option must be an instance of
- The "format_list" filter requires the "IntlListFormatter"…
- SpanishInflector is not available.
- The " " filter is part of the , which is not…
AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13).
Data as JSON: /api/errors/9c99cdb5a0207316.
Report an issue: GitHub.
Appendix: source
Thrown at src/BlockChain.php:54
* @param iterable<string|TemplateWrapper> $templates Templates ordered from highest to lowest precedence
* @param array<string, mixed> $context Default variables used to resolve dynamic parent expressions
*/
public function __construct(
private Environment $env,
iterable $templates,
private array $context = [],
) {
foreach ($templates as $template) {
if (\is_string($template)) {
$template = $env->load($template);
}
if (!$template instanceof TemplateWrapper) {
throw new \TypeError(\sprintf('Block chain templates must be strings or "%s" instances, "%s" given.', TemplateWrapper::class, get_debug_type($template)));
}
$template = $template->unwrap();
if (!$template->isOwnedBy($env)) {
throw new \LogicException('A block chain cannot contain templates from different Twig environments.');
}
$this->templates[] = $template;
}
if (!$this->templates) {
throw new \InvalidArgumentException('A block chain requires at least one template.');
}
}
/**
* @param array<string, mixed> $context
*/
public function hasBlock(string $name, array $context = []): bool
{
$blocks = $this->fixed ? $this->blocks : $this->resolveBlocks($context + $this->context + $this->env->getGlobals());
return isset($blocks[$name]);View on GitHub (pinned to a414c3a491)