パッケージ取得の流れ(2)
3. 間接依存の解決
ここまでで、「利用可能なパッケージ」「パッケージが持っているバージョン」「バージョンごとに必要な要件(ランタイム情報、依存パッケージ) =require
やrequire-dev
の情報」の入手方法が分かりました。
composer require
、composer update
で特定のパッケージを指定された場合は、まずそのパッケージの情報(メタ情報)を取得しにいきます。
その中から、次の条件に合致するバージョンを選択します。
- 許容するバージョンの範囲を満たすもの
-
minimum-stability
、prefer-stable
、prefer-lowest
、--patch-only
といった、バージョンの選好戦略を満たすもの -
PHPのバージョンや拡張を満たすもの(Composerコマンドの実行環境、もしくは
composer.json
のplatform
コンフィグ情報から判断したもの)
これでバージョンが決まったなら、一度composer.json
が作成されます。
そして、ここからの処理は実質的にcomposer update
と同様になります。すなわち、composer.json
をもとにして、間接依存を解決し、composer.lock
の作成まで行う処理です。
実際にコマンドを実行してみると、次のような出力が確認できます。
/app# composer require --no-cache -vvv guzzlehttp/psr7 psr/container Disabling cache usage (中略) Downloading https://repo.packagist.org/p2/guzzlehttp/psr7.json Downloading https://repo.packagist.org/p2/guzzlehttp/psr7~dev.json [200] https://repo.packagist.org/p2/guzzlehttp/psr7.json [200] https://repo.packagist.org/p2/guzzlehttp/psr7~dev.json Downloading https://repo.packagist.org/p2/psr/container.json Downloading https://repo.packagist.org/p2/psr/container~dev.json [200] https://repo.packagist.org/p2/psr/container.json [200] https://repo.packagist.org/p2/psr/container~dev.json ./composer.json has been created Reading ./composer.json (/app/composer.json) Loading config file ./composer.json (/app/composer.json) (中略) Running composer update guzzlehttp/psr7 psr/container Loading composer repositories with package information (以降、update処理)
composer.json
で指定されているパッケージ・バージョンのメタ情報から、依存関係に応じたパッケージを再帰的に取得しにいきます。
具体的に、どのようにパッケージ間の整合性をとり完璧な組み合わせを決定しているのでしょう?
これについては、あまりにも複雑になるため、本稿では解説を割愛します。興味のある方は、 ComposerのソースにあるDependencyResolver
名前空間配下にある、一連のクラスの実装を覗いてみてください[5]。
バージョンの解決からcomposer.lock
ファイルを作成する箇所では、次のような情報が出力されます。
/app# composer require --no-cache -vvv guzzlehttp/psr7 psr/container Disabling cache usage (中略) Built pool. Running pool optimizer. Pool optimizer completed in 0.002 seconds Found 126 package versions referenced in your dependency graph. 50 (40%) were optimized away. Updating dependencies Generating rules Resolving dependencies through SAT Looking at all rules. Something's changed, looking at all rules again (pass #1) Dependency resolution completed in 0.000 seconds Analyzed 76 packages to resolve dependencies Analyzed 97 rules to resolve dependencies Lock file operations: 5 installs, 0 updates, 0 removals Installs: ralouphie/getallheaders:3.0.3, psr/http-message:2.0, psr/http-factory:1.1.0, guzzlehttp/psr7:2.7.0, psr/container:2.0.2 - Locking guzzlehttp/psr7 (2.7.0) - Locking psr/container (2.0.2) - Locking psr/http-factory (1.1.0) - Locking psr/http-message (2.0) - Locking ralouphie/getallheaders (3.0.3) Writing lock file (以降、install処理)
4. パッケージ本体の取得・展開
間接依存まで含めて、詳細なバージョン情報までを確定できたら、内部的にはInstall
と呼ばれるフェーズに入ります。
次の処理が含まれます。
-
composer.lock
ファイルを読み取る - 必要なバージョンのメタ情報を取得する
-
メタ情報の
dist
(設定によってはsource
)フィールドの情報から、パッケージ本体を取得 - 書庫ファイルの場合は展開
-
vendor
ディレクトリ配下に移動 - 書庫ファイルがあれば削除
metadata-url
から取得したファイルの中に、「どこからパッケージ本体を取得すればいいか」の情報が埋め込まれいるので、これを利用するのです(上記の処理3)。
{ "minified": "composer/2.0", "packages": { "guzzlehttp/psr7": [ { "name": "guzzlehttp/psr7", "version": "2.7.0", "version_normalized": "2.7.0.0", "license": [/** ..省略.. */] "source": { "url": "https://github.com/guzzle/psr7.git", "type": "git", "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "dist": { "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "type": "zip", "shasum": "", "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "require": {/** ..省略.. */} "require-dev": {/** ..省略.. */} "provide": {/** ..省略.. */} }, /** 以降、様々なバージョンが続く */ ] }, "security-advisories": [/** ..省略.. */] }
api.github.com
からのファイル取得に際しては、Composerの設定ファイルにgithub-oauth
があれば、それを利用することになります。デフォルトでは、$HOME/.composer/auth.json
に保存されています。
このフェーズはそれほど複雑なことをしていないと言えるでしょう。
実行時ログを確認してみましょう。
/app# composer require --no-cache -vvv monolog/monolog Disabling cache usage (中略) Writing lock file Installing dependencies from lock file (including require-dev) Reading ./composer.lock (/app/composer.lock) Package operations: 2 installs, 0 updates, 0 removals Installs: psr/log:3.0.2, monolog/monolog:3.7.0 - Downloading psr/log (3.0.2) Downloading https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3 - Downloading monolog/monolog (3.7.0) Downloading https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8 [302] https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3 Following redirect (1) https://codeload.github.com/php-fig/log/legacy.zip/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3 [302] https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8 Following redirect (1) https://codeload.github.com/Seldaek/monolog/legacy.zip/f4393b648b78a5408747de94fca38beb5f7e9ef8 [200] https://codeload.github.com/php-fig/log/legacy.zip/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3 [200] https://codeload.github.com/Seldaek/monolog/legacy.zip/f4393b648b78a5408747de94fca38beb5f7e9ef8 - Installing psr/log (3.0.2): Extracting archive Executing async command (CWD): '/usr/bin/unzip' -qq '/app/vendor/composer/tmp-9959c48173795484bb8769bd74dc8877.zip' -d '/app/vendor/composer/33d54313' - Installing monolog/monolog (3.7.0): Extracting archive Executing async command (CWD): '/usr/bin/unzip' -qq '/app/vendor/composer/tmp-b3b428fedcc06750e89b1050456991ec.zip' -d '/app/vendor/composer/64acc429' Executing async command (CWD): rm -rf '/app/vendor/composer/33d54313' Executing async command (CWD): rm -rf '/app/vendor/composer/64acc429' (以下省略。dump-autoloadなど)
Downloading パッケージ名 (バージョン)
という出力と、Executing async command (CWD): '/usr/bin/unzip' -qq ・・・
という出力がされています。これがまさに、取得した書庫ファイルを展開している様子になります。
また、UIへの表示はありませんが、展開されたディレクトリは\Composer\Util\Filesystem::rename
によってvendor
ディレクトリへと移動されています。
そしてExecuting async command (CWD): rm -rf ・・・
によって、書庫ファイルを削除して、無事にパッケージのインストールは完了です!
おわりに
いつも何気なく「コマンド1つでパッケージを追加しよう!」と利用しているComposerを、解剖してみました。
深層部に潜り込んで、その仕事を分解的に見ていくと、最後に残るのはごくごく単純な処理です。「JSONに載っているURLからファイルを取得する」「ループを回しながらデータを組み立てる」なんていうのは、PHPプログラミングの初学者でも扱える処理でしょう。一方で、unzipやGit操作など、外部コマンドに頼る部分はproc_openを利用して処理しています。
こうしてみると、「こんなに素晴らしいComposerというツールでも、普段のプログラミングと地続きに存在しているものなんだ」と感じられるのではないでしょうか?
ぜひ、身近なオープンソースを題材に、内側から仕組みを理解する楽しさに触れてみてください!
PHPカンファレンス2024 実行委員より
2024年12月22日(日)に開催される日本のPHPコミュニティの最大のお祭り「PHPカンファレンス2024」タイアップ連載も4回目となりました。スポンサー、発表者や発表タイトルも公開され、来月の開催に向けて運営側もいよいよ佳境に入ってきた感じです。セッションのタイムテーブルも先日公開されたので、その中から基調講演について、軽くご紹介させて下さい。
今回の基調講演は2つありまして、一つは毎年恒例となっている廣川類さんの「PHPの今とこれから2024」です。PHPをとりまく現状と直近の将来について、わかりやすく話していただく評判のセッションです。もう一つはSaki Takamachiさんの「PHP RMは何をする?コア開発者と兼任するメリット/裏話」です。日本人として初めてPHP 8.4のリリースマネージャを努めているTakamachiさんに、リリースマネージャの役割やその裏話を語っていただく貴重な内容です。 他にも面白い内容のセッションを、6トラック+1ワークショップトラックで用意していますので、ご興味のある方はぜひ蒲田まで足をお運び下さい。