curl を使って composer update を 2 倍速くする

## はじめに

先日下記の記事を見まして、

[packagist.orgのミラーサーバを立ててcomposer updateを18倍速くする #packagist - Qiita](http://qiita.com/Hiraku/items/51961aa49f7a2922011d "packagist.orgのミラーサーバを立ててcomposer updateを18倍速くする #packagist - Qiita")

> composerがfile_get_contentsでjsonをダウンロードしていて、どうやらKeep-Aliveを使っていないし、並列ダウンロードもしていない

並列ダウンロードはさすがに簡単ではない（Composer 本体の修正が必要そう）けれど Keep-Alive なら curl でちょろっとできそうな気がしたのでやってみました。

それはそれとして、Composer の事を **パッケージ管理ツール** と呼ぶとツッコミを受けそうなので **依存関係管理ツール** と呼んだほうが良さそうです。

[Getting Started - Composer](https://getcomposer.org/doc/00-intro.md#dependency-management "Getting Started - Composer")

> Composer is not a package manager.
> ...
> Thus, it is a dependency manager.

## Composer プラグイン について

Composer にはプラグイン機構があります。

[Setting up and using plugins - Composer](https://getcomposer.org/doc/articles/plugins.md "Setting up and using plugins - Composer")

フレームワークのプラグインとかバンドルとかモジュールとか呼ばれる類のパッケージをインストールするときに、インストール先を Composer の本来の vendor/ ではなく、フレームワークが認識する所定のディレクトリに変更するためのカスタムインストーラーとしての用途が多いと思います。

[CakePHP Composerを使ってプラグインをインストールする | junichi11.com](http://junichi11.com/?p=2546 "CakePHP Composerを使ってプラグインをインストールする | junichi11.com")

今回やりたいことは packagist.org からの json のダウンロードを `file_get_contents` ではなく curl 拡張を使うようにすることなのですが、

[Event Handler - Composer](https://getcomposer.org/doc/articles/plugins.md#event-handler "Event Handler - Composer")

`PRE_FILE_DOWNLOAD` イベントを処理すればできそうです。

リンク先のサンプルコードを見た感じ、オレオレ Composer リポジトリを作った時にソースコードを S3 とかにホストして `s3://` のような独自のスキームを使えるようにすることが想定されていそうですが、Composer の本来の http/https のダウンローダーも差し替えることができました。

また、フレームワークのためのカスタムインストーラーとして使用する場合はプロジェクトの composer.json の require にプラグインのパッケージ名を記述してプロジェクトローカルにプラグインをインストールすると思いますが、`composer global require` でプラグインをインストールすればグローバルにプラグインを有効にすることができます。

[Using Plugins - Composer](https://getcomposer.org/doc/articles/plugins.md#using-plugins "Using Plugins - Composer")

## Composer Curl Plugin

プラグインのコードは下記に置いています。

[ngyuki/composer-curl-plugin](https://github.com/ngyuki/composer-curl-plugin "ngyuki/composer-curl-plugin")

Packagist に登録しているので `composer global require` でグローバルにインストールできます。

```console
$ composer global require ngyuki/composer-curl-plugin:dev-master
```

効果の程を `symfony/symfony` で確認してみます。

まずは `symfony/symfony` をインストールします。

```console
$ composer require symfony/symfony:\*
```

キャッシュを削除し、プラグインを使わずに（`--no-plugins` を指定） `composer update` してみます。

```console
$ rm -fr ~/.composer/cache/repo/
$ composer update --dry-run --profile --no-plugins
 :
Memory usage: 92.4MB (peak: 111.41MB), time: 139.99s
```

私の環境では２分強かかりました。

次に、プラグインが有効なままで `composer update` してみます。

```console
$ rm -fr ~/.composer/cache/repo/
$ composer update --dry-run --profile
 :
Memory usage: 92.61MB (peak: 111.62MB), time: 67.21s
```

２倍ぐらい早くなりました。

## さいごに

プラグインの中で curl のリソースを再利用しているので、複数回のダウンロードが Keep-Alive により共通のコネクションで行われるようになっています。
が、それだけで２倍も早くなるものですかね、PHP の http/https ストリームラッパーになにかしら遅くなる要因でもあるのかもしれません。

## 追記 2014/12/22

packagist.org 以外でも使えるようにしてみました。

デフォルトでは packagist.org でのみ有効になりますが、config で次のように追記しておくと、packagist.org 以外のミラーでも有効にできます。

```console
$ composer config -g -e
```

```json
{
    "config": {
        "curl-plugin": {
            "hosts": [
                "packagist.tojiru.net"
            ]
        }
    }
}
```

早速 packagist.tojiru.net ミラーで効果の程を確認してみます。

```console
$ composer config -g repositories.packagist composer http://packagist.tojiru.net
```

まずはプラグインを使わずに試してみます。

```console
$ rm -fr ~/.composer/cache/repo/
$ composer update --dry-run --profile --no-plugins
[7.2MB/0.08s] Loading composer repositories with package information
[7.6MB/0.15s] Updating dependencies (including require-dev)
[111.8MB/25.38s] Nothing to install or update
Memory usage: 93.01MB (peak: 112.05MB), time: 25.41s
```

私の環境では 25 秒ぐらいでした。

次に、プラグインを有効にして試してみます。

```console
$ rm -fr ~/.composer/cache/repo/
$ composer update --dry-run --profile
[7.3MB/0.08s] Loading composer repositories with package information
[7.7MB/0.15s] Updating dependencies (including require-dev)
[112.0MB/18.01s] Nothing to install or update
Memory usage: 93.23MB (peak: 112.28MB), time: 18.04s
```

18 秒ぐらいになりました。

元々レイテンシの良いミラーだとそれほどの効果はなさそうです。