composer 導入をまじめに考える

これは結構大きいPHPのプロジェクトに composer を導入する機会があったので、そのときに考えてたことや行ったこと、使い方などをメモするために書いた。

## モチベーション
私達は PHP のパッケージの管理を管理する際は pear と git submodule を利用していた。これらのやり方は意外と長続きした。これらにはついて様々な問題を抱えており、ついに限界がきてしまった。

### pear 
* pear でパッケージを導入するには root 権限が必要なので、毎回インフラチームに導入を依頼するのが必要があった。
* pear で導入されたパッケージについてバージョンを上げようとすると、全APサーバーで更新をかける必要があった。

これらの点から面倒だったのと、気軽に変更できないので、不要になったものも削除されることなく、放置されるのが問題だった

### git submodule 
* こちらは pear とは異なり手軽に導入＆更新することができた
* しかし、githubのURLをリポジトリに登録してしまうと github に問題が生じたとき（落ちてるなど）にデプロイできなくなるという問題があったため、一旦社内のリポジトリに登録してから submodule にするなどをしていた。これはとても面倒。
* 依存関係があるライブラリを導入しようと思うと、依存しているライブラリをすべてこちらでリポジトリに登録必要があった。これはつらすぎる・・・。

これらのことから手軽に管理できる＆依存関係をいい感じに取り扱ってくれる仕組みを求めていた。それに合致するのが composer だった。

## とにかく composer でパッケージを入れる
能書きはこれぐらいにして作業に移ろう。

まずは composer をダウンロードしよう

```bash
curl -sS https://getcomposer.org/installer | php
```

これで composer.phar がダウンロードされる。phar は java でいう jar みたいな感じ。以下のように直接実行することができる。

```bash
./composer.phar
```

こいつと同じディレクトリに composer.json を以下のように作成する。これに依存関係などを記述していく形になる。


```json
{
    "require": {
        "mockery/mockery": "*",
        "facebook/php-sdk": "v3.2.3"
    }
}
```

記述が終わったら以下のコマンドを実行して記述したライブラリを composer にインストールさせる。

```bash
./composer.phar install
```

デフォルトだと vendor というディレクトリが生成され、インストールされるようになっている。このとき、composer.lock というファイルも一緒に出力される。このファイルで composer install コマンドに対して影響を与える。まとめるとこんな感じ。


| コマンド             | 挙動         |
|:--------------------|------------:|
| composer install    |  composer.lock があればそれに基づいてパッケージをインストール。なければ composer.json に基づいてパッケージをインストールした後に composer.lock を作成 |
| composer update     |  パッケージのインストールと composer.lock の更新 |

動きとしてはこんな感じで、実際に使うときは以下のような使われ方になることが多い。

* プロジェクトをセットアップしたいときには composer install
* パッケージのバージョン上げたり、追加したりしたいときに composer.json をいじって composer update



## composer の設定ファイルを作りこむ
作りこむいうても、たいしたことはやらない。

composer に取り込むパッケージは https://packagist.org/ から探そう。
例えば https://packagist.org/packages/mockery/mockery を導入する場合は以下のようになる。

```json
{
    "require": {
        "mockery/mockery": "0.9.0"
    }
}

```

ここまでの composer.json では適当にバージョンを指定していたが、依存しているライブラリを追加する際は以下のJSONのように必ずバージョンを指定するようにする。バージョンがない場合（githubに置かれててリリースタグが切られていないケースなど）の場合はコミットハッシュを記載して固定する。

```json
{
    "require": {
        "mockery/mockery": "0.9.0",
        "kertz/twitteroauth": "dev-master#e79f24e7a8bc70604c0c78f00b502ff0dc0743b9"
    }
}

```
ここでバージョンを指定しなくても composer.lock の存在によって composer install で導入されるバージョンは常に同じになるが、composer update を行うときにすべてのパッケージのバージョンが上がってしまい、動作確認するのが大変になってしまうのでバージョンを必ず指定する。

逆に composer.json でバージョンを指定しているなら composer.lock はいらないと思う方もいると思うが、composer.json では依存しているライブラリ（require のところに書いたもの）が依存しているライブラリの composer.json に以下の様な JSON が記述されていた場合を考えてみると必要性がよくわかる。

```json
{
    "require": {
        "kertz/twitteroauth": "dev-master"
    }
}
```

