パッケージ取得の流れ
パッケージ取得の流れを簡略化して示すと、以下のような手順になります。
- 目的のパッケージの存在の確認
- 提供されているバージョンの確認
- 間接依存の解決
- パッケージ本体の取得・展開
これを基本としつつ、現実的にはより複雑な処理が発生します。大規模なリポジトリでは、効率的にパッケージとバージョンを特定するための工夫も必要です。
典型的な例として、Packagistを利用した際のシナリオについて見ていきましょう。
1. 目的のパッケージの存在の確認
目的のパッケージが存在するかを確かめるには、まずは「リポジトリ自体の情報を取得する」ところから始まります。Packagistの場合はhttps://packagist.org/packages.json
です。特殊な方法を用いずにブラウザ等でも閲覧可能なので、試しに開いてみてください[2]。
[2] 「composer」タイプのリポジトリではpackages.json
を参照する必要があるというのは、Composerのソースコードでは ComposerRepositoryクラスで示されています。
packages.json
によって、そのリポジトリに存在するパッケージ情報にアクセスが可能になるわけです。その実装を考えると、最も素朴な方法は「全てのパッケージとそのバージョンを列挙した完全なリスト提供する」というものでしょう。Composerの公式ドキュメントでは、次の例が紹介されています。
{ "packages": { "vendor/package-name": { "dev-master": { @composer.json }, "1.0.x-dev": { @composer.json }, "0.0.1": { @composer.json }, "1.0.0": { @composer.json } } } }
しかしながら、「完全なリスト」をいちいち取得して利用するには、Packagist上で登録されているパッケージの数が膨大すぎます[3]。 また、レポジトリ上にある利用可能なパッケージの大半は、自身のプロジェクトとは関係のない存在です。
そこで、metadata-url
という仕組みが提供されています。パッケージの全数を取得しなくても、目的のパッケージに限定して利用可否を確かめられるようにするものです。
Packagistのpackages.json
には、"metadata-url": "/p2/%package%.json"
と記述されています。%package%
の部分を目的のパッケージ名に置換して、GETリクエストを送信します。存在すれば利用可能なバージョンごとのcomposer.json
のリストが、存在しなければ404レスポンスが返ってくる仕様です。これによって、目的外のパッケージについては一切関与すること無く、効率的にパッケージ情報を取得できます。
[3] 2024年11月1日現在で、約41万件のパッケージが登録されています。Packagistの利用統計は https://packagist.org/statistics で公開されています。
ここまでの流れを、実際のComposerのrequire
コマンドを実行して確認してみましょう。
-vvv
オプションを付けることで、コマンド内部で行われているHTTPリクエストの情報などを細かく確認できます。
composer require --no-cache psr/log -vvv
出力されたログを示します。本稿の目的であるパッケージ情報の取得に関わるもの以外は割愛してあります。
/app# composer require --no-cache psr/log -vvv (中略) Downloading https://repo.packagist.org/packages.json [200] https://repo.packagist.org/packages.json Downloading https://repo.packagist.org/p2/psr/log.json [200] https://repo.packagist.org/p2/psr/log.json
まずpackages.json
を取得する、次に/p2/%package%.json
(今回の例ではpsr/log
)を取得しにいっていることが分かります。
存在しないパッケージ名を指定した場合はどうなるでしょう?
/app# composer require --no-cache psr/_cache -vvv (中略) Downloading https://repo.packagist.org/packages.json [200] https://repo.packagist.org/packages.json Downloading https://repo.packagist.org/p2/psr/_cache.json [404] https://repo.packagist.org/p2/psr/_cache.json Downloading https://packagist.org/providers/psr/_cache.json [200] https://packagist.org/providers/psr/_cache.json Downloading https://repo.packagist.org/p2/psr/_cache~dev.json [404] https://repo.packagist.org/p2/psr/_cache~dev.json Downloading https://packagist.org/search.json?q=psr%2F_cache&type= [200] https://packagist.org/search.json?q=psr%2F_cache&type= Could not find package psr/_cache. Pick one of these or leave empty to abort: [0] psr/cache [1] psr/cache-util [2] psr/simple-cache [3] epwt/psr-cache [4] viloveul/cache >
metadata-url
での解決には404応答が返ってきています。その後にproviders-url
という、packages.json
によって情報が提供されている仕組みによる解決を試行します。更に、search
を利用して、名前が似ているパッケージの情報を提供してくれています。とても便利ですね。
以上が、パッケージの存在の確認までの流れになります。
2. 提供されているバージョンの確認
metadata-url
で取得されたのは、JSONファイルです。「Composer」タイプのリポジトリの情報を提供しています。必須項目はpackages
というリストの記述です。すなわち、理屈としては先述の「全てのパッケージとそのバージョンを列挙した完全なリスト提供する」ための構造を利用している訳です。
https://repo.packagist.org/p2/guzzlehttp/psr7.json
の中身は、次のようになっています。なお、説明のために改変し、大幅に項目を削っています。
{ "minified": "composer/2.0", "packages": { "guzzlehttp/psr7": [ { "name": "guzzlehttp/psr7", "version": "2.7.0", "version_normalized": "2.7.0.0", "license": [/** ..省略.. */] "source": {/** ..省略.. */}, "dist": {/** ..省略.. */} "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.1 || ^2.0", "ralouphie/getallheaders": "^3.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "provide": { "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" } }, { "version": "2.6.3", "version_normalized": "2.6.3.0", "source": {/** ..省略.. */}, "dist": {/** ..省略.. */} }, { "version": "2.6.2", "version_normalized": "2.6.2.0", "source": {/** ..省略.. */}, "dist": {/** ..省略.. */} "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "^0.9", "phpunit/phpunit": "^8.5.36 || ^9.6.15" } }, /** 以降、様々なバージョンが続く */ ] }, "security-advisories": [/** ..省略.. */] }
ベースとなるのは、元々のプロジェクトに含まれるcomposer.json
です。各バージョンごとの情報が記載されます。これによって、「このバージョンを使うには、これらの依存が要求される」と判断できるようになっているのです。
一方で、依存パッケージを示すrequire
、require-dev
フィールドが、バージョンによっては存在していないことに気づいたでしょうか? これらのフィールドは、紙面の都合で割愛したわけではなく、実際に項目が削除されています。
これはデータ量を少しでも小さくするための工夫です。1つ前に記述されたバージョンと比較して、差分がない場合は省略されています。
具体的に見ていくと、
-
2.7.0
はrequire
、require-dev
を持つ -
2.6.3
は持たない -
2.6.2
はrequire-dev
を持つ
ようになっています。
これは、
-
2.7.0
は先頭のバージョンなので、完全な情報が記述されている -
2.7.0
←2.6.3
の間では変更が無かった -
2.6.3
←2.6.2
の間では、require-dev
の変更があった
ことを意味しています。
実際にguzzlehttp/psr7
のソースコードを取得して、tagごとのcomposer.json
のdiffを取ってみた結果は、次の通りです。
$ git --no-pager diff 2.6.3...2.7.0 composer.json |wc -l 0 $ git --no-pager diff 2.6.2...2.6.3 composer.json |wc -l 15 $ git --no-pager diff 2.6.2...2.6.3 composer.json diff --git a/composer.json b/composer.json index 70293fc..28d15f5 100644 --- a/composer.json +++ b/composer.json @@ -61,8 +61,8 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
詳しい仕組みは、composer/metadata-minifier
を確認してください。\Composer\MetadataMinifier\MetadataMinifier::minify
が重複情報を削る仕事の実態を担っており、\Composer\MetadataMinifier\MetadataMinifier::expand
が復元を担っています[4]。
[4] composer/metadata-minifierについては、バージョン1.0.0時点での実装をもとに解説しています。GitHub上のURLは https://github.com/composer/metadata-minifier/tree/1.0.0 です。
こうして、目的とするパッケージに「バージョンは何が存在しているか」「特定のバージョンについての情報をどう入手するか」の問題が解明されました。