開発現場でこんな絶望的な状況に直面したことはないだろうか。
自社製マイクロサービスの共通基幹ライブラリ(仮に `vendor/corp/auth-package` とする)にクリティカルなバグを発見した。あるいは新機能の急ぎの改修が必要になった。
あなたはそのライブラリのローカルリポジトリでバグ修正のブランチを切ってコードを直した。さあ、この修正が、実際にそのライブラリを依存関係として抱えている巨大なAPIアプリケーション(親プロジェクト)で正しく動くか検証したい。
ここで多くのエンジニアが犯す、生産性をドブに捨てる悪手がある。
「よし、`composer link` や `ln -s` でシンボリックリンクを張ろう」
「あるいは、毎回ローカルで `composer packagist` にでも投げるか?」
「いやいや、直接親プロジェクトの `vendor/corp/auth-package` のファイルをエディタで直接書き換えてデバッグして、後からGitに逆移ししよう」
――待ってほしい。そんな泥臭いハックは、今日で終わりにしよう。
シンボリックリンクはファイル監視のバグを生み、Composerのオートローダーキャッシュと激しく衝突し、Vendor配下の直接書き換えは `composer update` を一度走らせた瞬間にすべてを消し去る。
バックエンドアーキテクトである私たちが求めているのは、「本番同等の依存関係解決の整合性を保ったまま、ローカルの特定ブランチのコードをシームレスに親プロジェクトへインジェクトし、爆速でデバッグサイクルを回す環境」だ。
今回は、Composerが密かに持つ至高の機能 `alias`(エイリアス) を使いこなし、この依存関係地獄をエレガントに突破するプロの実践テクニックを徹底解説する。
—
1. なぜシンボリックリンクではダメなのか?Composer内部の挙動と限界
Composerの仕事の本質は、`composer.lock` を介した「依存関係グラフの決定論的な解決(Deterministic Dependency Resolution)」にある。
Composerは、リポジトリのメタデータ(`composer.json`)を読み込み、バージョン制約(SemVer)に厳密に従ってDAG(有向非巡回グラフ)を構築する。ここにシンボリックリンク(`path` リポジトリなど)を安易に導入すると、以下の弊害が生じる。
1. バージョン制約のミスマッチ: 親プロジェクトが `^1.2.0` を求めているとき、ローカルのバグ修正ブランチが `2.0.0-dev`(次期メジャー開発中)であった場合、Composerは「バージョン制約を満たさない」として解決を拒絶するか、意図しないダウングレードを引き起こす。
2. オートローダーの不整合: `composer dump-autoload` の挙動がシンボリックリンク先と曖昧になり、OPcacheやPSR-4のクラスマップ生成で予期せぬキャッシュ汚染が発生する。
ここで登場するのが 「バージョン・エイリアス(Version Aliasing)」 だ。
これを使えば、「Gitの未公開ブランチ(例: `feature/fix-auth`)を、あたかも親プロジェクトが要求する特定の安定バージョン(例: `1.2.x-dev`)であるかのようにComposerに誤認させる」 ことが可能になる。
—
2. 実践:`alias` 機能を使ったローカルブランチ切り替えの全手順
百聞は一見に如かず。ライブラリ側(`auth-package`)と、それを呼び出す親プロジェクト側の設定を構築していこう。
ステップ A: ライブラリ側(`auth-package`)の準備
まず、ライブラリ側のリポジトリでデバッグ用のブランチを切る。
ライブラリのローカルリポジトリに移動
cd /path/to/auth-package
修正用のブランチを作成
git checkout -b feature/critical-fix
ステップ B: 親プロジェクト側(APIアプリケーション)でのリポジトリ定義とエイリアス設定
親プロジェクトの `composer.json` を開き、ローカルのライブラリパスを `repositories` に追加しつつ、どのバージョンとして扱うか(Alias)を明示的に定義する。
以下が、実務でそのまま使える `composer.json` のベストプラクティス構成だ。
{
“name”: “corp/api-application”,
“type”: “project”,
“require”: {
“php”: “^8.2”,
“corp/auth-package”: “1.2.x-dev”
},
“repositories”: [
{
“type”: “path”,
“url”: “packages/auth-package”,
“options”: {
“symlink”: true
}
}
],
“config”: {
“process-timeout”: 0,
“sort-packages”: true
}
}
ここで重要なのは、親プロジェクトが求めている `”corp/auth-package”: “1.2.x-dev”` というバージョン制約だ。
通常、ローカルの `packages/auth-package` 側の `composer.json` が `2.0.0-dev` などのブランチ名を指している場合、バージョンが合わずに弾かれる。これを解決するために、ライブラリ側の `composer.json`、もしくは親プロジェクト側でエイリアスを強制する。
親プロジェクト側で明示的にエイリアスを張る場合は、以下のように記述する。
{
“require”: {
“corp/auth-package”: “1.2.x-dev as 2.0.0-dev”
}
}
↑「ローカルにある `1.2.x-dev`(またはブランチの最新)を、親が求める `2.0.0-dev` として解釈させろ」というComposerへの強力な命令である。
—
3. 開発スピードを極限まで高めるCLIテクニックと隠れたショートカット
設定を書くだけで満足してはいけない。日々の開発で秒単位の時間を削るための、プロフェッショナル向けComposer CLIコマンド群だ。
1. 依存関係の強制再解決(`–prefer-source` との組み合わせ)
ローカルパスリポジトリを変更した際、Composerの内部キャッシュが邪魔をすることがある。以下のコマンドで、キャッシュを無視してクリーンに依存関係を再構築する。
ベンダーディレクトリとロックファイルを綺麗にしつつ、ローカルパスから再構築
composer update corp/auth-package –prefer-source –no-interaction