Introduction
While running composer audit on a production EC-CUBE 4.3 site, I found a security advisory against twig/twig. "Just bump it to the patched version" — so I updated Twig from the 3.8 line to 3.30 and deployed to the dev environment. Every single page returned 500.
Why does a minor update within the same 3.x line break things this badly? The cause was the return types Twig added to its abstract methods in 3.12.0. This post is a record of the investigation, the 6-line fix, and the verification.
What happened — 500s everywhere and a Fatal error
Right after the deploy, every URL returned 500. The PHP error log showed:
PHP Fatal error: Declaration of Eccube\Twig\Template::getTemplateName()
must be compatible with Twig\Template::getTemplateName(): string
EC-CUBE 4.3 ships its own Eccube\Twig\Template class that extends \Twig\Template, and PHP was complaining that the override is no longer "compatible" with the parent.
The cause — return types on abstract methods in Twig 3.12.0
In 3.12.0, Twig added return types to five abstract methods of its Template class:
| Method | Declaration since 3.12.0 |
|---|---|
getTemplateName() |
: string |
getDebugInfo() |
: array |
getSourceContext() |
: Source |
doDisplay() |
: iterable |
display() |
: void |
In PHP, once a parent method declares a return type, every override must declare a compatible (covariant) return type. Having no return type does not count as compatible. The moment the parent gains a type, every untyped subclass turns into a Fatal error.
To pinpoint which version introduced the change, I compared src/Template.php across tags using GitHub raw:
for v in v3.9.0 v3.11.0 v3.12.0; do
echo "== $v =="
curl -s "https://raw.githubusercontent.com/twigphp/Twig/$v/src/Template.php" \
| grep -E 'abstract .*function|function (getTemplateName|getDebugInfo|getSourceContext|doDisplay|display)\('
done
No types up to v3.11.0; types from v3.12.0. That's the boundary.
The tricky part is on the EC-CUBE side: neither 4.3.0 nor 4.3.1 has return types on this class. The composer.json declares twig/twig: ^3.8, yet in practice it only runs on 3.11 or below. The fix only landed in 4.4.0-alpha-1.
The fix — add return types to 5 overrides (6 lines)
Flip that around and the "answer" already exists in the corresponding file of 4.4.0-alpha-1. I backported it to 4.3 by adding return types to the five overrides in Eccube\Twig\Template:
- public function getTemplateName()
+ public function getTemplateName(): string
- public function getDebugInfo()
+ public function getDebugInfo(): array
- public function getSourceContext()
+ public function getSourceContext(): \Twig\Source
- protected function doDisplay(array $context, array $blocks = [])
+ protected function doDisplay(array $context, array $blocks = []): array
{
+ return [];
}
- public function display(array $context, array $blocks = [])
+ public function display(array $context, array $blocks = []): void
Two points worth noting:
- The parent declares
doDisplay(): iterable, so I declared the override as: array— a covariant subtype — and addedreturn [];(the method never returned anything before). - I wanted to add the
#[\Override]attribute too, but that's a PHP 8.3 feature and the server runs PHP 8.2, so I skipped it this time.
Verification and rollback plan
The fix is 6 lines, but it's a production site, so verification went step by step:
- Syntax check with
php -l - Deploy to dev, then
bin/console cache:clear→cache:warmup - curl the 8 main pages — all 200
-
bin/console lint:twig— all 258 templates OK - Confirm 0 ERROR lines in the site log
- Confirm
composer auditnow reports 0 advisories
I also prepared the rollback path up front: keep a full backup of the pre-update vendor/ directory, and restoring is just a mv swap — about 40 seconds measured. Knowing you can go back makes applying changes to production far less stressful.
Takeaways
- Changing an abstract method's signature is a BC break in semver terms, and Twig did exactly that in 3.12. "Same 3.x, so it's safe" stops being true the moment you inherit from a framework class.
- Keep a list of which upstream abstract methods your codebase overrides. Those are the candidate breakage points for the next update.
- Look at the corresponding file in the upstream's next major release (even an alpha) — the fix you need to backport is often already written there. In this case, 4.4.0-alpha-1 was the answer.
The real takeaway for me: I now have one concrete answer to "I only bumped within 3.x — why did it break?"
Top comments (0)