Composerの「Dry-Run」とログ徹底活用:依存関係更新時の事故を未然に防ぐ防御的メンテナンス
テックリードの役割とは、華麗な新機能のリリースだけではない。システムが静かに、そして確実に稼働し続けるための「地盤の安全」を死守することだ。
PHPエコシステムの心臓部であるComposer。日々の開発で `composer update` を無思考で叩いていないだろうか?
「ステージング環境では動いたのに、本番デプロイ時に依存パッケージのマイナーバージョン差異で致命的なFatal Errorを踏んだ」「意図しないメジャーアップデートが走り、型定義の不整合で半日溶かした」――こうしたインシデントは、運が悪かったのではない。防御的メンテナンスのプロセスが欠如していた人災である。
本稿では、Composerの内部挙動を解剖し、`–dry-run` と `-vvv` を駆使して「事故を100%事前に検知・ブロックする実務フロー」を構築するための実践的知見を伝授する。
—
1. なぜComposerの更新は事故るのか?(内部挙動の理解)
Composerは単なるファイルのダウンローダーではない。裏側で強力なSAT(Boolean Satisfiability Problem)ソルバーを稼働させ、`composer.json` に記述されたバージョン制約を満たす依存関係の組み合わせを数学的に計算している。
ここで問題になるのが、`^ (caret)` や `~ (tilde)` といったバージョン制約の存在だ。
例えば `^1.4.2` と指定した場合、Composerは `1.4.2` から `2.0.0` 未満の最新版を探し出して解決しようとする。この「最新版への自動追従」こそが、開発時には便利である一方、本番運用においては最大の爆弾となる。
`composer update` を実行すると、以下のプロセスが走る。
1. リモートリポジトリ(Packagist等)からのメタデータ取得: 各パッケージの `composer.json` が持つ依存関係ツリーの読み込み。
2. 依存関係の解決(Solver): 競合が発生しない最大のバージョンコンビネーションの導出。
3. ロックファイルの生成/更新: `composer.lock` への書き込み。
4. ファイルのダウンロードと展開: `vendor/` ディレクトリへの実体配置。
このプロセスにおいて、ステップ2(解決)の時点で何が起きようとしているのかを、ファイルが書き換わる前に完全に把握すること。それがプロのエンジニアの仕事である。
—
2. `–dry-run` による変更差分の完全精査
`–dry-run` オプションは、Composerに「計算とシミュレーションまでやれ、ただしファイルシステムの変更(`composer.lock` の書き換えや `vendor/` の更新)は一切するな」と命じるフラグだ。
実践:更新シミュレーションの実行
実際の変更を行わずに、更新予定のパッケージ一覧とバージョン変化を出力する
composer update –dry-run
このコマンドを実行すると、次のような出力が得られる。
Loading composer repositories with information info
Updating dependencies
Lock file operations: 0 installs, 3 updates, 0 removals
- Upgrading doctrine/inflector (2.0.4 => 2.0.6)
- Upgrading symfony/console (v5.4.11 => v6.0.11) <-- ⚠️ メジャーアップデートの検知!
- Upgrading symfony/string (v5.4.3 => v5.4.11)
Writing lock file
(Dry run)
この出力結果の二行目を見てほしい。`symfony/console` が `v5.4.11` から `v6.0.11` へと、メジャーバージョンを跨いだアップデートがシミュレートされている。PHP 8.1環境でSymfony 6系への移行準備ができていないプロジェクトであれば、この瞬間に事故を未然に防げたことになる。
—
3. `-vvv` オプションによる「SATソルバーの思考プロセス」の覗き見
「なぜComposerはそのバージョンを選んだのか?」
依存関係の競合(Conflict)が発生した際、通常の出力では原因が分からないことが多い。そこで `-vvv`(Very, Very, Very Verbose)の出番である。
詳細ログの取得とファイル保存
デバッグ情報を極限まで引き出し、ログファイルとして非同期保存する
composer update –dry-run -vvv > composer_update_debug.log 2>&1
生成されたログの中身を解釈するための「見るべきポイント」を解説する。
ログリーディングの極意
1. Querying Locally / The Composer Repository:
Composerがどのミラーサーバーやキャッシュからメタデータを引いているかを確認。
2. Loading package repositories:
各パッケージの依存関係(`require`, `conflict`, `replace`)がメモリ上にどう展開されているかのトレース。
3. Dependency resolution exceptions:
もしバージョン解決に失敗した場合、ソルバーがどこで詰まったのか(例:パッケージAがPHP >= 8.2を要求しているが、現在の環境がPHP 8.1である等)の因果関係がツリー構造で出力される。
このログをgrep等で解析することで、チームメンバーから「なぜかビルドが通らない」と言われた際の原因究明を秒速で行うことができる。
—
4. チーム開発の生産性を底上げする「防御的設定」とベストプラクティス
属人性を排除し、チーム全員が安全な依存関係管理を行えるよう、プロジェクトのリポジトリに組み込むべき設定とルールを定義する。
4.1. `composer.json` の堅牢な設計(Config / Provide / Config)
以下に、実プロダクトで採用すべき `composer.json` の本番構成例を示す。
{
“name”: “enterprise/backend-service”,
“description”: “High-performance microservice backend application”,
“type”: “project”,
“require”: {
“php”: “^8.2”,
“ext-pdo”: “”,
“ext-redis”: “”,
“laravel/framework”: “^10.10”,
“guzzlehttp/guzzle”: “^7.5”
},
“require-dev”: {
“phpunit/phpunit”: “^10.0”,
“roave/security-advisories”: “dev-master”
},
“config”: {
“optimize-autoloader”: true,
“preferred-install”: “dist”,
“sort-packages”: true,
“allow-plugins”: {
“pestphp/pest-plugin”: true,
“phpstan/extension-installer”: true
}
},
“minimum-stability”: “stable”,
“prefer-stable”: true
}
アーキテクトのこだわりポイント解説
- `roave/security-advisories`: “dev-master”` の常時導入:
これが最強のセキュリティ・防御プラグイン。既知の脆弱性(CVE)を持つバージョンのパッケージが依存関係に入り込もうとした瞬間、Composerのソルバー自体がエラーを吐いてビルドを強制停止させる。人間の目視確認に頼らない、機械的なセキュリティ担保の要。
- `sort-packages: true`:
`require` や `require-dev` の記述をアルファベット順に自動ソートする。Gitのコンフリクトを劇的に減らすための必須設定。
- `prefer-stable: true`:
安定版(Stable)のリリースが存在する場合、不安定版(RCやBeta)が勝手に選択されるのを防ぎ、プロダクションの堅牢性を保つ。
—
5. CI/CDパイプラインに組み込む「ゼロ事故」運用フロー
ローカルでの手動実行に頼るだけでは、人間のミスを防げない。GitHub ActionsなどのCI/CDパイプラインに、Dry-Runを活用した自動チェック機構を組み込む。
実践:GitHub Actions ワークフロー設定例 (`.github/workflows/composer-security.yml`)
name: Composer Dependency Guard
on:
pull_request:
paths:
- ‘composer.json’
- ‘composer.lock’
jobs:
validate-and-dryrun:
name: Validate and Simulate Dependencies
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP Environment
uses: shivammathur/setup-php@v2
with:
php-version: ‘8.2’
tools: composer:v2
- name: Validate composer.json and composer.lock
run: composer validate –strict
# composer.jsonの構文エラーや、lockファイルとの整合性を厳密にチェック
- name: Check for Security Vulnerabilities (Roave)
run: composer install –no-interaction –prefer-dist
# security-advisoriesが効いている状態で依存関係をインストールし、既知の脆弱性を検知
- name: Simulate Update (Dry-Run Check for Major/Unexpected Updates)
run: |
echo “=== DRY RUN UPDATE SIMULATION ===”
composer update –dry-run –no-interaction
# プルリクエスト作成時に、意図しないバージョンアップが混入していないかをログに残す
このワークフローが機能していれば、開発者がうっかり `composer.json` の制約を緩めてバグを含んだパッケージや意図しないメジャーバージョンのパッケージを指定したプルリクエストを作ったとしても、マージボタンを押す前にCIが検知してブロックしてくれる。
—
テックリードからの総括
ツールに振り回されるな。ツールを飼い慣らせ。
`composer update` を直に叩く行為は、目隠しをして高速道路を逆走するようなものだ。本稿で紹介した `–dry-run` による事前シミュレーション、`-vvv` による内部ロジックの可視化、そして `roave/security-advisories` やCIパイプラインによる機械的ガード。これらを組織の標準プロセスとして定着させることで、あなたのチームは「依存関係地獄(Dependency Hell)」から永遠に解放される。
コードの品質は、それを支える足回り(エコシステム)の堅牢さに比例する。今日からあなたのプロジェクトでも、すべての依存関係更新の前に `–dry-run` を唱える習慣を徹底してほしい。