Edited at

Composerでどこでもテストしたかった問題

More than 1 year has passed since last update.


Composerのイケてないところ

例えば、RubyのBundlerだと、カレントディレクトリにGemfileが無くても、プロジェクトルートを勝手に判断して(つまり、Gemfileのあるディレクトリをワーキングディレクトリとして)実行してくれる。

しかし、我らがPHPのComposerはこうである。(以下、/path/to/project にcomposer.jsonがあると仮定)

$ pwd

/path/to/project/src/hoge
$ composer dumpautoload
Composer could not find a composer.json file in /path/to/project/src/hoge
To initialize a project, please create a composer.json file as described in the https://getcomposer.org/ "Getting Started" section

これの何が困るかというと、例えば composer test にPHPUnitを設定していて、testsディレクトリ内で作業中にPHPUnitを実行したくなった時、一旦プロジェクトルートにカレントディレクトリを移してコマンドを叩かなければならないことが挙げられる。


一応回避策はある


Global Options


  • --working-dir (-d): If specified, use the given directory as working directory.

引用:Command-line interface / Commands - Composer


要するに、

$ pwd

/path/to/project/src/hoge
$ composer dumpautoload --working-dir=/path/to/project
Generating autoload files

こうすればちゃんと動いてくれるということである。が、いちいち指定するのは面倒くさいしアホらしい。


それでこうした

.bashrcに以下の関数を記述した。shファイルに切り出してPATHの通ってるところに置いてもいいかもしれない。

composer() {

local dir="$PWD"
while [ -n "$dir" ]; do
[ -f "$dir/composer.json" ] && break
[ "$dir" = "/" ] && dir="." && break # / の dirname を取ると / が返るので、見つからなかったらカレントディレクトリを使うようにする
dir="$(dirname $dir)"
done
"$(which composer)" "$@" --working-dir="$dir"
}

簡単に言うと、カレントディレクトリから上位ディレクトリに向けてcomposer.jsonを探索し、最初に見つかった場所をワーキングディレクトリとしてcomposerに渡している。

例えば、これで /path/to/project にいないときでも composer test するとPHPUnitが実行できるようになる。


もうちょっといい感じにしたかった

これが動かない。

$ pwd

/path/to/project/tests
$ composer test HogeTest.php
> phpunit 'HogeTest.php'
Cannot open file "HogeTest.php".

Script phpunit handling the test event returned with error code 1

composer test tests/HogeTest.php とプロジェクトルートからのパスを渡してやる必要がある。

もちろん引数をすべてチェックして、パスであれば絶対パスに変換する方法もある。しかし、例えばカレントディレクトリに init ファイルが存在するとき、composer init するとうまく動かない。

何かもうちょっといい方法は無いものか・・・


追記 (2017-06-11)

ちょっと手を加えた。

composer() {

local command="$1" && shift
local
opt options="" dir="$PWD"
while [ -n "$dir" ]; do
[ -f "$dir/composer.json" ] && break
[ "$dir" = "/" ] && dir="." && break
dir
="$(dirname $dir)"
done
for
opt in "$@"; do
[ -e "$PWD/$opt" ] && opt="$PWD/$opt"
options+="$opt "
done
"$(which composer)" $command $options --working-dir="$dir"
}

composerの一つ目の引数をコマンドとみなして保管。後の引数をすべて回して、パスであれば絶対パスに変換するという対応をした。前のスクリプトも同様だが、ディレクトリ名にスペースがあるとうまく動かないので注意されたい。また、コマンド呼び出しの $options をダブルクォートすると、引数を複数渡したときに単一の引数とみなされてうまく動かないので注意。