【テクニカル・上級編】依存関係のクリーンアップ:`composer-unused`を使って未使用のパッケージを特定・削除する実践フロー – ビルド・パッケージ管理ツール生産性向上バイブル

依存関係の地獄からコードベースを解放せよ:`composer-unused`で実現する極限のデットコード・エクソシズム

長く運用されているPHPプロダクトのコードベースを覗いてみてほしい。`composer.json`の`require`セクションに並ぶ、かつて「何となく便利そうだから」「一時的な検証のために」導入された無数のサードパーティパッケージ群。それらは本当に現在もプロダクトの血肉となっているだろうか。

多くの現場では、パッケージの追加は容易であっても、「削除」は恐怖を伴う作業として放置される。結果として、使われていないライブラリが autoload のメモリを圧迫し、サプライチェーン攻撃の潜在的リスクを広げ、CI/CDのビルド時間を無駄に引き延ばしている。

本稿では、PHPエコシステムにおける依存関係管理のパラダイムを一段引き上げるため、`composer-unused`を用いたデットパッケージの自動検出、そしてそれをCI/CDパイプラインに組み込み「二度とゴミを置かせない」完全自動防衛体制の構築ハンズオンを、アーキテクトの視点から徹底解説する。

—

1. なぜ `composer-unused` なのか?(内部アーキテクチャの理解)

世の中には静的解析ツールや依存関係チェッカーが溢れているが、単に `composer.json` とソースコードの文字列マッチングを行うだけのツールは、大規模なエンタープライズ環境では使い物にならない。誤検知の嵐か、あるいはエイリアスや動的解決を見落としてクラッシュするかだ。

`composer-unused` の内部メカニズムは、以下の強固なパイプラインで構成されている。

1. Composer Runtime APIの完全活用: `composer.json` およびロックファイル(`composer.lock`)をパースし、定義されているパッケージ名と、それらが提供するautoloadのネームスペース(PSR-4 / PSR-0 / Classmap)のマッピングを正確に構築する。
2. AST(抽象構文木)ベースのコード走査: 文字列検索ではなく、nikic/php-parserをエンジンとして用いたAST解析により、ソースコード内の `use` 文、完全修飾名(FQN)、さらにはアノテーションや文字列としてのクラス名参照までを追跡する。
3. Composerプラグインやフレームワークの暗黙的依存の考慮: LaravelのサービスプロバイダやSymfonyのバンドルなど、コード側から直接クラス名としてコールされなくとも、設定ファイル経由でインスタンス化されるパッケージの特性をハンドリングする拡張性を持つ。

つまり、`composer-unused` は「Composerが知る世界と、PHPの実行コンテキスト(AST)の世界を突き合わせ、交点を持たない孤立したノードを暴き出す」ための精密機械なのだ。

—

2. 導入とローカル環境での極限チューニング

まずは、プロジェクトのルートにおいて `composer-unused` をセットアップする。グローバルインストールは環境の再現性を損なうため、プロジェクトの dev 依存として組み込むか、独立した実行環境を用意する。

開発環境(dev)としてプロジェクトに厳密にバージョンを固定して導入
composer require –dev icanhazdep/composer-unused

厳密な設定ファイル `composer-unused.php` の作成

プロジェクトのルートディレクトリに `composer-unused.php` を配置する。この設定ファイルこそが、誤検知を防ぎ、プロジェクト固有のアーキテクチャ特性をツールに教え込むための核心部分である。

addSourceDir(Path::canonicalize(__DIR__ . ‘/app’))
->addSourceDir(Path::canonicalize(__DIR__ . ‘/database’))

// テストコード側でしか使わないが、本番コードからは参照されないライブラリを明示的に除外
// (例: PHPUnitやPestなど、composer.lockのrequire-devに含まれるものを除外対象に追加)
->addExclude(‘phpunit/phpunit’)
->addExclude(‘pestphp/pest’)

// フレームワークのDIコンテナや設定ファイル経由で暗黙的にロードされるため、
// AST解析に引っかからないが実際には必須のパッケージを「自己責任でスルー(IgnoredPackages)」に指定
->addNamedPackage(
NamedPackage::fromBasePackage(
new \ComposerUnused\SymbolParser\Scope\Namespace\NamespaceSymbol(‘Illuminate\\Database’)
)->withName(‘illuminate/database’)
);
};

> Architect’s Note:
> フレームワーク(LaravelやSymfonyなど)を使用している場合、コンフィグファイル(`config/.php`)やサービスプロバイダ内で文字列としてクラス名が指定されているケースがある。これらはASTから直接検出できない場合があるため、`composer-unused.php` の中で適切にプロキシ設定を行うか、フレームワーク特有のエクステンションを組み合わせる必要がある。

—

3. 実践:デットパッケージの検出と実行ログ

設定が完了したら、CLIから解析を実行する。

vendor/bin/composer-unused

もしプロジェクト内に未使用のパッケージが存在する場合、以下のような圧倒的な緊迫感を持つログが出力される。

[OK] scanning files…
[OK] collecting usages…
[OK] comparing dependencies…

Unused packages detected:

  • guzzlehttp/guzzle (Used in: none)
  • nesbot/carbon (Used in: none)

Found 2 unused packages.

この瞬間、開発者は「なぜこのパッケージが入り込んだのか」「いつ使わなくなったのか」をコードの歴史(Git blame)から逆引きし、迷わずアンインストールコマンドを叩くことができる。

composer remove guzzlehttp/guzzle nesbot/carbon

この数秒の作業が、バイナリサイズを削り、依存関係の解決にかかるComposerのCPU時間を短縮し、サプライチェーン上の脆弱性パッチをあてるべき対象を極小化する。

—

4. CI/CDパイプラインへの完全統合(GitHub Actions)

手動でこのチェックを行うよう開発者に委ねるのは、DevOpsの観点から見て「悪」である。人間は忘れるし、サぼる。だからこそ、CIのゲートキーパーとして `composer-unused` を常駐させ、未使用パッケージが含まれるプルリクエストはマージすらできない状態を強制する。

以下に、最高効率で最適化された GitHub Actions のワークフロー定義を示す。

name: “Dependency Cleanliness Check”

on:
pull_request:
branches:

  • main
  • develop

push:
branches:

  • main

jobs:
composer-unused:
name: “Check Unused Composer Dependencies”
runs-on: ubuntu-latest

steps:
# 1. リポジトリのソースコードを高速チェックアウト

  • name: Checkout repository

uses: actions/checkout@v4

# 2. PHP環境のセットアップ(必要な拡張機能を網羅しつつキャッシュを活用)

  • name: Setup PHP

uses: shivammathur/setup-php@v2
with:
php-version: ‘8.3’
extensions: mbstring, intl, pdo, sqlite3
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. 依存関係のキャッシュ復元(ビルド時間の極限短縮)

  • name: Cache Composer dependencies

uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${

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