こんにちは!日々のPHP開発、本当にお疲れ様です。
複数のマイクロサービスや、共通ライブラリとアプリケーションを1つのリポジトリで管理する「モノレポ(Monorepo)」構成、最近とても増えていますよね。
「共通の認証モジュールをちょっと修正しただけなのに、わざわざ別リポジトリにpushして、パッケージのバージョンを上げて、`composer update`を走らせて……あぁ、面倒くさい!」
そんなプチストレスを抱えたまま開発していませんか?
これを解決するのが、今回解説する Composer Workspaces(pathリポジトリ活用によるローカルパッケージのシームレスな参照) です。
これをマスターすれば、ローカルのコード変更がリアルタイムで各プロジェクトに反映されるようになり、毎日のコーディングとデバッグが劇的に楽になりますよ。
今回は、初心者の方でもつまずかないよう、ツールの役割から実際のセットアップ、そして「おぉ、こうやって動いているのか!」と感動できる動作確認まで、優しく丁寧に解説していきますね。
—
1. なぜComposer Workspaces(pathリポジトリ)が必要なのか?
まずは、私たちが直面している課題と、Composerが内部でどう動いているのかを整理しましょう。
従来の辛い開発フロー
通常のComposerは、Packagistなどのリモートリポジトリや、Gitリポジトリから外部パッケージをダウンロードして `vendor/` ディレクトリに配置します。
もしあなたが「アプリA」と「共通ライブラリB」を別々に管理していて、アプリAからライブラリBを使いたい場合、以下のような面倒な手順を踏むことになります。
1. ライブラリBのコードを修正する。
2. ライブラリBの変更をGitにコミット&プッシュする(またはタグを切る)。
3. アプリAの `composer.json` のバージョン指定を更新する。
4. アプリAで `composer update vendor/lib-b` を実行する。
これでは、ちょっとしたバグ修正や機能追加のたびにタイムロスが発生し、開発スピードが落ちてしまいます。
Pathリポジトリという魔法
Composerの `repositories` 機能にある `type: “path”` を使うと、リモートからダウンロードする代わりに、ローカルの別ディレクトリにあるコードへ直接シンボリックリンク(またはコピー)を張ることができます。
これにより、ライブラリB側のコードをエディタで書き換えた瞬間、アプリA側からその変更を即座に読み込めるようになります。モノレポ環境において、これほど開発体験を爆発的に向上させる機能はありません。
—
2. 実践!モノレポ環境の基本セットアップ
百聞は一見にしかず。実際に手を動かして、ローカルパッケージが連携する環境を作ってみましょう。
今回は、以下のようなシンプルなモノレポ構造を想定します。
my-monorepo/
├── apps/
│ └── web-app/ # アプリケーション(ライブラリに依存)
└── packages/
└── logger-lib/ # 共通のログ出力ライブラリ
ステップ1: ディレクトリの作成
まずは、プロジェクト全体のルートディレクトリを作り、その中にアプリとパッケージの置き場所を用意します。
モノレポ用のルートディレクトリを作成
mkdir my-monorepo
cd my-monorepo
アプリ用とパッケージ用のフォルダを作成
mkdir -p apps/web-app packages/logger-lib
ステップ2: 共通ライブラリ (`logger-lib`) の作成
最初に、依存される側の共通ライブラリを設定します。
cd packages/logger-lib
ここで、ライブラリ側の `composer.json` を作成します。
`packages/logger-lib/composer.json`
{
“name”: “acme/logger-lib”,
“description”: “モノレポ用の共通ロガーライブラリ”,
“version”: “1.0.0”,
“type”: “library”,
“license”: “MIT”,
“autoload”: {
“psr-4”: {
“Acme\\Logger\\”: “src/”
}
},
“require”: {
“php”: “>=8.1”
}
}
- 解説: パッケージ名を `acme/logger-lib` と定義し、PSR-4オートローディングによって `src/` ディレクトリ配下のクラスが `Acme\Logger\` 名前空間で読み込まれるようにしています。
次に、ライブラリのソースコードを書きましょう。
mkdir src
`packages/logger-lib/src/Logger.php`
ステップ3: アプリケーション (`web-app`) の作成とPathリポジトリの設定
次に、このライブラリを利用するアプリケーション側を設定します。ルートディレクトリに戻りましょう。
cd ../../apps/web-app
ここでアプリケーション側の `composer.json` を作成しますが、これが今回の核心部分です。`repositories` に `path` を指定します。
`apps/web-app/composer.json`
{
“name”: “acme/web-app”,
“description”: “共通ロガーを利用するWebアプリケーション”,
“type”: “project”,
“require”: {
“php”: “>=8.1”,
“acme/logger-lib”: “^1.0”
},
“repositories”: [
{
“type”: “path”,
“url”: “../../packages/logger-lib”,
“options”: {
“symlink”: true
}
}
],
“autoload”: {
“psr-4”: {
“App\\”: “src/”
}
}
}
- 解説:
- `repositories`: Composerに対して「パッケージを探す場所」を追加しています。ここでは `type: “path”` を指定し、相対パスで `../../packages/logger-lib` を指しています。
- `symlink: true`: これが非常に重要です。ファイルをコピーするのではなく、シンボリックリンク(ショートカットのようなもの)を作成するため、ライブラリ側の変更が即座にアプリ側に反映されます。
- `require`: 通常の外部パッケージと同様に `”acme/logger-lib”: “^1.0″` を指定して依存関係を宣言します。
—
3. 精度高い HelloWorld 的な動作確認
設定が完了したので、実際にアプリからローカルライブラリを呼び出してみましょう。
1. アプリ側の依存関係をインストール
`apps/web-app` ディレクトリにいる状態で、Composerコマンドを実行します。
composer install
実行時のログのイメージ:
Installing dependencies from lock file
Verifying lock file contents
…
- Installing acme/logger-lib (1.0.0): Symlinked from ../../packages/logger-lib
注目して欲しいのは、最後の行です。`Symlinked from …` と表示されていますね!これでローカルのコードが正しくリンクされました。
2. アプリのコードを作成して実行
動作確認用のスクリプトを作成します。
mkdir src
`apps/web-app/src/Application.php`
log(“Composer Workspaces経由でローカルパッケージが正常に動いています!”);
}
}
さらに、これを実行するためのエントリーポイント(`index.php`)をプロジェクトルート付近、または適当な場所に作成します。今回は分かりやすく `run.php` を作成しましょう。
`apps/web-app/run.php`
run();
3. 実行してみる!
ターミナルで以下のコマンドを実行します。
php run.php
出力結果:
[2023-10-25 12:34:56] LOG: Composer Workspaces経由でローカルパッケージが正常に動いています!
お見事です!外部のサーバーやレジストリを一切介さず、モノレポ内のローカルパッケージが完璧に連携して動作しました。
—
4. アーキテクトが教える、大規模開発におけるバージョン競合回避の極意
ここまでの基本ができれば十分実用的ですが、さらに大規模な開発チームや複雑なモノレポを運用する上で、知っておくべき「プロの知見」をいくつか授けましょう。
1. シンボリックリンクの罠とCI/CD環境での注意点
ローカル開発では `symlink: true` が非常に便利ですが、GitHub ActionsなどのCI/CDパイプラインやDockerコンテナビルドの際、ディレクトリ構造が変わっていてシンボリックリンクが切れてしまうトラブルがよく起きます。
そのため、CI環境では `–prefer-source` や環境に応じたcomposerの挙動に気を配るか、あるいはDockerビルド時にマルチステージビルドや適切なボリュームマウントを活用して、パスが正しく解決されるように設計してください。
2. 依存関係の「バージョン競合」をどう防ぐか?
モノレポ内で複数のアプリ(`web-app`, `api-app`, `admin-app`)が同じ共通ライブラリ (`logger-lib`) を参照する場合、それぞれのアプリが要求するバージョン(`^1.0` や `^2.0` など)がバラバラになると、Composerの依存性解決(SATsolver)が破綻し、エラーを引き起こします。
ベストプラクティス:
- モノレポ内の共通ライブラリは、基本的に「常に最新のメインブランチのコードが全アプリで共有される」という前提で運用します。
- 各アプリの `composer.json` では、ローカルパスリポジトリに対して適切な制約(“ や `^1.0` など)をかけつつ、開発時は常に最新のコードがリンクされるように統一します。
- もし破壊的変更(Breaking Changes)が入る場合は、パッケージ側をメジャーバージョンアップ(`2.0.0`)し、移行期間を設けて各アプリ側の `composer.json` の要求バージョンを順次書き換えていきます。
—
まとめ
今回は、Composerの `path` リポジトリを用いたモノレポ管理の極意について解説しました。
- ツールの役割: リモートにプッシュせずとも、ローカルの別ディレクトリにあるPHPパッケージをシンボリックリンクで即座に利用できる。
- セットアップ: `composer.json` の `repositories` に `type: “path”` と `symlink: true` を記述するだけ。
- メリット: コードの修正から動作確認までのリードタイムがゼロになり、開発体験が劇的に向上する。
この手法を取り入れるだけで、マルチパッケージ・マイクロサービス的なPHP開発のストレスが嘘のように消え去ります。ぜひ、あなたの次のプロジェクトや既存のモノレポ環境に取り入れて、快適な開発ライフを満喫してください!