DEV Community

sunah kim
sunah kim

Posted on Originally published at zenn.dev

Twig 3.30 Broke Every Page of EC-CUBE with a 500 — Blame the Return Types Added in 3.12

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Two points worth noting:

  • The parent declares doDisplay(): iterable, so I declared the override as : array — a covariant subtype — and added return []; (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:

  1. Syntax check with php -l
  2. Deploy to dev, then bin/console cache:clear → cache:warmup
  3. curl the 8 main pages — all 200
  4. bin/console lint:twig — all 258 templates OK
  5. Confirm 0 ERROR lines in the site log
  6. Confirm composer audit now 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)