昨今のPHPを使った開発の現場では、パッケージ(プロジェクトで利用するライブラリ)の依存管理をComposerに任せて、packagistに登録されたOSSのパッケージや、Satis等で構築したプライベートなComposerリポジトリに登録した自社のパッケージを組み合わせて開発するスタイルになっていると思います。 このような開発で、自社のパッケージも積極的に作っていく場合、どうしても、1つのアプリケーションを開発しながら同時に複数のライブラリパッケージを修正するといったことが必要になってきます。

Composerを使うと、開発対象のアプリケーションの依存パッケージは、プロジェクトのvendorディレクトリ配下にすべて保存され、composer updateコマンドで更新などが行えます。複数のパッケージの開発を同時に進める場合は、基本的にルートとなる1つのアプリケーション/パッケージのプロジェクトを用意して、そのvendor配下に展開された個々のパッケージという関係で作業します。つまり、vendor配下に開発中のパッケージも入っている状態です。このような状態で、vendor配下に入っているパッケージもその場で更新などを行っていけると、動作する1つのアプリケーションで一気通貫に開発できてとてもスムーズになります。

今回と次回との2回に分けて、Composerを使った複数パッケージの同時開発のTIPSを解説します。今回はまず基礎知識として、vendor配下をComposerがどのように管理しているのかということを整理しておきます。

vendor配下のパッケージのgitの状態

普通にstableリリースされているパッケージをcomposer requireすると、VCS系の情報が削除された状態でインストールされます。GitHubのリポジトリでリリースしているパッケージであれば、リリースタグごとのzipファイル等から展開されます。たとえばquartet/contextual-validatorcomposer requireしてみると、次のようになります。

$ composer require quartet/contextual-validator
Using version ^1.1 for quartet/contextual-validator
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing symfony/property-access (v2.7.0)
    Downloading: 100%

  - Installing phpmentors/domain-kata (v1.3.0)
    Downloading: 100%

  - Installing quartet/contextual-validator (v1.1.6)
    Downloading: 100%

Writing lock file
Generating autoload files

ここでvendor/quartet/contextual-validatorディレクトリへ移動してみると、そこに.gitディレクトリはないことが分かります。

├── .gitignore
├── .php_cs
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src
└── tests

Composerによるパッケージのインストール時にVCSの情報を保持したままにしたい場合は、コマンドのオプションに --prefer-sourceをつけます。この指定は、OSSへパッチを送りたいときなどに便利です(実際の開発で使う指定は、次回解説します)。

$ composer require quartet/contextual-validator --prefer-source
Using version ^1.1 for quartet/contextual-validator
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing symfony/property-access (v2.7.0)
    Cloning bc738f091816455d5c30b99f5a8d714469b44ad4

  - Installing phpmentors/domain-kata (v1.3.0)
    Cloning 0395da95eb2c8d88bccd1cffb0f371242bc69809

  - Installing quartet/contextual-validator (v1.1.6)
    Cloning cfe034f88a563955134ca7dd165807dc9a7dea6f

Writing lock file
Generating autoload files

メッセージでもリポジトリをcloneしていることが分かります。今度は.gitディレクトリがある状態になりました。

├── .git
├── .gitignore
├── .php_cs
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src
└── tests

.gitディレクトリがありますので、このディレクトリ配下を独立してgit操作可能です。 このパッケージのgitローカルリポジトリは、Composerで取得したときに次のような状態になっています。

  • remote名composerが登録される(URLは、ソースの取得元)
  • 該当するコミットがチェックアウトされ、デタッチ状態になる (prefer-sourceの場合)

git remoteの結果は次のようになっています

$ git remote -v
composer	git://github.com/quartetcom/contextual-validator.git (fetch)
composer	git://github.com/quartetcom/contextual-validator.git (push)
origin	git://github.com/quartetcom/contextual-validator.git (fetch)
origin	git@github.com:quartetcom/contextual-validator.git (push)

また、tig等でログ/ブランチすると、次のようになっています。

log-branch

Composerのソースでは

Composer/Downloader/GitDownloader.phpupdateToCommit()メソッドで処理を行っています。

このあたりであれこれやっているのは、次で説明するような「ローカルに変更があった場合」のさまざまな対応をしているということかと思います。

vendor配下のコードに変更がある場合

vendor配下のパッケージがgitリポジトリになっている場合、開発中にそこのファイルを直接編集したら、その後のパッケージの更新などはどうなるのでしょうか? ためしに、vendor/quartet/contextual-validatorディレクトリでREADME.mdの内容を少し編集しておきます(コミットなどは行いません)。

$ git status
HEAD detached at v1.1.6
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   README.md

この状態で、プロジェクトのルートディレクトリへ移動して、composer statusコマンドを実行すると、変更が検出されます。

$ composer status -v
You have changes in the following dependencies:
(....)/vendor/quartet/contextual-validator:
    M README.md

(この変更の検出には、内部でgit status --porcelain --untracked-files=noというコマンドが実行されています)

この状態でcomposer update quartet/contextual-validatorなどとした場合、Composerリポジトリ(packagist)上のcontexual-validator本体に更新がなければ特に何も起こらず、READMEの変更もそのままになります。

では、contextual-validator本体が更新されていたらどうでしょうか?この場合、ローカルの変更をどうするのか、プロンプトが表示されます。

$ composer update quartet/contextual-validator
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Updating quartet/contextual-validator dev-master (86ddd81 => 79342b3)
    The package has modified files:
    M README.md
    Discard changes [y,n,v,s,?]?

yと入力すれば、ローカルの変更は破棄され、composer updateが継続されます。変更をどこかにとっておきたい場合は、sを入力すると変更がgit stashされcomposer updateが継続され、その後stashされた分の復帰を試みます。stashの復帰が問題なく完了すればcomposer updateの処理が継続しますが、競合などがあってstashの復帰に失敗した場合は、composer updateが中断されます。プロンプトの時点でnを入力すれば、中断になります。

Composerのソースでは

Composer/Downloader/GitDownloader.phpに次のメソッドがあります。

  • discardChanges()メソッド
    • 内部ではgit reset --hardが実行されています
  • stashChanges()メソッド
    • 内部ではgit stashが実行されています
  • reapplyChanges()メソッド
    • 内部ではgit stash popが実行されています

まとめ

Composerは便利なツールですが、Composerへの依存度が高くなってくると、バージョンの指定方法やコマンドの挙動などについて理解を深めておかないと、いろいろと悩まされることが増えてきます。 また、Composerの挙動と合わせて、gitに代表されるバージョン管理ツールの使い方の知識も補強しておくと、ぐっと安心感が高まりますね。

次回は、composer.jsonでのバージョン指定方法との絡み、開発の流れなどを解説します。