【実務・中級編】Composerの「Vendorディレクトリ」を排除したデプロイ術:phar化とマージ機能で軽量パッケージを配布する方法 – ビルド・パッケージ管理ツール生産性向上バイブル

こんにちは。テックリードの私だ。

日々のPHP開発において、`composer install –no-dev` を実行し、数万ファイルにも及ぶ `vendor/` ディレクトリをFTPやrsync、あるいはCI/CDパイプライン経由で本番サーバーや各ノードへ転送していまいちフラストレーションを感じていないだろうか?

「たった数ファイルの修正なのに、ファイル数が多すぎて転送に数分かかる」
「パーミッションの不整合で、デプロイ直後に一部のクラスがオートロードできず500エラーを踏む」
「ファイルI/Oのオーバーヘッドが大きく、初回のリクエストでOPcacheのウォームアップに時間がかかる」

これらは、現代のモダンなPHPアプリケーション運用において完全にアンチパターンである。

今回は、Composerの依存関係を単一の実行可能ファイル(`.phar`)に凝縮し、`vendor/` ディレクトリそのものをデプロイメントから完全に排除する「phar化とゼロ・ベンダー・デプロイ戦略」を解説する。ツールの内部挙動から実務で即座に使える設定まで、私の知見をすべて共有しよう。

—

1. なぜ `vendor/` ディレクトリの転送は悪なのか?

PHPエコシステムにおける `vendor/` は、開発時の利便性のために「あえて」数千・数万の独立したファイルとしてディスク上に展開されている。だが、これをそのまま本番環境に持ち込むことには、アーキテクチャ上の致命的な欠陥がある。

1. inodeの枯渇とファイル転送の非効率
小さなファイルが無数に存在すると、ファイルシステムのinodeを不必要に消費し、ネットワーク転送(SCP/SFTP/rsync)のオーバーヘッドが劇的に増大する。
2. デプロイの不可分性(Atomicity)の欠如
転送途中のファイルを読み込まれたり、一部のファイルが欠損した状態でプロセスが起動すると、致命的な致命傷(Fatal Error)につながる。単一ファイルであれば、アトミックな置き換え(`rename` システムコール)が容易になる。

これを解決するのが、PHPのアーカイブフォーマットである PHAR (PHP Archive) だ。すべての依存関係、設定、ソースコードを1つのバイナリ(あるいはライブラリパッケージ)にコンパイルし、単一の `.phar` として配布・実行する。

—

2. 必須ツール:`box` による Phar ビルドの自動化

Pharを構築するための事実上のデファクトスタンダードが humbug/box だ。Composerで管理されたプロジェクトを、堅牢かつ高速なPharファイルへとコンパイルする。

導入と神プラグイン・設定

開発体験を最大化するため、プロジェクトのルートに `box.json` を配置する。まずは、実務でそのまま使える最高峰の設定ファイルを見てほしい。

{
// Pharファイルの出力先とファイル名
“output”: “dist/application.phar”,

// スタブ(Pharのエントリポイントとなる起動スクリプト)の指定
“stub”: “bin/run.php”,

// コンパイル対象から除外するファイル・ディレクトリ
“exclude”: [
“tests”,
“var”,
“docs”,
“.git”
],

// 圧縮アルゴリズム(GZ または BZ2。速度とサイズのバランスでGZを推奨)
“compression”: “GZ”,

// セキュリティのための署名アルゴリズム
“sign-algo”: “SHA256”,

// バイナリに含めるファイルのホワイトリスト/ブラックリスト設定
“files”: [
“config/production.php”
],

// 依存関係を強制的にバイナリ内に巻き込むための設定
“finder”: [
{
“name”: “.php”,
“exclude”: [“test”, “tests”, “Test”, “Tests”],
“in”: “vendor”
}
]
}

スタブ(`bin/run.php`)の実装

