テックリードの皆さん、日々のCI/CDパイプラインの待ち時間にイライラしていないだろうか。「たかが依存関係のインストールに毎度2分も待たされる」「なぜかキャッシュが効かずにvendorディレクトリを一からダウンロードしている」。こうした小さなタイムロスは、チーム全体のフローを停滞させ、開発者の集中力を削ぐ最大の敵である。
今回は、GitHub ActionsとPHPのパッケージマネージャーであるComposerを極限まで最適化し、CIの実行時間を数秒単位まで削ぎ落とす実践的なアーキテクチャを解説する。単に動くだけのYAMLを書く時代は終わった。ツールの内部挙動をハックし、真の爆速CI/CD環境を構築しよう。
—
1. なぜ「普通の `composer install`」ではCIが遅くなるのか
GitHub Actionsのデフォルト環境で、毎回のビルド時に素直に `composer install` を実行すると、以下のボトルネックに直面する。
1. Packagist APIとの通信・バージョン解決のオーバーヘッド: ロックファイルが存在しても、パッケージのメタデータ解決にCPUとネットワークが消費される。
2. vendorディレクトリの肥大化: 数万ファイルに及ぶPHPのソースコードを毎回アーティファクトとして保存・復元しようとすると、I/Oの限界に達する。
3. 環境のミスマッチ: ローカルのPHPバージョンや拡張機能の差異による予期せぬエラー。
これを解決するのが、「Composerの内部キャッシュ機構の完全理解」と「GitHub Actionsのキャッシュアクション (`actions/cache`) の戦略的活用」である。
—
2. 現場の生産性を極限まで高める:最適化済みYAMLベストプラクティス
以下に、実務のプロダクション環境で即座に採用できる、洗練されたGitHub Actionsのワークフロー定義 (`.github/workflows/ci.yml`) を提示する。
name: Backend CI/CD Pipeline
on:
push:
# mainブランチへのマージ時、またはプルリクエスト作成時にのみ実行し、リソースを最適化する
branches: [ “main” ]
pull_request:
branches: [ “main” ]
jobs:
test:
name: PHP Unit Test & Static Analysis
runs-on: ubuntu-latest
# ジョブ全体のタイムアウトを明示的に設定し、無限ループやハングアップをデッドロック前に防ぐ
timeout-minutes: 10
steps:
# 1. リポジトリのチェックアウト(深さは最小限の1に抑え、クローンコストを削減)
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 1
# 2. プロジェクトで必要なPHPのバージョンを厳密に指定(ネイティブのプレインストール版を利用して高速化)
- name: Setup PHP Environment
uses: shivammathur/setup-php@v2
with:
php-version: ‘8.3’
# 開発に必要な拡張機能のみをピンポイントで有効化。不要なものは無効にしてロード時間を短縮
extensions: mbstring, intl, pdo_mysql, bcmath, zip
# カバレッジツール(Xdebug等)はテスト実行速度に直結するため、この段階では無効化(none)にするのがプロの選択
coverage: none
tools: composer:v2
# 3. Composerのキャッシュディレクトリのパスを動的に取得して環境変数に格納
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo “dir=$(composer config cache-files-dir)” >> $GITHUB_OUTPUT
# 4. GitHub Actions標準のキャッシュ機構の構築
# composer.lockのハッシュ値をキーにすることで、依存関係が変化した時だけキャッシュをパージする
- name: Cache Composer Dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles(‘/composer.lock’) }}
restore-keys: |
${- runner.os }}-composer-
# 5. 依存関係のインストール(高速化フラグを完全装備)
- name: Install Dependencies
run: |
# –no-interaction: 対話型プロンプトを抑制
# –no-ansi: カラー出力を無効化しログ解析を容易に
# –no-progress: プログレスバーの描画コスト(I/O)を排除
# –prefer-dist: 可能な限りZIPアーカイブから展開し、Gitクローンを避ける
# –optimize-autoloader: クラスマップを最適化し、プロダクション同等の実行パフォーマンスを担保
composer install –no-interaction –no-ansi –no-progress –prefer-dist –optimize-autoloader
# 6. 静的解析(PHPStanなど)の実行
- name: Run Static Analysis (PHPStan)
run: |
vendor/bin/phpstan analyse –memory-limit=1G
# 7. 単体テスト(PHPUnitなど)の実行
- name: Run Unit Tests
run: |
vendor/bin/phpunit –colors=always
—
3. アーキテクトが解説する:この設定が速い理由と内部挙動
① `composer config cache-files-dir` の動的取得
Composerは内部でダウンロードしたパッケージのzipファイルをローカルキャッシュ(通常 `~/.cache/composer/files` など)に保持する。これをハードコーディングせず、Composer自身にパスを問い合わせることで、ランナーのOSやバージョン変更に完全に対応できる堅牢性を手に入れている。
② `hashFiles(‘/composer.lock’)` によるスマートなキャッシュ管理
キャッシュのヒット率を最大化する鍵は「キーの設計」にある。`composer.lock` が1文字でも変わればキーが不一致となり新しいキャッシュが作成され、変わっていなければ前回のキャッシュが一瞬で復元される。これにより、毎回数千のパッケージをリモートから取得する無駄なネットワークコストが完全に消滅する。
③ 高速化フラグ(`–prefer-dist` & `–optimize-autoloader`)の真価
- `–prefer-dist`: ソースコードのGitリポジトリではなく、圧縮されたディストリビューションアーカイブ(zip)を優先的にダウンロード・展開する。ディスク容量を食わず、展開速度が圧倒的に早い。
- `–optimize-autoloader`: クラス名からファイルパスへのマッピングをあらかじめ構築し、PSR-4のオートローディングにおけるファイルシステムへの `file_exists` アクセスを激減させる。CI上でもこれを適用することで、テスト実行時のオーバーヘッドを極限まで削ることができる。
—
4. チーム開発で絶対に守るべき運用ルールとトラブルシューティング
最後に、このCI/CDフローをチーム全体で円滑に運用するための実務知見を共有する。
- `composer.lock` は必ずGit管理下に置く: CIの再現性を担保する絶対条件である。ローカルで `composer update` を行った際は、必ずロックファイルも一緒にコミットし、CIのキャッシュキーを正しく更新させること。
- ローカル開発環境との差異をなくす: 開発メンバー全員がComposer v2を使用し、PHPのバージョンをCIと一致させる(`.php-version` や `composer.json` の `config.platform.php` を活用する)ことで、「ローカルでは動くのにCIで落ちる」という悪夢を未然に防ぐことができる。
{
“config”: {
“platform”: {
“php”: “8.3.0”
}
}
}
上記のように `composer.json` にプラットフォームエミュレーションを設定しておくと、開発者のローカル環境がPHP 8.2であっても、CIと同じPHP 8.3向けの依存関係を確実に解決・固定化できる。
まとめ
CI/CDの最適化は、単なる「作業の自動化」ではない。開発者の「待ち時間」という名の生産性を奪うノイズをハックし、コードに集中できるフローをデザインすることこそが、優れたテックリードの仕事である。
今回紹介したYAMLとキャッシュ戦略をあなたのプロジェクトに導入し、驚異的なビルドスピードを手に入れてほしい。