はじめに:なぜ `composer.lock` はチーム開発の「地雷」になるのか
テックリードとして多くのPHPプロジェクトを監査・支援してきた中で、いまだに絶えないチーム開発の悲劇がある。
それは、CI/CDパイプラインやステージング環境へのデプロイ直前に、突然依存関係の解決エラー(`Your requirements could not be resolved to an installable set of packages.`)が爆発することだ。
原因の多くは、開発者Aが `composer.json` を修正したものの、うっかり `composer update` を叩いてしまい、意図しないパッケージまでバージョンアップされた `composer.lock` がリモートリポジトリに混入したことにある。あるいは、開発者Bが手元の古い Composer のままで `composer install` を実行し、ローカルのロックファイル構造が勝手に書き換わってコンフリクトを起こすケースだ。
`composer.lock` は、プロジェクト全体の依存関係の「スナップショット」であり、全環境で完全に同一のバイナリ(同等のコードベース)を保証するための聖域である。これを汚すことは、ビルドの再現性を自ら破壊する行為に等しい。
今回は、このロックファイルの意図しない変更を防ぎ、チーム開発の生産性を極限まで高めるためのキーストーンとして `composer update –lock` の正しい運用哲学と実践的なワークフローを伝授する。
—
1. 核心理解:`composer update –lock` とは何か?
多くのエンジニアが混同しているが、`composer update` と `composer install` の挙動の境界線を正確に理解しているチームは意外と少ない。
- `composer install`: `composer.lock` が存在する場合、そこに記載されている正確なバージョンをダウンロードする。`composer.json` が新しくても、ロックファイルが優先される。
- `composer update [
]` : リモートのパッケージリポジトリに問い合わせ、`composer.json` の制約(caret `^` や tilde `~`)の範囲内で最新バージョンを探し、`composer.json` と `composer.lock` の両方を書き換える。
ここで問題になるのが、「`composer.json` に手動で新しいパッケージを追加したり、バージョン制約を変更したが、依存関係自体のアップデート(マイナー/パッチバージョンの繰り上げ)は行いたくない」 というユースケースだ。
ここで登場するのが `composer update –lock` である。
パッケージのダウンロードやリモートリポジトリへの通信を行わず、
現在の composer.json の状態に合わせて composer.lock のメタデータのみを再構築する
composer update –lock
内部で何が起きているのか?
このコマンドを実行すると、Composerはネットワークを一切経由せず、ローカルの `composer.json` と既存の `composer.lock` の差分を計算し、ロックファイルの依存関係ツリーの構造だけを `composer.json` の最新の定義に同期させる。
余計なパッケージのアップデート(予期せぬバグの混入リスク)を完全に排除しながら、定義ファイルの不整合だけを安全に解消できるのだ。
—
2. 実務で役立つ:ロックファイルコンフリクト解消の黄金律
Gitでのマージ時に `composer.lock` がコンフリクトした時、絶対にやってはいけないこと:
- `git checkout –theirs composer.lock` などと適当にどちらか片方を採用する。
- コンフリクトマーカーを手動でエディタの置換等で無理やり消す(JSONの構文が壊れる)。
正しいコンフリクト解消のアルゴリズムは以下の通りである。
1. コンフリクトが発生したら、まずコンフリクトマーカーを綺麗に清算するか、
一度マージ前の状態(あるいは相手のブランチ)の composer.json を正とする
git checkout –ours composer.json
git checkout –theirs composer.lock # またはその逆の意図するjsonを正とする
2. composer.json のみに正しい変更が反映されている状態を作る
3. ネットワークアクセスを伴わない –lock コマンドでロックファイルを完全に再生成する
composer update –lock
4. 最後に正確な依存関係をベンダーディレクトリに反映させる
composer install –prefer-dist –no-interaction
この手順を踏むことで、コンフリクトの泥沼から一瞬で脱出し、チームメンバー全員が完全に同一のロックファイルを共有できる状態を担保できる。
—
3. 生産性を加速させる:開発環境のベストプラクティス設定
ここからは、個人の開発スピードを劇的に高め、チーム全体のインシデントをゼロにするための設定とツール群を紹介する。
3.1. Composer 信頼の「神プラグイン」: `composer-normalize`
`composer.json` の記述順序(`require`, `require-dev`, `autoload` など)が開発者ごとに異なると、JSONの差分が膨らみ、コードレビューのノイズになる。また、ロックファイルとの整合性にも悪影響を及ぼす。
これを自動で綺麗に整地(ソート・フォーマット)してくれるのが `ergebnis/composer-normalize` だ。
グローバルインストール(全プロジェクト共通で恩恵を受ける)
composer global require ergebnis/composer-normalize
プロジェクトの `composer.json` への組み込み(推奨)
チーム全員参加を強制するために、プロジェクトの `require-dev` に組み込み、Gitフックと連携させる。
{
“name”: “example/backend-service”,
“type”: “project”,
“require”: {
“php”: “^8.2”,
“laravel/framework”: “^10.0”
},
“require-dev”: {
“ergebnis/composer-normalize”: “^2.40”
},
“config”: {
“sort-packages”: true,
“allow-plugins”: {
“ergebnis/composer-normalize”: true
}
},
“scripts”: {
“post-update-cmd”: [
“@composer normalize”
]
}
}
- `”sort-packages”: true`: パッケージ名(アルファベット順)を自動ソートする。
- `@composer normalize`: コマンド実行時に自動で `composer.json` を美しいフォーマットに正規化する。
—
3.2. チーム開発で絶対に共有すべき `composer.json` の完全版構成例
プロダクションレディなバックエンド開発において、事故を防ぐための設定が網羅された `composer.json` のベストプラクティス構成を提示する。
{
“name”: “enterprise/core-api”,
“description”: “High-performance backend service with strict dependency management”,
“type”: “project”,
“license”: “proprietary”,
“require”: {
“php”: “^8.3”,
“ext-pdo”: “”,
“ext-json”: “”,
“guzzlehttp/guzzle”: “^7.8”,
“laravel/framework”: “^10.48”
},
“require-dev”: {
“ergebnis/composer-normalize”: “^2.40”,
“friendsofphp/php-cs-fixer”: “^3.40”,
“nunomaduro/larastan”: “^2.9”,
“phpunit/phpunit”: “^10.5”
},
“config”: {
“optimize-autoloader”: true,
“preferred-install”: “dist”,
“sort-packages”: true,
“allow-plugins”: {
“ergebnis/composer-normalize”: true,
“pestphp/pest-plugin”: true
}
},
“scripts”: {
“post-autoload-dump”: [
“Illuminate\\Foundation\\ComposerScripts::postAutoloadDump”,
“@php artisan package:discover –ansi”
],
“post-update-cmd”: [
“@composer normalize”
],
“reflesh-lock”: [
“@composer update –lock –no-scripts”,
“@composer install –prefer-dist”
]
},
“minimum-stability”: “stable”,
“prefer-stable”: true
}
この設定のキラーポイント解説
1. `”optimize-autoloader”: true`: 本番環境と同等のクラスマップ最適化を常時有効化し、パフォーマンス劣化を防ぐ。
2. `”preferred-install”: “dist”`: Gitリポジトリではなく、zipアーカイブ(dist)からの高速インストールを強制し、CI/CDのビルド時間を短縮する。
3. `”reflesh-lock” カスタムスクリプト`:
`composer.json` を手動書き換えした後に `composer run reflesh-lock` と叩くだけで、余計なネットワーク通信をせずに `–lock` でメタデータを整え、即座に整合性の取れた `install` まで一気通貫で実行できるカスタムコマンドを定義している。
—
3.3. 開発スピードを極限まで高めるCLIショートカットとエイリアス
毎度長いコマンドを打つのはエンジニアのタイムロスだ。`~/.zshrc` や `~/.bashrc` に以下のエイリアスを仕込み、指の反射で実行できるようにする。
依存関係を一切更新せず、composer.json の変更をロックファイルにのみ反映する(今回の主役)
alias c-lock=”composer update –lock”
ネットワークを遮断し、ローカルキャッシュのみで超高速にインストール(オフライン時やCIの高速化)
alias c-i-fast=”composer install –prefer-dist –no-dev –optimize-autoloader –no-progress”
composer.json の正規化とロックファイル更新をワンライナーで行う最強コンボ
alias c-sync=”composer normalize && composer update –lock && composer install”
これを導入することで、「あ、依存関係のバージョン変えたのにロックファイル更新し忘れてCIが落ちた」というヒューマンエラーを物理的にゼロに近づけることができる。
—
4. テックリードからの提言:CI/CDパイプラインでの厳格な検証
最後に、チームメンバー全員がこのルールを遵守しているかを機械的に担保するため、GitHub Actions等のCIワークフローに以下のガードレールを設置することを強く推奨する。
name: Composer Dependency Guard
on:
pull_request:
paths:
- ‘composer.json’
- ‘composer.lock’
jobs:
validate-lock:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ‘8.3’
tools: composer:v2
- name: Validate composer.json and composer.lock synchronization
# composer validate は jsonの構文だけでなく、
# lockファイルが json の内容と完全に同期しているかも厳密にチェックする
run: composer validate –strict –no-check-all
この `–strict` オプション付きの `composer validate` は、`composer.json` と `composer.lock` にわずかでも不整合(意図しない変更や同期漏れ)がある場合、容赦なく非ゼロの終了コードを返し、Pull Requestのマージをブロックする。
—
おわりに
ツールは使われるものではなく、使いこなして「事故の起きる余地そのものを排除する」ためにある。
`composer update –lock` という小さなコマンドの持つ意味と背景をチーム全体で共有し、日々の開発から無駄なコンフリクトやデプロイ前の不具合を駆逐してほしい。あなたのプロジェクトのビルドスピードと開発体験が、この知見によって一段上のステージへ引き上げられることを確信している。