このように指定されていると composer install タイミングによって git の HEAD が異なる可能性があるため、必ず同じ状態を構成することができない。従って composer.lock もリポジトリに含める必要がある。

## リポジトリに入れるファイル
composer.phar, composer.json, composer.lock の3つをリポジトリに入れる。
composer.phar は何故かリポジトリに入れないと書いている人もいるが、プロジェクトを正常にセットアップできるバージョンの composer がリポジトリにないと他の開発者やデプロイツールが利用する composer のバージョンが異なるようなことが発生してしまうのでよくないと思う。

## 依存関係に導入したくない PHP-Extension を求められた時
composer 移行するときに以下のような記述があるパッケージを見つけた

```json
{
   "require": {
        "ext-gmp": "*"
   }
}
```

ext-gmp とは http://www.php.net/manual/en/book.gmp.php これのことである。
php-extension なのでサーバーに導入する必要があるが、このライブラリの ext-gmp に依存している部分を利用していなかったので、これを導入したくなかった。しかし、導入しないと composer install が通らない。こんなので困っているあなたは以下の'provide'の部分の設定を composer.json に入れよう。

```composer.json
{
    "provide": {
        "ext-gmp": "*"
    },
    "require": {
        "mockery/mockery": "0.9.0",
        "ore/extgmp-ni-izonshiteruyatsu": "0.5.0",
    }
}
```

provide という項目にそのライブラリを記述すると、すでに導入されているように見せかけることができるようだ。

これで余計なものを導入せずにすんだ。めでたしめでたし。


## github など外部サービス依存をはずす

