環境情報
- EC-CUBE 4.0.x
- PHP 7.4.x
- Composer 2.8.x
- Twig 2.12.x → 2.15.x
- Webサーバー: Apache 2.4 + PHP-FPM
- OS: Amazon Linux 2
- 本番制約:
composer updateを実行できない。脆弱性対応を最優先。
EC-CUBE 4.0の本番環境で、Twigを脆弱性対応のために2.12から2.15へ上げる必要がありました。しかし本番のComposer 2.8は、composer.json 内に大文字を含むパッケージ名があると受け付けません。結果として composer update が走らず、脆弱性を抱えたまま稼働が続く状態でした。
そこで本番全体の依存解決は行わず、vendor/twig/twig だけを公式タグのzipで差し替え、composer.lock と vendor/composer/installed.json の該当箇所だけ版と参照を書き換える方法を選びました。追加クラスは1件だけで、TwigはPSR-4で読まれるため、今回の環境ではautoload再生成は不要でした。切り戻しも退避した旧ディレクトリを戻すだけです。
起きたこと
composer update を実行すると、依存解決の前に composer.json のバリデーションで落ちます。エラーは次のようなものです。
Invalid package name "EC-CUBE/...": Package name must be lowercase.
Composerのパッケージ名は小文字が必須です。Composer 2.8ではこの検証が厳しくなり、大文字を含むパッケージ名が composer.json にあるだけでupdateが拒否されます。EC-CUBE本体やプラグイン側で大文字名が残っていると、Twig単体の更新もできません。
この状態で放置すると、Twigの脆弱性修正が入らないまま本番が動き続けます。一方で、本番で composer update を実行して依存全体を動かすのは、PHPバージョン制約、プラグイン互換、DBマイグレーション、ダウンタイムの観点でリスクが大きいです。
取った方針
今回は「環境全体を直す」のではなく「Twigだけ差し替える」を選びました。
-
vendor/twig/twigを公式タグのzipで置換 -
composer.lockのtwig/twigブロックを更新 -
vendor/composer/installed.jsonとinstalled.phpの版情報を更新 - 追加クラスは1件のみ。PSR-4で名前空間と配置が対応するため、autoload再生成は不要
- 切り戻しは退避した旧ディレクトリとバックアップしたlock/installedを戻すだけ
比較表
| アプローチ | メリット | デメリット | 使う条件 |
|---|---|---|---|
composer update で全体更新 |
依存関係が正しく解決される | 本番影響大。プラグイン互換やPHP制約で失敗しやすい | ステージングで検証済み |
composer require twig/twig:^2.15 |
依存解決を任せられる |
composer.json の大文字名検証で同じく落ちる可能性 |
根本修正済み |
vendor/twig/twig を手動差し替え |
最短で脆弱性対応できる | lockと実ファイルが一時的に不整合 | 緊急対応。切り戻し可能 |
| WAFやミドルウェアで緩和 | アプリを触らない | 根本修正にならない | 時間稼ぎ |
手順
0. バックアップ
まずlockとvendorを退避します。本番では必ず復元できる状態にします。
cd /path/to/ec-cube
cp -a composer.lock composer.lock.bak.$(date +%Y%m%d%H%M%S)
cp -a vendor/composer/installed.json vendor/composer/installed.json.bak.$(date +%Y%m%d%H%M%S)
cp -a vendor/composer/installed.php vendor/composer/installed.php.bak.$(date +%Y%m%d%H%M%S)
cp -a vendor/twig/twig vendor/twig/twig.old.$(date +%Y%m%d%H%M%S)
1. 現状確認
現在のTwigバージョンと参照を確認します。
php -v
composer --version
composer show twig/twig
grep -n '"name": "twig/twig"' composer.lock
grep -n '"name": "twig/twig"' vendor/composer/installed.json
composer show が大文字名の検証で落ちる場合は、composer.lock と installed.json を直接確認します。
2. Twig 2.15のzip取得
公式リポジトリのタグzipを取得します。コミットハッシュはGitHub APIから取ります。
curl -s https://api.github.com/repos/twigphp/Twig/git/refs/tags/v2.15.0 | jq -r '.object.sha'
取得したハッシュを使ってzipを展開します。
curl -L -o /tmp/twig-2.15.0.zip \
https://github.com/twigphp/Twig/archive/refs/tags/v2.15.0.zip
unzip -q /tmp/twig-2.15.0.zip -d /tmp/twig-2.15.0
ls /tmp/twig-2.15.0
3. vendor/twig/twigを差し替え
旧ディレクトリはすでに退避済みなので、新しいTwigを配置します。
rm -rf vendor/twig/twig
mv /tmp/twig-2.15.0/Twig-2.15.0 vendor/twig/twig
ls vendor/twig/twig/src
4. composer.lockを更新
composer.lock の twig/twig ブロックだけを更新します。version、version_normalized、source.reference、dist.reference、dist.url を新しい値に合わせます。
{
"name": "twig/twig",
"version": "v2.15.0",
"version_normalized": "2.15.0.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "NEW_COMMIT_HASH"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/NEW_COMMIT_HASH",
"reference": "NEW_COMMIT_HASH",
"shasum": ""
}
}
content-hash は composer.json を変更していないため原則そのままです。ただし手動編集後は composer validate が通るか確認します。
5. installed.json / installed.phpを更新
Composer 2では vendor/composer/installed.json と vendor/composer/installed.php の両方に版情報が入ります。実行時に Composer\InstalledVersions を参照するライブラリがあるため、両方を更新します。
{
"name": "twig/twig",
"version": "v2.15.0",
"version_normalized": "2.15.0.0",
"reference": "NEW_COMMIT_HASH"
}
installed.php は次のような配列です。
'twig/twig' => array(
'pretty_version' => 'v2.15.0',
'version' => '2.15.0.0',
'reference' => 'NEW_COMMIT_HASH',
'type' => 'library',
'install_path' => __DIR__ . '/../twig/twig',
'aliases' => array(),
'dev_requirement' => false,
),
6. OPcacheクリアとPHP-FPMリロード
PHPファイルを差し替えただけではOPcacheが古いコードを返すことがあります。
php -i | grep opcache.enable
sudo systemctl reload php-fpm
必要に応じてApacheやNginxもリロードします。
7. 動作確認
TwigのバージョンとComposerの認識を確認します。
php -r "require 'vendor/autoload.php'; echo \Twig\Environment::VERSION, PHP_EOL;"
php -r "require 'vendor/autoload.php'; echo \Composer\InstalledVersions::getPrettyVersion('twig/twig'), PHP_EOL;"
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/
テンプレート描画、管理画面、フロント画面、キャッシュ生成を一通り確認します。
8. 切り戻し
問題があれば退避ディレクトリとバックアップを戻します。
rm -rf vendor/twig/twig
mv vendor/twig/twig.old.20250101000000 vendor/twig/twig
cp -a composer.lock.bak.20250101000000 composer.lock
cp -a vendor/composer/installed.json.bak.20250101000000 vendor/composer/installed.json
cp -a vendor/composer/installed.php.bak.20250101000000 vendor/composer/installed.php
sudo systemctl reload php-fpm
ハマりポイント
- Composer 2.8の大文字パッケージ名拒否は、
composer updateだけでなくcomposer requireやcomposer dump-autoloadでも発動する可能性があります。 -
composer dump-autoloadが通らない場合、classmap-authoritativeが有効だと新しいクラスを読めません。今回の環境はPSR-4フォールバックが有効だったため不要でした。 -
installed.phpを忘れると、Composer\InstalledVersions::getVersion()が古い値を返します。 - OPcacheをクリアしないと、差し替え後も古いTwigが動いているように見えます。
-
composer installを後で実行すると、手動編集したlockと実ファイルが上書きされる可能性があります。
FAQ
Q. composer install は使えますか?
A. Composer 2.8が composer.json を拒否する場合、installでも同じ検証に落ちる可能性があります。今回は手動差し替えを前提にしました。
Q. autoload再生成は不要ですか?
A. PSR-4で新規クラスが読まれるなら不要です。ただし classmap-authoritative が有効な場合は必要です。composer dump-autoload が通るなら実行してください。
Q. lockとinstalled.jsonのどちらを直すべきですか?
A. 両方です。lockは将来の composer install、installed.jsonとinstalled.phpは実行時メタデータだからです。
Q. 根本解決は?
A. composer.json 内の大文字パッケージ名を小文字に修正し、composer validate --strict を通すことです。
composer validate --strict
composer update --dry-run
学び
次からは、更新が通らないときに「lockと実ファイルのどちらを正とするか」を先に決めます。
- lockを正とするなら、手動差し替えは一時避難。後で必ず依存解決をやり直す。
- 実ファイルを正とするなら、lockとinstalledを合わせて更新し、次のデプロイで戻らないようにする。
この判断を先に決めないと、緊急対応したはずの差分が次のデプロイで消えたり、逆に不整合を起こしたりします。更新が詰まったとき、あなたは環境を直す派ですか。一部だけ差し替える派ですか。
この記事を書いた人
BENTEN Web Works — 業務自動化・システム開発のフリーランスエンジニアです。
GAS / Python / RPA を使った業務自動化や、Web制作・システム開発のご相談を承っています。
「こんなこと自動化できる?」というご質問だけでもお気軽にどうぞ。
👉 BENTEN Web Works — 詳細・お問い合わせはこちら
🐦 X(旧Twitter) — 日々の知見を発信中