SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

PHPカンファレンス実行委員プレゼンツ PHPの最前線

PHPパッケージ管理ツール「Composer」の処理の仕組みを見てみよう!

PHPカンファレンス実行委員プレゼンツ PHPの最前線 第4回

  • X ポスト
  • このエントリーをはてなブックマークに追加

パッケージ取得の流れ(2)

3. 間接依存の解決

 ここまでで、「利用可能なパッケージ」「パッケージが持っているバージョン」「バージョンごとに必要な要件(ランタイム情報、依存パッケージ) =requirerequire-devの情報」の入手方法が分かりました。

 composer requirecomposer updateで特定のパッケージを指定された場合は、まずそのパッケージの情報(メタ情報)を取得しにいきます。

 その中から、次の条件に合致するバージョンを選択します。

  1. 許容するバージョンの範囲を満たすもの
  2. minimum-stabilityprefer-stableprefer-lowest--patch-onlyといった、バージョンの選好戦略を満たすもの
  3. PHPのバージョンや拡張を満たすもの(Composerコマンドの実行環境、もしくはcomposer.jsonplatformコンフィグ情報から判断したもの)

 これでバージョンが決まったなら、一度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]

[5] ComposerのDependencyResolverの実装(GitHub)

 バージョンの解決から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と呼ばれるフェーズに入ります。

 次の処理が含まれます。

  1. composer.lockファイルを読み取る
  2. 必要なバージョンのメタ情報を取得する
  3. メタ情報のdist(設定によってはsource)フィールドの情報から、パッケージ本体を取得
  4. 書庫ファイルの場合は展開
  5. vendorディレクトリ配下に移動
  6. 書庫ファイルがあれば削除

 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ワークショップトラックで用意していますので、ご興味のある方はぜひ蒲田まで足をお運び下さい。

関連リンク

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
PHPカンファレンス実行委員プレゼンツ PHPの最前線連載記事一覧

もっと読む

この記事の著者

金城 秀樹(キンジョウ ヒデキ)

 ずっとPHPにお世話になり続けているWeb系プログラマー。趣味は積ん読とNBA観戦。最近は、本を読んだ感想を話すPodcast「readlinefm」もやっています。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/20455 2024/11/13 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング