オープンソースこねこね

Webプログラミングなどについてあれこれ。

OmnibusでPHPアプリケーションをRPMにパッケージングする

要約: Omnibusを使うとApachePHPをバンドルしたオールインワンなPHPアプリケーションのRPMパッケージを作れます。

最近CLIツールやAPIサーバなどはGoで書くようになって、PHPをさわる頻度が減ってきているのですが、それでもPHPの実行環境が不必要になることはなく、その個人的な一つの理由にAdminerがあります。これは2012年に一度ブログにも書いたのですが、PHPのシングルファイルをDocumentRootにおくだけで動作する、データベース用のWebUIです。

Adminer - PHPの1ファイルを置くだけで簡単に使えるDB管理ツール - オープンソースこねこね

現在でもメンテは続いているようで、今ではElasticsearchやMongoDBなどにも対応しているそうな。個人的にもずっと使い続けています。

ただ、前述のようにGoの適用範囲が広がってきたので、新しく立ち上げたサーバとかだと、Adminerを使う以外にApachePHPが必要ない環境もあります。後々の運用管理のことも考えてサーバ構築にはプロビジョニングツールを使っていることもあり、これ一つのために、ApachePHPを入れてhttpd.confとphp.iniを書き換えるレシピ書いて。。。てなことがやや面倒くさいです。

Adminerの機能単体でぱっとインストール、アンインストールできればいいかもしれない、と考えたので、omnibusを使ってApachePHPをバンドルしたオールインワンなPHPアプリケーションのRPMを作成してみました。omnibusについては以前ブログに書いたのでそちらを参照してください(CentOS用にSupervisor3.3.0のRPMパッケージを作成した - オープンソースこねこね )。今回の成果物は以下

github.com

Githubのリリースページに生成したRPMをあげたので

$ sudo yum install https://github.com/kohkimakimoto/omnibus-adminer-server/releases/download/v0.3.0/adminer-server-0.3.0-1.el7.x86_64.rpm

でインストールできます。これだけで/opt/adminer-serverApachePHPランタイムを含めたアプリケーションがインストールされます。これらはもちろんシステムワイドにあるApachePHPとは干渉しません。あとは、

$ sudo systemctl start adminer-server

とすればApacheが14200ポートで起動しますのでブラウザでhttp://localhost:14200/adminer.phpにアクセスすると

f:id:kohkimakimoto:20170329115005p:plain

と画面がでます。

補足

  • サービス設定は現状systemdのみの対応なので、このRPMはCentOS7限定です。initスクリプトを書けばCentOS6などにも対応できますが、自分が使わないので書いてません。
  • 設定ファイルは/etc/adminer-server、adminer.phpなどのPHPファイルは/var/lib/adminer-server以下に配置してあります。
  • 今回はやってないがMySQLなどDBもバンドルすれば、いわゆるLAMPアプリケーションがパッケージ配布可能になるんじゃないかと。

あとがき

こんなことしなくても、今はDocker使えばいいかも、とも思いますが、Systemdにサービスを登録するところまでyum installlでできるのでミドルウェア的なものを扱うのなら、RPMも便利ですよと。。。

PHPでcronのように定期実行をするプログラムを書いた

kohkimakimoto/workerphp · GitHub

自分で使う分には最低限、必要な機能を実装できたので紹介します。以下のようなPHPファイルを記述してPHPコマンドラインから実行するとプロセスが起動しっぱなしになりcront_timeで指定したスケジュールでジョブを定期実行してくれます。

<?php
require_once __DIR__.'/vendor/autoload.php';

$worker = new \Kohkimakimoto\Worker\Worker();

// job for every minute.
$worker->job("hello", ['cron_time' => '* * * * *', 'command' => function(){
    echo "Hello world\n";
}]);

// job runs a shell command.
$worker->job("uptime", ['cron_time' => '10 * * * *', 'command' => "uptime"]);

$worker->start();

ジョブはクロージャPHPのコードとして書くか、シェルのコマンドを文字列で直接指定することができます。インストールはcomposer installするだけですが、その他詳しい情報はリポジトリのREADMEを参照してください。内部的にジョブはプロセスをforkして実行させているので、PHPpcntlエクステンションがインストールされている必要があります。linuxならyumとかでインストールできると思います。

cronが普通に使える環境ならまあそれを使えばいいんですが、個人の用途でherokuの無料枠dynoを使って定期実行処理をしたかったので、こんなの作りました。

Web API

cronにない拡張機能として、簡易なWeb APIを提供するHTTPサーバを組み込んでいます。

$worker->httpServer->listen();

のように書くと、8080ポートを開きます。ポートを変更したいときは

$worker->httpServer->listen(8888, 'localhost');

のようにもかけます。HTTPサーバが立ち上がったら、

$ curl -XGET http://localhost:8080/{ジョブ名}

でジョブの情報を取得

$ curl -XPOST http://localhost:8080/{ジョブ名}

