日記

日々のことと、Python/Django/PHP/Laravel/nodejs などソフトウェア開発のことを書き綴ります

PHP Composer を使ってみた

PHP も最近はパッケージ管理ツールがあるらしく Composer というものらしい。 諸事情で使わないといけなくなったため、導入から利用までを簡単にまとめてみたいと思う。

完全に自分用の備忘録ですが…。

インストール

Mac 上でやろうと思ったけど、現状の環境を変更してしまうと、ちょっと困るタイミングだったので、 vagrantubuntu 14.04 をインスコして試してみた。

vagrantubuntu の準備
vagrant box add ubuntu14.04 https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box
vagrant init
vagrant up

そして ssh でログイン

vagrant ssh
PHP のインストール

とりあえず最低限のみ

sudo apt-get update
sudo apt-get php5 libapache2-mod-php5
Composer のインストール

Composer は取りあえずローカル環境のみにインストール。

mkdir php; cd php
curl -sS https://getcomposer.org/installer | php

composer.phar というファイルがカレントディレクトリに生成されたら、Composer のインストールは完了。試しに実行してみると

./composer.phar install

Composer could not find a composer.json file in /home/vagrant/php To initialize a project, please create a composer.json file as described in the https://getcomposer.org/ "Getting Started" section

composer.json を作れって怒られた。

Composer 実行の準備

Basic usage - Composer

実行ログに出ていた上記のページを参考にしつつ、準備をしてみる。 ちなみに日本語訳は下記にて作成してくれている。

Composer ドキュメント日本語訳

composer.json の作成

ドキュメントにならって、下記の内容で composer.json を作ってみる。

