この記事を書くにあたって、これまで何度か読んだことのあったComposerのソースを再度読み直して理解しようとしてみました。ですが、Composerのソースってなんだか難しくて、分かった!という感触はまだまだつかめていません。Composerのソースがひどくスパゲティだ!なんてことはないのですが。

おそらくこれはソースの問題というよりも、私自身の、取り扱っている問題そのものに対する理解が浅いからなのだろうと、現時点では結論づけています。ひとまずComposerに登場する最も主要な構成要素を、図にまとめておきます。

Composerの構成要素

ところで、前回の記事では、Composerがvendorディレクトリ配下のパッケージ内の状態をどのように管理しているのかを簡単に解説しました。

今回は、複数のパッケージを同時に開発する場合の流れを例にして解説します。

注:複数のパッケージを開発するときに、必ずこの流れでないといけないということではありません。composerの状態などを順に見ていくための例です。

前提:パッケージは可能な限りstableリリースを利用する

プロジェクトを何らかの状態でリリースする時には、少なくとも依存パッケージ側はバージョンを切ってstableリリースし、そのバージョンをプロジェクトのルートパッケージのcomposer.jsonで利用するようにします。ルートパッケージのcomposer.jsonでは、minimum-stabilityフラグは基本的に利用せず、stableリリースされたパッケージだけがインストールされる状態にしておきます。

このようにしておくと、依存パッケージに機能追加を行う場合などでも、stableリリースしないかぎり勝手に更新がインストールされることはなくなるため、安心して依存パッケージを更新していけます。(composer.lockファイルへの依存度を下げていけます)

最初:2つのパッケージを初期化

  • ルートパッケージ:Quartet.SampleApp
  • 依存パッケージ:Quartet.SampleLibrary

2つのパッケージは、それぞれ独立したGitHubのリポジトリを持つようにします。Composerのリポジトリとしてpackagistを利用します。(Git/GitHubのリポジトリ作成手順、packagistへの登録手順はこの記事では割愛します。)

この状態で別々の2つのプロジェクトとして開発していき、Quartet.SampleApp側で随時composer updateするか、またはvendorディレクトリ内にシンボリックリンクを作ったりする対応方法もあります。2つのパッケージの独立性が高く(※そもそも、独立性の高いものを分割するのですが)メンテナンスのタイミングなどがばらばらであれば、別々のプロジェクトのままでも特に困ることはありません。

しかし、1つのプロジェクトで使うパッケージを複数同時に開発する場合もあります。この時は、1つのプロジェクト内で開発してしまう方がロスも少なく、スムーズです。

composer.jsonを修正

依存パッケージ側

依存パッケージ側は、1つの開発のゴールが達成された時点で1.0.0としてリリースします。そこで、現在の開発ブランチがどのバージョンの系統なのかを、composer.jsonbranch-alias で明示しておきます。

{ 
   ...
   "extra": {
        "branch-alias": {
            "dev-master": "1.0-dev"
        }
    }
}
  • php/skeleton を使ってパッケージを初期化している場合は、デフォルトでbranch-aliasdev-developになっているので注意が必要です。
  • minimum-stabilityは、どうしても継続的にdevバージョンのパッケージを全体として使う、という場合を除いて、削除しておくことをおすすめします。

この状態でGitHubへpushし、packagistへの登録もできていれば、Composerを使ってこのパッケージのdev-masterブランチを取得できるようになります。

確実にGitHubへpushできたことを確認したら、この後はルートパッケージ側のプロジェクトの中で依存パッケージも開発しますので、ローカルにある最初に作った依存パッケージ用のプロジェクトは、削除しておきます。

ルートパッケージ側

ルートパッケージ側では、まず依存パッケージを利用するようにComposerコマンドで登録します。この段階ではまだ依存パッケージ側はリリースされていないので、このパッケージに対しては開発ブランチを利用するよう指定します。これで、VCS情報を保持したまま(Gitリポジトリの状態で)パッケージがインストールされます。

$ php composer.phar require "quartet/sample-library:@dev"

TIPS:すでにstable版でパッケージをインストールしている場合

依存パッケージのstable版がインストール済みの場合、--prefer-sourceオプションをつけてupdateすればVCS情報のある状態へ更新してくれそうに思えるのですが、そのような動きにはなっていないようです。この場合、一旦 vendor/quartet/sample-library ディレクトリを削除してから、php composer.phar update quartet/sample-library --prefer-sourceとすれば、VCS情報付きでインストールされた状態になります。

この方法は、composer.jsonの記述としてはstable指定のまま、vendorディレクトリにVCS情報付きのリポジトリを取得したい場合に便利です。

ただし、コマンド実行時点ではstable版の最新のタグのコミットがcheckout(detached head)された状態になります。ですので、--prefer-sourceオプションをつけずにインストールした場合と同じソースコードの状態になっています。ここから機能修正を行う場合は、ブランチをどこから始めるのかということに注意してください。

開発を進める

ルートパッケージ側は、普通にアプリケーションを開発するときと同じですね。 そして、今回は依存パッケージの機能もまだまだできあがっていないので、このプロジェクト内で同時にコードを追加・修正していきます。つまり、vendor/quartet/sample-libraryディレクトリ配下のコードも、どんどん修正していくということです。

依存パッケージ側(vendor内)の注意点と開発方法