でジョブを実行することができます。また$worker->httpServer->setAPIKey()を使えば、文字列一致だけで判定する簡易な認証機能をつけられます。 ちなみに、私は以下の様な設定でherokuのPHPスタック上で動かしています。

<?php
require_once __DIR__.'/vendor/autoload.php';
date_default_timezone_set('Asia/Tokyo');

use Kohkimakimoto\Worker\Worker;

$worker = new Worker();
// herokuはhttpのportを動的に割り当てるので環境変数から取得する
$worker->httpServer->listen(getenv('PORT'));
// apiにアクセスするときのキー
$worker->httpServer->setAPIKey('vjdioaewdg49q3tg...');
// 5分毎にメモリ使用量などをログに出力する設定
$worker->stats->on();

$worker->job("hoge", ['cron_time' => '* * * * *', 'max_processes' => 1, 'command' => function(){
    // ...
}]);

$worker->job("foo", ['cron_time' => '* * * * *', 'max_processes' => 1, 'command' => function(){
    // ...
}]);

// 30分に一回dynoをwebからアクセスしてスリープさせないようにする。
$worker->job("wakeup_dyno", ['cron_time' => '*/30 * * * *', 'max_processes' => 1, 'command' => function(){
    file_get_contents("https://my_worker_xxx.herokuapp.com/?apiKey=vjdioaewdg49q3tg...");
}]);

$worker->start();

とりあえず、1日動かしっぱなしにしてみたが問題はなさそうです。

PHP5.5+OPcacheでシンボリックリンクでデプロイするとキャッシュが消えない

という問題にぶち当たりました。 どういうことかを話す前にPHPアプリケーションの個人的なデプロイ構成について説明します。 要はCapistranoのデプロイと同様なのですが、デプロイ先は以下のようなディレクトリ構成になっています。