Pharが実行されたときに最初に呼ばれるエントリーポイントだ。ここで `Phar::mapPhar()` を呼び出し、内部のオートローダーを正しく駆動させる必要がある。

  • Application Phar Stub
  • 外部から呼び出された際のエントリポイント
  • /

    // Phar内実行であることをマッピング
    Phar::mapPhar(‘application.phar’);

    // Phar内部のComposerオートローダーを読み込む
    require_once ‘phar://application.phar/vendor/autoload.php’;

    use App\Core\Application;

    try {
    // アプリケーションの起動
    $app = new Application();
    $app->run();
    } catch (\Throwable $e) {
    fwrite(STDERR, “Fatal Error: ” . $e->getMessage() . PHP_EOL);
    exit(1);
    }

    __HALT_COMPILER();

    —

    3. Composerオートローダーの極限最適化

    `vendor/` を排除して単一Pharファイルにする場合、ファイルI/Oの性質が変わるため、Composerのオートローダー設定を極限までチューニングしなければパフォーマンスが劣化する。

    `composer.json` の `config` セクションに以下の設定を必ず追加せよ。

    {
    “config”: {
    // 最適化されたクラスマップを生成(classmap-authoritative)
    // APCuなどのキャッシュがなくても、ファイルシステムへの無駄な存在確認(stat)を完全に排除する
    “classmap-authoritative”: true,

    // 厳格なPSR-4準拠を強制し、オートローダーの検索アルゴリズムをO(1)に近づける
    “optimize-autoloader”: true
    }
    }

    なぜ `classmap-authoritative` が神なのか?

    通常、Composerのオートローダーは、クラスが見つからない場合にPSR-4の規約に従ってファイルシステムを探索(`file_exists` や `include`)しようとする。しかし、Pharアーカイブ内部でのファイルシステム探索は通常のOS上のディスクI/Oよりも重い。
    `classmap-authoritative` を有効にすると、依存関係にあるすべてのクラスとファイルの対応関係を完全に事前コンパイル(Classmap化)し、ファイルシステムへの問い合わせを一切行わずにメモリ上で解決するようになる。

    —

    4. チーム開発におけるビルドの自動化と共有ルール

    属人性を排除し、誰が実行しても同じPharバイナリが生成されるよう、Makefile または Taskfile を用いてビルドプロセスをコード化する。

    実用的な `Makefile` のベストプラクティス

    .PHONY: build clean test

    デフォルトターゲット
    all: clean test build

    キャッシュや古いビルド成果物のクリーンアップ
    clean:
    @echo “==> Cleaning old builds…”
    rm -rf dist/

    テストの実行(ビルド前に必ず品質を担保)
    test:
    @echo “==> Running test suite…”
    vendor/bin/phpunit

    プロダクション用依存関係のみインストール
    vendor-prod:
    @echo “==> Installing production dependencies…”
    composer install –no-dev –optimize-autoloader –classmap-authoritative –no-interaction

    Boxを用いたPharのビルド実行
    build: vendor-prod
    @echo “==> Building Phar package…”
    vendor/bin/box compile -v
    @echo “==> Build complete: dist/application.phar”

    チームメンバーは、デプロイ用パッケージを作成する際に以下のコマンドを叩くだけで済む。

    make build

    これにより、CI/CDパイプライン(GitHub ActionsやGitLab CIなど)でも `make build` を呼ぶだけで、一貫性のあるクリーンなPharパッケージが生成され、成果物(Artifact)として数メガバイトの単一ファイルをクラウドストレージやサーバーへ転送するだけでデプロイが完了する。

    —

    5. テックリードからの実務上の注意点(罠と回避策)

    Phar化およびゼロ・ベンダー・デプロイには、PHPの仕様に起因するいくつかの「罠」が存在する。ここを知っているかどうかが、プロとアマの分かれ目だ。

    1. `__FILE__` や `__DIR__` の挙動変化
    Phar内部から実行されるスクリプトでは、`__FILE__` は `phar:///path/to/app.phar/src/Foo.php` のような形式になる。もしアプリケーション内で相対パスを使って設定ファイルやテンプレートを読み込んでいる場合、これらはそのままでは動作しない。
    対策として、パス解決には常に `Phar::running(false)` をベースにしたベースパス取得関数を用意すること。

    $BasePath = Phar::running(false) ?: __DIR__;

    2. 書き込み可能なストレージの分離
    Pharファイル自体は読み取り専用(Read-only)である。ログファイル、アップロードされた画像、キャッシュ(`var/cache` や `var/log`)などをPhar内部に書き込もうとすると、例外(`PharException`)が発生してクラッシュする。
    したがって、書き込みが必要なディレクトリは、Pharの外部(ホスト側のストレージ)にマウントするか、起動時に外部の適切なディレクトリを指すようにシンボリックリンクや設定で明示的に分離しなければならない。

    3. OPcacheの設定
    Pharファイルを本番稼働させる際は、`php.ini` の `opcache.enable_cli=1` と共に、以下の設定を入れると飛躍的に速度が向上する。

    [opcache]
    opcache.enable = 1
    opcache.memory_consumption = 256
    opcache.phar_readonly = 1

    —

    結びにかえて

    `vendor/` ディレクトリをそのままデプロイする時代は終わった。
    Boxを用いたPhar化と、Composerの `classmap-authoritative` による極限の最適化を組み合わせることで、あなたのプロジェクトのデプロイ速度は劇的に向上し、ファイル転送のトラブルや環境差異によるバグは過去のものとなるだろう。

    今日の夕方、まずは手元のプロジェクトに `box.json` を置き、その軽さと圧倒的なスマートさを体感してほしい。チームの開発生産性は、こうした細部へのこだわりから確実に底上げされていく。

    タイトルとURLをコピーしました