1つ注意する必要があるのは、「依存パッケージ側は、依存パッケージの中で閉じているようにする」ことです(依存パッケージ側からルートパッケージの機能を要求しているようであれば、パッケージの再設計を検討した方がよさそうです)。

依存パッケージのコードの追加・修正は、そのままルートパッケージから利用できるのは当然なので、依存パッケージのコードを書きつつ、動作の確認はルートパッケージ側で行う、ということもできます。 ただし、依存パッケージ側のユニットテストなどは、依存パッケージの中だけで完結させたいですね。この場合、vendor/quartet/sample-libraryディレクトリでcomposer installすれば、その配下に(指定してあれば)PHPUnitなどテストに必要なコンポーネントがインストールされます。vendor/quartet/sample-library配下のbin/phpunitを実行して、依存パッケージ内のユニットテストを実行するという風にできます。

Gitリポジトリもルートパッケージとは独立していますので、適宜コミットやpushを行っていけます。

TIPS;依存パッケージ側のコミット忘れ防止

vendorディレクトリ配下で依存パッケージの開発を進めていると、そこはルートパッケージとは別のGitリポジトリになっているため、うっかり変更をコミットするのを忘れてしまう場合があります。 これを根本的に防ぐ設定などを私は知らないのですが、気づきやすくする習慣としては、ルートパッケージ側でマメにphp composer.phar statusコマンドを実行しています。このコマンドを実行すると、vendor配下でコミットしていない修正があると、それが表示されます(前回の記事参照)。

開発の区切り:依存パッケージのリリース

機能の実装が一段落したら、依存パッケージにタグ付けし、タグとともにGitHubへpushします。これで、バージョン1.0.0がリリースされた状態になります。

$ git tag v1.0.0
$ git push origin master --tags

GitHubでは、タグは自動的にリリースのように扱われますが、明示的にリリースノートを書いて登録することもできるので、書いておくことをオススメします。なお、リリースノートはリリース前にDraftとして作成することもできます。私は、次にリリース予定の機能を、バージョン番号とともにDraftに書いておき、それを目指して開発するというサイクルで開発しています。

依存パッケージをリリースしたら、ルートパッケージ側では、サポートする最も古いバージョンとして、今回リリースした 1.0.0composer.jsonに指定しておきます。

"quartet/sample-library": "^1.0.0"

その他のTIPS

複数ブランチでの作業

ルートパッケージ側で複数ブランチを切り替えて作業している場合、依存パッケージの状態がブランチによって異なるという状況がでてきます。この時、ブランチを切り替えてcopmoser installすると、切り替え先ブランチのcomposer.jsonに記述がない依存パッケージは、vendorディレクトリからごっそり削除されるので注意が必要です。(この時はさくっと消してくれる(涙) 常に変更分をGitHub等へpushしておくことは安全策として必要ですね)

複数の依存パッケージを、ルートで同時に更新する

複数の依存パッケージを修正し、それをルートパッケージ側に反映させる場合、composer updateの引数に、依存パッケージをすべて並べて指定します。この引数の指定をComposerではホワイトリストと呼んでいます。たとえばquartet/sample-libraryの更新と同時に、quartet/commonも更新する場合は、次のようにします。

$ php composer.phar update quartet/sample-library quartet/common

必要なものがすべて指定されていれば、問題なく更新されます。しかし、次のような状況だと、この時点でバージョンの解決エラーになります。

  • quartet/sample-libraryの新しいバージョンは、quartet/collection v1.1に依存している
  • コマンド実行前に、プロジェクトに quartet/collection v1.0 がインストールされていた

quartet/sample-libraryを更新するときに、新しい依存バージョンの quartet/collection を自動的にインストールしてくれるように勘違いしそうですが、上のコマンドのように引数でパッケージを個別に指定してアップデートする場合はそのようにはなりません。引数に指定されてていないパッケージは、すでにインストールされているバージョンのままバージョンチェックが行われます。この結果、バージョンが解決できないというエラーになります。

この場合、対処法は2つあります。

1. 更新が必要なパッケージを、明示的にホワイトリストに追加する。

quartet/collectionも明示的に更新が必要なので、次のようにComposerコマンドを実行すれば問題なく更新できます。

$ php composer.phar update quartet/sample-library quartet/common quartet/collection

2. --with-dependencies オプションをつける。

このオプションをつけると、ホワイトリストに指定していなくても依存バージョンの更新分を自動的に更新します。

$ php composer.phar update --with-dependencies quartet/sample-library quartet/common

2 のオプションは便利なのですが、自分の管理しているパッケージについては、できるだけ 1 のように明示的に更新していく方がよいのではないかと思います。

おわりに

前回と今回とで、複数のパッケージに分割しながら開発を進めるためのComposerの使い方の基本を解説しました。慣れないと手間に感じるようなこともありますが、ソフトウェアを小さなコンポーネントの集まりとして作っていくスタイルでは、個々のパッケージをより上手く開発し使っていく技術として、Composerの使い方・管理の仕方の知識も必須です。少し知っておけば、開発が捗り、また、いろいろなことを安心して進められるようになります。これまで何となくパッケージのインストールにComposerコマンドを使っていただけという方も、是非もう一歩踏み込んで管理の仕方を復習してみると、いろいろな気づきがあってオススメです!