Edited at

Composerのcreate-projectは何をしているのか、何が出来るのか


記事のゴール

BEAR.Sundayのチュートリアルでプロジェクト作成時に行う以下の操作。

$ composer create-project bear/skeleton MyVendor.Weekday

Installing bear/skeleton (1.7.4)
- Installing bear/skeleton (1.7.4): Loading from cache
Created project in MyVendor.Weekday
> BEAR\Skeleton\Composer::install

What is the vendor name ?

(MyVendor):

What is the project name ?

(MyProject):Weekday

上記の対話で如何にして名前空間の設定やテストの作成などを行なっているのかを知る。


create-projectの挙動を知る


This is the equivalent of doing a git clone/svn checkout followed by a composer install of the vendors.

---

git clone/svn checkoutのあとにcomposer installを実行すること同等です。


$ composer create-project foo/bar

上記のコマンドは以下の操作と同等のものであるとのこと。

$ git clone https://github.com/foo/bar

$ cd bar
$ composer install

参考:

https://getcomposer.org/doc/03-cli.md#create-project


BEAR\Skeleton\Composer::install について知る

bear/skeletonインストール直後に呼ばれていたBEAR\Skeleton\Composer::installがどこから呼ばれていたか探したところ、composer.jsonのscriptsから以下のように呼ばれていることがわかった。


composer.json

{

...
"scripts" :{
"pre-update-cmd": "BEAR\\Skeleton\\Composer::install",


composer.jsonのscriptsとは何か


A script, in Composer's terms, can either be a PHP callback (defined as a static method) or any command-line executable command.

---

Composerの用語では、スクリプトはPHPコールバック(静的メソッドとして定義)またはコマンドライン実行可能コマンドのいずれかです。



  • 静的メソッドとして定義されたコールバック


  • composer xxx のような形でコマンドラインから実行するコマンド

の2つに分類される。

今回は前者であろう。

参考:

https://getcomposer.org/doc/articles/scripts.md#scripts


pre-update-cmdとは何か

Composerは処理の実行中にいくつかのイベントを発火させる。その中の1つ。

関係しそうなイベントをいくつか抜粋。

イベント
タイミング

pre-install-cmd
composer.lockが存在する状態でinstallコマンドが実行される前

post-install-cmd
composer.lockが存在する状態でinstallコマンドが実行された後

pre-update-cmd
updateコマンドが実行される前
composer.lockが存在しない状態でinstallコマンドが実行される前

post-update-cmd
updateコマンドが実行された後
composer.lockが存在しない状態でinstallコマンドが実行された後

post-root-package-install
create-projectコマンド中にルートパッケージがインストールされた後

post-create-project-cmd
create-projectコマンドが実行された後

イベントは手動で発火させることも可能。

$ composer run-script pre-update-cmd

参考:

https://getcomposer.org/doc/articles/scripts.md#running-scripts-manually


pre-update-cmdはどのタイミングで発火するのか

composer create-projectにおいてどのタイミングで発火するかテストする。

$ composer create-project piotzkhider/composer:dev-master

Installing piotzkhider/composer (dev-master 2d8512df0a34f4c0b9cd65b08a3feb4721c290b1)
- Installing piotzkhider/composer (dev-master 2d8512d): Cloning 2d8512df0a from cache
Created project in /packages/composer
> Skeleton\Composer::postRootPackageInstall
postRootPackageInstall <- post-root-package-install
> Skeleton\Composer::preUpdate
preUpdate <- pre-update-cmd
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
Generating autoload files
> Skeleton\Composer::postUpdate
postUpdate <- post-update-cmd
Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? Y
> Skeleton\Composer::postCreateProject
postCreateProject <- post-create-project-cmd

composer.lockが存在しないのでinstall系イベントは発火していない。


どのように対話を行なっているのか


When an event is fired, your PHP callback receives as first argument a Composer\EventDispatcher\Event object. This object has a getName() method that lets you retrieve the event name.

---

イベントが発生すると、PHPコールバックは最初の引数としてComposer\EventDispatcher\Eventオブジェクトを受け取り ます。このオブジェクトにはgetName()、イベント名を取得できるメソッドがあります。


コールバックはスクリプトの種類に応じたComposer\EventDispatcher\Eventのサブクラスを引数として受け取ることが出来る。

今回のイベント、pre-update-cmdはコマンドイベントにあたるためComposer\Script\Eventが渡される。

関係するいくつかのサブクラスを抜粋。他にも色々。

イベントの種類
渡されるサブクラス

コマンドイベント
Composer\Script\Event

インストーラーイベント
Composer\Installer\InstallerEvent

パッケージイベント
Composer\Installer\PackageEvent

渡されたEventのサブクラスからIOに関するクラスを呼び出し対話を行なっている。


src/Install.php

    public function __invoke(Event $event) : void

{
$io = $event->getIO();
$vendor = $this->ask($io, 'What is the vendor name ?', 'MyVendor');
...

private function ask(IOInterface $io, string $question, string $default) : string
{
$ask = sprintf("\n<question>%s</question>\n\n(<comment>%s</comment>):", $question, $default);

return $io->ask($ask, $default);
}


参考:

https://getcomposer.org/doc/articles/scripts.md#event-classes

https://getcomposer.org/doc/articles/scripts.md#defining-scripts

https://getcomposer.org/apidoc/master/Composer/Script/Event.html


名前空間の設定やテストの作成など

素直にスタブに設定された文言を対話で得たベンダ名、パッケージ名へと置き換えていく。

不要となったInstallファイルの削除やディレクトリの権限設定、composer.jsonの内容の置換なども同様に難しい手段は取っていない。


src/Install.php

    private function rename(string $vendor, string $package) : callable

{
$jobRename = function (\SplFileInfo $file) use ($vendor, $package) {
if (is_dir($file) || ! is_writable($file)) {
return;
}
$contents = file_get_contents($file);
$contents = str_replace(
['BEAR.Skeleton', 'BEAR\Skeleton', 'bear/skeleton'],
["{$vendor}.{$package}", "{$vendor}\\{$package}", strtolower("{$vendor}/{$package}")],
$contents
);
file_put_contents($file, $contents);
};

return $jobRename;
}