github などに composer が依存しているとデプロイ時に github が落ちているときにデプロイできないことが発生する。通常時は復活するのを待っていればいいのかもしれないが、課金周りなどクリティカルなところで問題があったときに 「github が落ちているから修正を反映できません！！」では困る。外部サービス依存をはずすために [satis](https://github.com/composer/satis) というプロダクトを使用する

satis はいわゆるオレオレ packagist を立ち上げるためのプロダクトだが composer 用のプロキシとしても使える。
今回はプロキシ用途としての使い方の紹介になる。

### satis のセットアップ
マニュアル通りにやれば大丈夫。以下の様な感じ

```bash
curl -sS https://getcomposer.org/installer | php
./composer.phar create-project composer/satis --stability=dev --keep-vcs
```

そして composer.json と同じディレクトリに satis.json を以下のように作成する

```satis.json
{
    "name": "ore",
    "homepage": "http://oreore.satis.com",
    "repositories": [
        {
                "type": "composer",
                "url": "https://packagist.org/"
        }
    ],
    "require": {
        "mockery/mockery": "*"
    }
}

```

そして satis.json に対していろいろ設定を入れて、以下のコマンドを実行して satis を更新する形になります。
運用するときはこのコマンドと、最新の satis.json を取り込む操作を cron に登録しておくといいでしょう。

```bash
php satis/bin/satis build satis.json public
```

satis を apache に登録するのが面倒だったので、ここではビルトインサーバーを使います。（手抜き）

```bash
php -S localhost:43210 public
```

http://localhost:43210 にアクセスしてみて機能していればセットアップは完了です。
動作を確認できたところでビルトインサーバーみたいな手抜き運用ではなく、ちゃんと設定してあげよう。

### satis の設定を作りこむ
先ほど適当に設定を書いてしまったのでここで設定をきちんとさせる。
ここまでやってきたら例を見れば、だいたい設定の仕方がわかると思うので例をいきなりペタリと貼る。
これを satis.json として保存する

```satis.json
{
    "name": "oreore",
    "homepage": "http://oreore.satis.com",
    "repositories": [
        {
            "type": "vcs",
            "url": "git@shanai:shanai/mylib"
        },
        {
            "type": "composer",
            "url": "https://packagist.org/"
        }
    ],
    "require": {
        "mockery/mockery": "*",
        "kertz/twitteroauth": "*",
        "facebook/php-sdk": "*"
    },
    "archive": {
         "directory": "dist",
         "format": "zip",
         "prefix-url": "http://oreore.satis.com",
         "skip-dev": false
    },

    "require-dependencies": true
}
```

補足

* require に記述するバージョンは常に "*" を指定します。（バージョンはリポジトリ側で管理しないと面倒なので）
* archive の下の format は tar と zip が指定できるようですが、github に合わせてここでは zip にしています。
* archive の下の prefix は satis をプロキシとして使用した時に使用される prefix です。最後に / は付けないようにしましょう。
* arichive の下の skip-dev は true にするとgit のブランチ（master も含む）を取り込まないようになります。master ブランチしかないものもあるので、通常は false でいいでしょう。
* require-dependencies は必ず true にしておきます。これがないと依存関係にあるライブラリが取り込まれません。

### github の API トークンを設定する
satis の設定も終わったことだし、早速使おう・・と思いきや satis が github の API を叩きすぎて Rate Limit に引っかかってしまうことがある。これは非常に困るので github の API トークンを設定して引っかからないようにする必要がある。

satis を実行しているユーザーの ~/.composer に config.json を以下のように作成して github の API トークンを登録しましょう。

```config.json
{
    "config": {
        "preferred-install": "dist",
        "github-oauth": {
            "github.com": "TOKENTOKENTOKENTOKENTOKENTOKEN123456"
        }
    }
}
```


### リポジトリの composer.json が satis を参照するようにする
satis のほうを composer が見るように設定します。

デフォルトだと packagist に問い合わせてしまう設定になっているので、repositories のところを以下のように設定する。

```composer.json
{
    "repositories": [
        {
                "packagist": false
        },
    ]
    "require": {
        "mockery/mockery": "0.9.0",
        ...
    },
}
```

これで packagist に登録されているものについては satis を参照するようになるものの、git リポジトリに登録されているもの（もちろん github のものも含む） を登録している場合は source のほうを優先的に使うようなので、デプロイ時に composer を動かすときは composer を以下の様に --prefer-dist をつけて常に dist から取得させるようにする。

```php
./composer.phar install --prefer-dist
```

これを指定することによってパッケージを常に dist から取得するようになるので、satis のほうからデータを取得するようになる。

だが、こんなオプションをつけて実行するのは面倒くさくて誰もやらない（私もやりたくない）ので、以下のようにcomposer.json の 'config' に設定を追加して --prefer-dist を強制するようにする。

```composer.json
{
    "repositories": [
      ...
    ]
    "require": {
      ...
    },
    "config": {
        "preferred-install": "dist"
    }
}
```

## デプロイ時にエラーが出るんだけど・・。
さぁ、satis 対応も終わり、本番反映も済ませた。めでたしめでたし、と思いきやそんなことはなかった。

アクセス数がある程度あるサービスでデプロイ方法がイケてないと、デプロイ直後だけ以下のようなエラーが発生する。 

```
PHP Fatal error:  Class 'ComposerAutoloaderInit2ee96b8a32d3333d6a04bd565ca3b432' not found in /home/www/public/vendor/autoload.php on line 7
```

composer は composer install を実行した時に毎回初期化用のメソッド名を書き換えるようになっている。
ドキュメントルートに直接 rsync するような運用をしている場合はすべてのファイルのデプロイ（rsync）が完了するまでにアクセスされるとエラーになってしまう。
つまり composer を導入したことによってデプロイするたびにエラーが発生するようになってしまった。これは非常に困った。

これを解消するにはアトミックにデプロイが行えるようにする必要があった。
これを実現する方法についてはまた別に機会があれば書いてみようかと。

## 運用する上で知っておかないといけないこと
### composer self-update する際は必ずバージョンを指定する
composer 本体を更新したいときは composer.phar 以下のようにバージョンを指定して実行する必要がある。
```
./composer.phar self-update 1.0.0-alpha8
```

後ろのバージョンを指定しないで実行した場合は github の master を利用するようになっているので、カジュアルに壊れた composer が降ってきたりして危険です。（なんでこんな仕様になってるんだろ・・。）

ちなみにバージョンは https://github.com/composer/composer/tags から調べます。

### composer のパッケージが何故か更新されない現象が発生した
composer のキャッシュもたまに正常に更新されなくなったりします。なんか更新されないなと思ったらホームディレクトリに自動的に作られるキャッシュディレクトリ(~/.composer)と vendor 以下のファイルを削除して、再度 composer install を実行すれば解消します

## どうでもいい話
* これ全然メモじゃねぇ！
* 最低限必要なことしか書いてないので、他の記事やドキュメントを見たほうがいいですねー