.
|-- current -> /path/to/releases/20141011000001
|-- releases
|   |-- 20141011000000
|   `-- 20141011000001

currentアプリケーションサーバ(apachephp-fpm)から参照される箇所で、releases配下の最新のアプリケーションへのシンボリックリンクになっている。新しくアプリをデプロイするとreleases配下に20141011000002のようなタイムスタンプで新しくディレクトリが作成され、そこにアプリケーションのコードが配置される。その後currentシンボリックリンクが新しいアプリケーションへと切り替えられる。これによってダウンタイムなしでPHPアプリのコードをデプロイできます。

OPcacheのキャッシュ

ところが、PHP5.5+php-fpm+OPcacheという環境でこの構成のデプロイをためしたところ、実行中のアプリケーションにデプロイコードが反映されないという問題が生じました。 (※OPcacheはPHP5.5の新しいコードキャッシュシステムでPHP5.4以前のAPCに替わるものです)

調査していたら以下の解説記事を発見しました。

OpCache and symlink-based deployments

要約すると

  • OPcacheはAPCとはファイルのキャッシュの仕方が違う。
  • OPcacheはシンボリックを解決して、実ファイルパスの状態でキャッシュする。
  • よってシンボリックリンクを更新しても、実ファイルパスのキャッシュが保持されてしまう。

というわけだそうです。(このへんをちゃんとPHPのソース読んで、自分で裏付けとれるといいと思うのですが。。。技術力の低さが露呈しますね。。。(ー_ー;))

上記の解説記事では対応方法もいくつか提示されていて

  • webサーバの設定を直接書き換える(シンボリックリンクの代わりにweb(AP)サーバが参照するパスを直接書き換える)
  • OPcacheをリセットする
  • php-fpmを再起動する(多少のダウンタイムあり)

ansibleやpuppetを使っていればデプロイでwebサーバの設定を書き換えるのは簡単かも、と言及されています。 ただ、シンボリックリンクを使い続けたいならキャッシュをリセットするか、php-fpmを再起動する。 OPcacheはキャッシュをリセットする関数opcache_resetをもっているので、 これを使用した対応方法も(Laravelを使った例で)示されています。

さて、自分はどーしようか悩み中。 デプロイタスクがちょっとだけど複雑になるのと、コードキャッシュはアプリケーションより一段下の技術レイヤと考えているので、 それのリセット処理をアプリケーションのコードに直接入れるような対応はやりたくない。

この際、最近盛り上がってきているdockerでBlue-Green Deployment的なことをやってしまうのもアリかなと考えています。

第77回PHP勉強会でAltaxをとりあげていだだきました

4/28にEngineYardさんで行われた第77回PHP勉強会で、私がオープンソースで作っているAltaxというPHPデプロイツールをとりあげていただきました。とっても嬉しかったのでブログに書いちゃいます。PHP勉強会についての詳細はリンク先を参照ください。

第77回PHP勉強会が開催されました

セッションの内容が動画でアップされてあったので先日拝見させていただきました。 登壇者の木村さんには丁寧な資料を作っていただき、デモまでして頂いて感謝の限りです。

第77回 PHP勉強会で発表してきました : ぐりぺん

自分が作っているプログラムが衆人の前で実行される場面を見るというのは、なんだか緊張と気恥ずかしさみたいなものがあって、動画を再生しているPCの前でニヤニヤ、もじもじしてしまいました。俺きめぇ。

さて、セッション内であがっていた話題などについて、いくつか言及させていただきたいと思います。

利用実績について

Altaxの利用実績があるのかという質問に対して、登壇者の木村さんも「......う〜ん(笑」と苦笑いしておりましたが、私も聞いたことがありません(笑。ごめんなさい。 あ、でも「作者は使っているハズ」という件について、はYESと答えられます。 私自身は自分が仕事で開発、運用しているWebアプリケーションのデプロイに使っています。 使い方はデプロイ用の作業サーバがあってそこでAltaxを実行してます。処理としては

という一連の作業を自動化しています。

Githubで公開しているライブラリについて

セッション中、私がGithubで公開している他のPHPライブラリについても取り上げていただきました。 いくつか概要と私個人の利用内容について記載します。

kohkimakimoto/BackgroundProcess

これはPHPのWebアプリから非同期にコマンドを実行させるためのライブラリで、ここ にも書いたように、CSVファイルなどから一括で大量データをDBに投入するコマンドとWebインターフェースの連携に実際に使っています。 実はエラー処理まわりが不十分でその辺を修正してアップデートさせたいと思っているのですが、手がまわっていません。

kohkimakimoto/phpmigrate

MySQL用のDBマイグレーションツールです。PHPファイル一つで動くシンプルなツールで、後継のライブラリにkohkimakimoto/lib-migrationがあります。こちらはcomposerからのインストールに対応しています。 両方ともPHPクラス内に記述した素のSQLスキーマ変更を行います。 これらのツールはこれまで素のSQLファイルと手作業でDB変更を行ってきていた古い既存システムがあって、そこにDBマイグレーションを導入する際に作ったという経緯があります。そのため今までのSQLをそのまま使いたかったのと、スキーマ定義をあまり抽象化した記述で行いたくなかったので、こういう設計のツールになりました。詳しくはここに書いてあります。

システムメンテの改善と小さなツール

Altaxもそうなのですが、これらのツールは私が実際に仕事で関わっている「3、4年まえからメンテし続けているちょっと古いシステム」の運用を改善するために作ったものです。このようなシステムは「まるっとすべてのリプレースを要するほど非効率な状態にあるわけでもないが、モダンなフレームワークなどにはある便利機能がなくて不満」という状況にあります。そこを少しずつ埋めるために開発しました。

おわりに

Altaxにはなぜか海外の開発者が多くGithubのスターをつけてくれていて、たまーにプルリクエストをくれるのも海外の方が多いです。うまく書けている自信は全然ないのですが英語でドキュメントを書いていたのがよかったのかもしれない、と個人的には思っています。 Altaxの初期バージョンはOSのSSHコマンドをPHPプログラムから実行するただのラッパーでした。改めてこれまでの開発を見直すと、なかなか成長したなあと思え、ちょっと感慨深いです。

今回、PHP勉強会で取り上げられたことはまったく予期していなかったので本当に驚いて、そしてこのように自分の作っているソフトウェアに好意的な反応をいただけるのはとても嬉しいことだな、と思いました。

コード上の利便性からオブジェクト指向を考える

今回はオブジェクト指向について書きたいと思います。 私がオブジェクト指向を初めて学んだのは8年くらい前で、そのときはJavaでコードを書いていました。 クラス、オブジェクト、継承、ポリモーフィズムなどは 「個々の犬オブジェクト(ポチ、シロとか)があって、それは犬クラスから生成されて、犬クラスと猫クラスは、 ほ乳類クラスを継承していて両方とも鳴くメソッドを持っていて、犬はワンとなくけど、猫はニャンとなく」とか本で説明されていたのを読んで 「これがオブジェクト指向か! なるほど(キリッ」 とか理解した気になって、おかしなコード書いて 「あれ? やっぱオレよくわかってなくね?」 とか思ったものです。

そして間違ったコードをその後も幾度となく書いて何度も書き直して試行錯誤して、 改めて本を読み直したりして、今現在なんとかわかった気になっています。 よいコードと悪いコードをいっぱい書いてきて頭の中で知識が整理されてからだと、 前述のような犬の比喩や抽象的な説明もわかるようになります。 整理された知識が理論で支えられているような、理解に安心感が得られます。

比喩や抽象的な説明はしない

この記事は以下の方針で書かれています。

  • なるべく「抽象的」「比喩的」な説明はしない。
  • コード上の「利便性」に注目する。

これはオブジェクト指向における、比喩や抽象的な話を否定するものではありません。 でもそれだけだと、理解するのがやっぱり難しいのではないかと思っています。 そこで別の視点、もっとコードよりで結局オブジェクト指向って何が便利なの?という視点から 解説してみてはどうかなと思って書いてみました。 何はともあれ、オブジェクト指向がプログラミングで使われているのは、それが便利なのだからです。

なお、説明のためのサンプルコードはPHPを使うものとします。では始めましょう。

クラス - 関数をまとめる

クラスはオブジェクトを生成するひな形、インスタンスの設計図などと説明されることが多いですが、 ここではそのような側面は取り上げません。まず理解すべきことは以下の性質です。

クラスは関連する処理(関数)をまとめる

たとえば文字列に関する関数を以下のようにまとめておくことができます。

// 文字列関連の関数をまとめたクラス
class StringUtils
{
    // 空白文字を削除する
    public function removeBlank($str)
    {
        return str_replace(array(" ", "\t", "\n", "\r"), $str);
    }

    // 先頭n文字を切り出す
    public function head($str)
    {
        return substr($str, 0, $n);
    }
}

// 以下のように使う
$strutils = new StringUtils();
$str1 = $strutils->removeBlank("あ い うえお");
$str2 = $strutils->head("あいうえお", 2);

関連する処理、種類が同じ処理はまとまって記述してあった方が、コードが理解しやすいですよね。 これがクラスの「利便性」です。クラス内に定義した関数をメソッドと呼びます。これは名前が変わっただけです。 ただのクラス内でまとめられた関数でしかありません。

これをオブジェクト指向を使わないで書くと以下のようになります。

// string_utils.php

// 空白文字を削除する
function removeBlank($str)
{
    return str_replace(array(" ", "\t", "\n", "\r"), $str);
}

// 先頭n文字を切り出す
function head($str, $n)
{
    return substr($str, 0, $n);
}

$str1 = removeBlank("あ い うえお");
$str2 = head("あいうえお", 2);

ただ関数を一つのファイルに並べただけです。クラスにまとめたときとあまり変わらないですか? むしろオブジェクト指向なコードはnew StringUtils()とかやっていて手間が増えていて解りづらいような。。。 そもそもnewって何をやってるんですかね。すぐにわかります。でも、今は先に進みましょう。

クラス - 変数をまとめる

クラスは関数をまとめておけると述べました。同様に関連する変数(データ)もまとめられます。

// 「人」クラス
class Person
{
    public $name;

    public $height;

    public $weight;
}

これは人を表すクラスですが、名前($name)、身長($height)、体重($weight)と人に関する変数をまとめています。 ちなみにクラスにまとめられた変数をプロパティといいますが、これも名前が変わっただけです。 変数をまとめると何が便利なんでしょうか? 関数をまとめた時と同様にコードが理解しやすくなります。 まず$name変数が格納しているデータが人の名前であること(何かほかの動物の名前などではなく)がコード上でもはっきりわかります。 また、このプログラムが扱う「人」のデータの内容がクラス内にリストアップされていて、わかりやすいです。 それに、データを一つにまとめることで関数の引数にデータを渡すときも一回ですみます。

// クラスでまとめられた変数に値を代入
$v = new Person();
$v->name = "鈴木";  // name変数に代入
$v->height = 173;    // height変数に代入
$v->weight = 60;     // weight変数に代入

// ユーザのデータを(DBなどに)保存する処理。
// いちいち名前と身長、体重を別々に渡す必要がないので見やすい!
save_person($v);

変数$vはvalueの略でこの場合意味はありません。->はクラスでまとめた変数、関数にアクセスするための記号です。 ところで、また出てきたこのnewは何でしょう?

インスタンス - データ構造にあわせた入れ物を作る

PHPのドキュメントには以下のように書かれています。

あるクラスのインスタンスを生成するには、new キーワードを使わなければなりません。

newはクラスのインスタンスを生成しているらしいです。 クラスのインスタンス(以下単にインスタンス)とはなんでしょうか?

先ほどクラスは「変数をまとめる」と書きました。これをもうちょっと掘り下げて言い換えましょう。 クラスは変数をまとめて新しいデータ構造を作ります。 データ構造はデータ型ともよばれます。 ここでのポイントはクラスはデータそのものではなく、データの構造を作っているという点です。 例示しましょう。データ構造の例としては配列がいいです。 以下はPHPが最初から用意している配列型データ構造とそれにデータを代入している例です。

// 配列型データ構造
$v = array();
$v[0] = "aaa";
$v[1] = "bbb";
$v[2] = "ccc";

配列は複数のデータを順番にならべて格納できるデータ構造ですね。 上記のコードでは$v = array();で空の配列で変数を作っています。その後実際のデータaaa,bbb,cccをその構造にそって代入しています。

さて、前述のPersonクラスのコードを再掲してみましょう。ちょっと配列と操作が似てませんか?

// クラスのインスタンス化
$v = new Person();
$v->name = "鈴木";
$v->height = 173;
$V->weight = 60;

配列の場合もクラスの場合も、やっていることは

  • 指定したデータ構造の変数をつくる。
  • 変数の構造にあわせてデータを入れる。

ということをやっています。前述したデータに関する言葉をそれぞれのコードに当てはめてみましょう。

  • データ構造:
    • 配列の例:PHPが用意している配列という仕組み。
    • クラスの例:プログラマが定義したPersonクラス。
  • データそのもの:
    • 配列の例:$vに代入されているaaa,bbb,ccc
    • クラスの例:$vに代入されている鈴木,173,60

インスタンスとはクラスという新しいデータ構造から作られたデータの入れ物なのです。

オブジェクト

オブジェクト指向」の「オブジェクト」について語るときがきました。 まず、言葉としてオブジェクトとインスタンスは基本的に同議です。おなじものなのです。 文脈によってインスタンスといわれることが多かったり、オブジェクトといわれることが多かったりしますが、 ほとんどの場合どっちを使ってもいいです。 例えば前述のnewキーワードの説明の

あるクラスのインスタンスを生成するには、new キーワードを使わなければなりません。

あるクラスのオブジェクトを生成するには、new キーワードを使わなければなりません。

でも全く問題なく、意味が通じます。それでは先に進みましょう。

オブジェクト - データと関数をまとめる

ここまでクラスの利便性を二つ述べました。

  • 関数をまとめられる
  • 変数をまとめて新しいデータ構造を作れる

実はこの二つの利便性は組み合わせることで真価を発揮します。以下にコードを例示します。

// 「人」クラス
class Person
{
    public $name;

    public $height;

    public $weight;

    // 身長、体重からBMIを計算する処理
    public function calcurateBmi()
    {
        return ($this->weight / ($this->height / 100 * $this->height / 100));
    }
}

// 以下のように使う
$v = new Person();
$v->name = "鈴木";
$v->height = 173;
$V->weight = 60;
// BMIを計算して表示する
echo $v->calcurateBmi();

これは身長$heightと体重$weight変数からBMIを計算しています。 これの利便性はやっぱりまとまっていると理解しやすいということです。 またcalcurateBmiメソッドが必要とするデータ(身長と体重)は同じクラス内にまとめられてあるので、 処理を呼び出す際データを間違えることがありません。

比較のために同じことをオブジェクト指向を使わずに書いてみましょう。

// 身長、体重からBMIを計算する処理
function calcuratePersonBmi($weight, $height)
{
    return ($weight / ($height / 100 * $height / 100));
}

$personName = "鈴木";
$personHeight = 173;
$personWeight = 60;

// BMIを計算して表示する
echo calcuratePersonBmi($personWeight, $personHeight);

calcuratePersonBmi関数に注目しましょう。 オブジェクト指向で書いたときは、オブジェクトが内部に保持しているデータを使って計算処理をしています。 一方オブジェクト指向でない(手続き型といいます)ときは、データは引数で関数の外側から渡されています。

たいした違いではありませんか? しかしcalcuratePersonBmiの実行するとき、プログラマが引数の正しい順番(体重、身長の順)を意識して関数を呼び出さなければなりません。 それよりもあらかじめ、体重、身長をまとめたデータ構造のオブジェクトを作っておいて、あとは単純に calcurateBmiメソッドを引数なしで呼び出した方がちょっぴりシンプルで、間違えにくいと思いませんか? これは非常に単純な例で、些細な比較ですが、オブジェクト指向プログラミングの重要な性質が現れています。つまり

データと処理をまとめることで、処理の呼び出し部分をちょっとシンプルにできる

ということです。この「呼び出し部分」はAPIなどとも言われます。 APIがシンプルであれば処理の内容が理解がしやすく、ほかのプログラムからもこれが使いやすくなります。

カプセル化

オブジェクト指向の利便性をもっと高めるため「制約」の話をしましょう。 「制約」は何かを制限するという意味の言葉なので「利便性」とは逆の概念に思えますが、プログラミングの世界では「制約」はよい影響をもたらすためによく使われます。

「人」クラスで例示してみましょう。

// 「人」クラス
class Person
{
    protected $name;

    protected $height;

    protected $weight;

    public function __construct($name, $height, $weight)
    {
        $this->name = $name;
        $this->height = $height;
        $this->weight = $weight;
    }

    // 身長、体重からBMIを計算する処理
    public function calcurateBmi()
    {
        return ($this->weight / ($this->height / 100 * $this->height / 100));
    }
}

// 以下のように使う
$v = new Person("鈴木", 173, 60);
// BMIを計算して表示する
echo $v->calcurateBmi();

前回までと変わったところは、プロパティの属性がpublicからprotectedに変更され、__constructメソッドが追加された点です。

protectedから説明しましょう。これはオブジェクトのプロパティへのアクセスを外部から禁止します。 つまり以下のようなコードが書けなくなります。

$v = new Person();
$v->name = "鈴木"; // これはエラーになる。->でプロパティにアクセスできなくなる。

__constructコンストラクタとよばれるメソッドで、newによるオブジェクト生成時に呼び出され オブジェクトの初期処理を行います。上記の例では引数で渡した$name,$height,$weight でプロパティに初期値を設定しています。 あわせるとこのクラス(正確にはそこから生成されたオブジェクト)には

プロパティの内容はオブジェクト生成時に設定され、以後変えることができない

という制約がつけられます。このような制約をカプセル化と呼びます。これの何が便利なのでしょうか?

間違いを予防できるというのが答えです。間違ってデータを変更、削除してしまうのをさけられます。 最初に設定したら変えることができないのだから当然ですね。

Personクラスの例でいうとcalcurateBmiメソッドが正常に動作するにはプロパティの身長$height、体重$weightが設定されている必要があります。 自由にプロパティの変更ができてしまうと、これらのプロパティに値が入ってないという状況が発生してしまうかもしれません。 しかしprotected属性でアクセスを制限しているので、コンストラクタで初期値を確実に入れさえすればその後、変更、削除されることはなくcalcurateBmiは間違いなく動くということが保証されます。

プログラミングにおいて間違いをおこさせない仕組みで作るというのは設計上の基本戦略です。

ついでにもうちょっと、Personクラスを拡張してみましょう。

// 「人」クラス
class Person
{
    protected $name;

    protected $height;

    protected $weight;

    public function __construct($name, $height, $weight)
    {
        $this->name = $name;
        $this->height = $height;
        $this->weight = $weight;
    }

    // 身長、体重からBMIを計算する処理
    public function calcurateBmi()
    {
        return ($this->weight / ($this->height / 100 * $this->height / 100));
    }

    // 名前を取得
    public function getName()
    {
        return $this->name;
    }

    // 身長を取得
    public function getHeight()
    {
        return $this->height;
    }

    // 体重を取得
    public function getWeight()
    {
        return $this->weight;
    }
}

// 以下のように使う
$v = new Person("鈴木", 173, 60);
// 名前を表示する
echo $v->getName();

メソッドを三つ追加しました。getNamegetHeightgetWeightです。 このメソッドによって人オブジェクトからそれぞれ名前、身長、体重のデータを外部から取得できるようになりました。 でも、依然データの更新、削除はprotectedによって禁止されています。 言い方をかえると変数と関数の組み合わせによって制約の一部を緩めることができるということです。

オブジェクトにどのような制約をつけるかは、あなたが実際に作るシステムの要件によります。 たとえば、人の名前を画面に表示する必要がある場合は、人オブジェクトから名前データをとってこなくてはならないので、 今回のように取得できるように制約を緩めます。

適切な設計へのポイント

オブジェクト指向の利便性を述べてきましたが、この利便性は適切に設計しなければその恩恵を受けられません。 オブジェクト指向でプログラミングする場合、従来の手続き型プログラミングの構造化に意識が行き過ぎていると設計に失敗します。 手続き型プログラミングだと処理を適切な粒度に切り出し関数として実装し、その関数に必要なデータを引数で渡す、というようにデータを処理というフィルターにかけるような意識で設計します。一方オブジェクト指向の場合データを適切な粒度にまとめて、そのデータを必要とする処理をくっつけるという意識で設計します。

クラスに変数と関数をまとめるとき「本当にこの場所(=クラス)に実装するのが適切か」と考える必要があります。 あわせて制約も注意深く考えましょう。特にオブジェクト内のデータを後から書き換えられるように制約を緩めるときは注意です。 不適切な変数や関数がクラス内にまとめられているかもしれません。

今回説明していないこと

クラスメソッドやクラス変数、継承やポリモーフィズムについてはまた後日。。。書くかも。

Composerがパッケージのstabilityを解決するしくみ

PHPとComposerで先日composer/composerdev-masterに依存したプログラムを作っていたら、composer installのときに以下のようなエラーがでてインストールできない問題にぶちあたりました。

Your requirements could not be resolved to an installable set of packages.

Problem 1
    - kohkimakimoto/altax v3.0.6 requires composer/composer dev-master -> no matching package found.
    ...

解決方法はcomposer.json"minimum-stability": "dev""prefer-stable": trueを指定するか、対象のパッケージに"composer/composer": "@dev"のようなstabilityフラグをつければOKでした。

さて、この件に関連する日本語情報があまりなかったのですが、 根本の仕組み(Composerがパッケージのstabilityを判断してダウンロードする仕組み)を丁寧に書いたブログをみつけたので、以下和訳してみました。

Composerのスタビリティフラグ

原題:Composer Stability Flags

https://igor.io/2013/02/07/composer-stability-flags.html

今のところcomposerのサポートにやってくる最もよくある問い合わせは、スタビリティ(パッケージの安定性、stability)がどう解決されるのかよくわからないというものだ。 よくこのケースは以下のような問い合わせになる。

パッケージB:dev-masterに依存するパッケージA:dev-masterを要求(require)したら、composerがパッケージBが見つからないというんだ。

ルートパッケージ

ルートパッケージはメインのcomposer.jsonファイルのことだ。これはcomposer installを実行するときにいるディレクトリと同じディレクトリ内にある。 多くのcomposer.jsonのフィールドはルートオンリーで、これはルートパッケージ内で指定されたときだけ影響をもつということだ。

ルートパッケージはコンテキストだ。 あなたが自分のパッケージのディレクトリ内でパッケージAに依存しているといった場合、あなたのパッケージがルートパッケージとなる。 パッケージAのディレクトリにcdしたらAがルートパッケージだ。

スタビリティはルートパッケージで決定される。そしてルートパッケージのみで決定される。 これを忘れないようにして、ちょっと考えてみよう。

Composerはユーザの手にわたる依存物がどのようなスタビリティか判断をする。 ユーザとしてあなたは開発版、ベータ、または安定版のリリースを使いたいか決める。

最低限のスタビリティ(minimum-stability)

このスタビリティの判断はルートパッケージのminimum-stabilityフィールドに基づいて行われる。これはルートオンリーだ。スタビリティフラグのデフォルト値を定義し、下限として振る舞う。

f:id:kohkimakimoto:20140403113456p:plain

これは引き下げることのできるルーラーで、デフォルトは"stable"をさしている。 しかし引き下げると、より低いスタビリティフラグを示すことができる。

minimum-stabilityはすべての制約のためのデフォルトの安定性を定義する。

スタビリティの解決

それでは、つぎのようなシナリオを考えてみよう。ルートパッケージがA:dev- masterを要求していて、 それがさらにB:dev-masterを要求している場合だ。

f:id:kohkimakimoto:20140403114304p:plain

ルートパッケージは以下のようになる

{
    "require": {
        "A": "dev-master"
    }
}

Composerは以下のステップを踏む:

  • minimum-stabilityを決定:このケースではフィールドが定義されていないのでデフォルト値が設定される。これは"stable"だ。
  • Adev-masterというバージョン制約をもっている。dev-プリフィクスがついているので、これはdevバージョンであることがわかる。そしてdevバージョンは"dev"スタビリティをもっている。ルートパッケージで定義されたこのdevバージョン制約のため、暗黙的に@devスタビリティフラグを得る。
  • Aは制約A:dev-master@devをもっているので、このバージョンはマッチしてcomposerはリンクする。ABdev-masterという制約つきで依存している。これはdev-プリフィクスを持っている、よって"dev"スタビリティをもっている。

    ところが、この制約はパッケージAの中で定義されていて、かつルートパッケージではないので、暗黙的に@devスタビリティフラグを得ることはできない。その代わりにminimum-stabilityを継承する。これは"stable"だ。よって、解決される制約はB:dev-master@stableとなる。

ここが障害のポイントだ。なぜならB:dev-master@stableはどうやっても解決できないからだ。composerは与えられたスタビリティの範囲でパッケージBが見つからないとこたえるのだ。

この問題に対処する方法の一つはminimum-stabilityを"dev"に下げることだ。 しかしこれは通常とてもよくないアイデアだ。これはすべての制約に適用されてしまい、 その結果、すべてのパッケージを不安定なバージョンで取得してしまう。

だからお願いだ。それをしないでくれ。

スタビリティフラグ

代わりにスタビリティフラグを使おう。

フラグはバージョン制約の一部として定義される。スタビリティはルートパッケージでのみで決定されるので、フラグもまたルートオンリーだ。 依存パッケージ内で定義されたフラグは単純に無視される。

フラグは不安定版パッケージを指定するホワイトリストとして使うことができる。このケースにおいて、私はBホワイトリストに追加したい。このようにする:

{
    "require": {
        "A": "dev-master",
        "B": "@dev"
    }
}

注目すべきなのは、実際のバージョンをルートパッケージ内で定義していない点だ。 これはルートパッケージはインストールされるBのバージョンを扱わないということを意味する。 バージョンの決定は指定する制約をもっているAに委譲している。

これによって、もしABへの依存をdev-masterから~1.0またはそれ以外に変更したとしても、ルートパッケージはなにも変更する必要がなくなる。

Silexの例

この動作が実際にどのようなものか、よりアイデアを得るために、silexを例に見てみよう。

これを書いている現時点で、silexは安定バージョンがない(訳者注:今は1.2の安定バージョンがありますね)。インストールするために@devフラグを追加する必要がある:

{
    "require": {
        "silex/silex": "1.0.*@dev"
    }
}

Silexは1.0の開発バージョンである1.0.x-devバージョンのみがある。

Silexのすべての依存物は安定バージョンがある。これはデフォルトでv2.1.7の多数のsymfonyコンポーネントv1.0.1のpimpleを得るということだ。

もし数日前にリリースされたsymfonyコンポーネントv2.2.0-RC1バージョンを試したくなった場合、 それを以下のようにホワイトリストにすることができる:

{
    "require": {
        "silex/silex": "1.0.*@dev",
        "symfony/event-dispatcher": "@RC",
        "symfony/http-foundation": "@RC",
        "symfony/http-kernel": "@RC",
        "symfony/routing": "@RC"
    }
}

バージョンをすべて指定するのは面倒なので、minimum-stabilityを下げることもできる。 この場合それはOKだ。あなたが望まない不安定バージョンがインストールされることがないからだ。

{
    "minimum-stability": "RC",
    "require": {
        "silex/silex": "1.0.*@dev"
    }
}

prefer-stable

このポストを書いてしばらくした後、composerはprefer-stableという機能をリリースした。 もし、依存物のスタビリティを把握したくない場合、単にprefer-stableフィールドをルートパッケージで使うことができる。 Composerは可能なもので最も安定した依存物を導きだす。

これはとても便利でほとんどの場合それで十分だ。しかし、私はあなたが本当に必要としているスタビリティを考えることをお勧めする。明示的にそれを定義することもね。あなたは便利さと制御をトレードしてるかもしれない。

結論

composerがどのようにスタビリティを解決しているか。不安定バージョンを取得するためにスタビリティフラグどのように使うことができるか。この記事があなたのよりよい理解に役立てば、幸いだ。

でも覚えておいてくれ:スタビリティフラグを必要とするもっともよくある理由は、あなたの依存物のメンテナが安定板をタグ付けしない理由によるものだ。彼らにブランチエイリアスの追加とタグリリースをさせるため、いってやってくれ。彼らがそれを行ったらすぐに、スタビリティフラグを捨てて、またハッピーになれるよ。

元記事はこちら

PHPデプロイツールのAltaxのバージョン3をリリースしました。

去年10月位にバージョン2に書き直して、今回さらにまるっと書き直しました。 タスクの記述方法などもごっそり変えてしまったので、すでに使っている人は今までの設定ファイルは使えないので注意してください。

https://github.com/kohkimakimoto/altax

ドキュメントはこちら

http://kohkimakimoto.github.io/altax/ja/

一応英語圏のひとにも使ってもらえるようにドキュメントは英語でも書いています。正直、英語苦手なので変な英語だったら誰かツッコミいれてください。

概要

PHPでタスクが記述できるCapistranoみたいなものです。 SSHでリモートサーバに対してコマンドを並列実行します。 またpharでソースを全部まとめて配布しているので、動作に必要なのはPHPaltax.pharだけなので手軽につかってもらえると思います。

使い方

チュートリアルを書きました。

http://kohkimakimoto.github.io/altax/ja/tutorial/

タスク定義の例をあげると、Gitからデプロイする簡単なタスクは以下のように定義します。

Server::node("web1.example.com", "web");
Server::node("web2.example.com", "web");

Task::register("deploy", function($task){

    $appDir = "/path/to/app";

    // Execute parallel processes for each nodes.
    $task->exec(function($process) use ($appDir){

        // Run a command remotely and get a return code.
        if ($process->run("test -d $appDir")->isFailed()) {
            $process->run("git clone git@github.com:path/to/app.git $appDir");
        } else {
            $process->run(array(
                "cd $appDir",
                "git pull",
                ));
        }

    }, array("web"));

});

バージョンアップにあたって

タスクをLaravelのRouting定義ぽい記述方法にかえました。

実行コマンドのリターンコードや、出力内容を取得できるようにしました。

リモートホストに対してのファイルダウンロードやアップロードに対応しました。

プラグインでタスクを拡張できるようになりました。

プラグイン

プラグインで機能拡張をできるようにしました。 サンプルにAdminerというMySQL管理ツールをさくっと使えるようになるプラグインも同時に作って公開したので、 ご興味のあるかたはどうぞ。

http://kohkimakimoto.github.io/altax/ja/docs/plugins-introduction.html

このプラグインのしくみなんですが、元ネタというか影響をうけたのがGruntのプラグインの仕組みだったりします。 Gruntはプラグインのインストールをnpmをつかってやって、Gruntfile.jsに設定を書くという構成ですが、 AltaxはPHPのツールなのでComposerでプラグインをインストール、PHPの設定ファイルに設定を書く、という構成にしています。

個人的に今はリモートサーバにたいしてchefリポジトリをgitからcloneしてchef-soloをするという、 自動化タスクをプラグインで書いてみたりしています。

https://github.com/kohkimakimoto/altax-chef

開発とかテストの環境

最近メインPCをWindowsからMacにかえました。 だだし開発自体はVirtualBoxで構成したCentOS6上でやってます。そういうわけで、 プログラム動作は主にOSXCentOSで検証しています。あとCIにTravisを使っています。

なおMacに入っているデフォルトのPHPだとpcntlモジュールが入っていないので、 並列処理の際にプロセスをforkする処理が動かないので、この場合は並列動作させずに、 順次実行するようになっています。