{
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

作成したら、インストールコマンドを実行

./composer.phar install

Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing monolog/monolog (1.0.2)
    Downloading: 100%         

Writing lock file
Generating autoload files

インストールが出来たみたい。カレントディレクトリ以下に vendor ディレクトリが作成されて、その下にパッケージがインスコされるようだ。

JSONを書くの辛いから、 pip freeze みたいにパッケージをインストールした後に定義ファイルを出力できたらいいのにね。

composer.json の中身

先ほど使った composer.json の中身を振り返り。

パッケージとバージョン指定
{
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

require キーの下に必要なパッケージを指定していくようで、パッケージ名をキーに、値はバージョン指定を行う。 パッケージ名は [ベンダー名] / [プロジェクト名] となっていて、これが Composer ルールの模様。

この例のバージョン指定は 1.0. 系であれば、どのバージョンでも良く、複数あれば一番新しいものが選択されます。先ほどの monolog の例では 1.0.2 が 1.0. 系だったため選択されたようです。

バージョン指定の変更

monolog は 1.0.2 がインスコされたが、1.0.1 じゃないと互換性が無くて困る!と言うときは、完全なバージョン番号を指定します。

{
    "require": {
        "monolog/monolog": "1.0.1"
    }
}

例としては、こんな感じに。そしてもう一度、composer のコマンドを実行します。

./composer.phar install

この結果は…

Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. Run update to update them.
Nothing to install or update
Generating autoload files

怒られました。 lock file って何?と思いつつ、再実行するときは update を実行しろとのこと。

./composer.phar update

今度の結果は

Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Removing monolog/monolog (1.0.2)
  - Installing monolog/monolog (1.0.1)
    Downloading: 100%         

Writing lock file
Generating autoload files

monolog 1.0.2 が削除されて、1.0.1 がインストールされました\(^o^)/

lock file ?

install を再実行したときに出て来た lock file は、ドキュメントを読み進めると出て来ました。

依存物をインストールしたあとに、Composerはインストールしたパッケージの実際のバージョンのリストをcomposer.lockに書き込みます。 このファイルはプロジェクトをこれらの特定バージョンにロックします。

アプリケーションの composer.lockを(composer.jsonと一緒に)バージョンコントロールにコミットしてください。

これは重要なことです。なぜならinstallコマンドはロックファイルが存在するかチェックし、 あるならそこに指定されているバージョンをダウンロードするからです。 (composer.jsonの内容に関わらず)

むむむ、超重要なファイルらしい。

「なぜならinstallコマンドはロックファイルが存在するかチェックし、 あるならそこに指定されているバージョンをダウンロードする」

そうなると、composer.json の存在意義が気になるところです。 composer コマンドを実行した時に、何が行われているのか軽く調べてるのに、lockファイルのバックアップを取る

cp composer.lock composer.lock.old

そして、 composer.json の monolog のバージョン指定を 1.0.* に直して、 composer update を実行します。

Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Removing monolog/monolog (1.0.1)
  - Installing monolog/monolog (1.0.2)
    Loading from cache

Writing lock file
Generating autoload files

1.0.* に指定して、composer update を行うと自動的に最新を取りに行くみたいですね。実運用時は気をつけないと、痛い目を見そう。やっぱり pip freeze 的なの欲しい。

そして、composer.lock の diff を取ってみます。

diff ./composer.lock ./composer.lock.old 
7c7
<     "hash": "e4e3109acc8a04877ee974efb7478127",
---
>     "hash": "9ba87706681b25aed62d8ed39d808fe7",
11c11
<             "version": "1.0.2",
---
>             "version": "1.0.1",
15c15
<                 "reference": "b704c49a3051536f67f2d39f13568f74615b9922"
---
>                 "reference": "303b8a83c87d5c6d749926cf02620465a5dcd0f2"
19,20c19,20
<                 "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b704c49a3051536f67f2d39f13568f74615b9922",
<                 "reference": "b704c49a3051536f67f2d39f13568f74615b9922",
---
>                 "url": "https://api.github.com/repos/Seldaek/monolog/zipball/303b8a83c87d5c6d749926cf02620465a5dcd0f2",
>                 "reference": "303b8a83c87d5c6d749926cf02620465a5dcd0f2",
27,31d26
<             "autoload": {
<                 "psr-0": {
<                     "Monolog": "src/"
<                 }
<             },
50c45
<             "time": "2011-10-24 09:39:02"
---
>             "time": "2011-08-25 20:42:58"

composer.lock 側には、 1.0.* で指定した時も明示的にバージョン番号を持っている模様。そしてパッケージは GitHub から取得してそうな感じが見て取れます。 そして気になったのが、これ。

<             "autoload": {
<                 "psr-0": {
<                     "Monolog": "src/"
<                 }
<             },

autoload? psr-0? そして 1.0.1 では設定が無いの?

オートローディング

よく分かっていない autoload ですが、これもドキュメントに説明がありました。

オートロード情報を指定するライブラリのためにComposerはvendor/autoload.phpファイルを生成します。 単にこのファイルをインクルードすれば、オートローディングを自由に手に入れることができます。

どうやら、 composer は install / update を実行した時に、 vendor/autoload.php を作成・更新しているらしい。 Composer 管理下のパッケージは、この autoload.php を require で指定すれば、使えるようになり楽ができるみたい。require 地獄から抜けられるわけですね…。

中身が気になったので見てみると

$ cat ./vendor/autoload.php 
<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer' . '/autoload_real.php';

return ComposerAutoloaderInit7d157a49b8a49b4a7eb78d59621a74d7::getLoader();

こんな感じのようです。実態が別のところにあるので、 autoload_real.php → autoload_namespaces.php と辿っていくと monolog を読み込む部分を見つけた。

$ cat vendor/composer/autoload_namespaces.php 
<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Monolog' => array($vendorDir . '/monolog/monolog/src'),
);

ここでふと、 /monolog/monolog/src の部分が気になったので、autoload_namespaces.php をバックアップして、monolog を 1.0.1 に戻してみる。

$ cp vendor/composer/autoload_namespaces.php ./autoload_namespaces.php.1.0.2

まずはバックアップして、 composer.json の monolog バージョンを 1.0.1 に変更。そして、 composer update

$ ./composer.phar update
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Removing monolog/monolog (1.0.2)
  - Installing monolog/monolog (1.0.1)
    Loading from cache

Writing lock file
Generating autoload files

バージョンを落としたので、 autoload_namespaces.php を比較してみます。

$ diff ./autoload_namespaces.php.1.0.2 ./vendor/composer/autoload_namespaces.php 
9d8
<     'Monolog' => array($vendorDir . '/monolog/monolog/src'), 

やっぱり、1.0.1 では /monolog/monolog/src の記述が無くなっていた。

<             "autoload": {
<                 "psr-0": {
<                     "Monolog": "src/"
<                 }
<             },

きっとこれの影響ですね。恐らく monolog のディレクトリ構造が変わって、ソースコードを置いている場所が変わったため、入った設定なのかなと。

更に読み進めると、自前のアプリケーションも Composer のオートローディングに載せることが出来るらしい。やり方としては、composer.json に以下のような定義を追加するだけ。PSR-0 のことが分かってないけど、ファイルかクラスの書き方の規約か何かでしょう。

{
    "autoload": {
        "psr-0": {"Acme\\": "src/"}
    }
}

Acme パッケージは src がルートと指定してます。この src の位置は、vendor と同じ階層に作れば良いらしい。 PSR-0 などよく分からないけど、自分のコードはこんな感じで置けば良いのか。

パッケージのリポジトリ

パッケージ指定は monolog/monolog で指定しているが、それだけで指定が済んでいるのはリポジトリのおかげらしい。 このリポジトリのことを packagist と言うらしい。

とりあえず、入門としてはこんなところかな。 明日は、 PSR-0 のことを少し調べて、 Laravel に入門する予定。