PHP懇親会に参加しました

3/16に開催された第2回PHP懇親会に参加しました。新宿ファンデリで美味しいご飯を食べながら、参加者全員でライトニングトークをする集まりの2回目です。

今回は参加者30名で、一人の持ち時間が5分、5時間に渡る長丁場でしたが、終わってみればあっという間に時間が過ぎてしまう楽しいイベントでした。


私はといえば、最近internals MLでホットな話題のTraitsについて簡単に紹介しました。

TraitsはPHPでmix-inを実現する言語仕様の拡張です。ezComponents/ezPublishなどの開発に参加しているStefan Marr氏が、internals MLに提案したもので、PHP-5.2, 5.3へのパッチがあります。

詳しく知りたい人は以下のURLを参照してください。

PHP5の例外機能の使い方

  • PHP5ってどうするんだ
    • PHP5になると美味しいことたくさん
    • 色々組み込まれてる。XMLもDBも美味しい。
    • 例外処理よくわからないから誰か教えて
やめだやめだ!Ethnaでいくぞ! - 肉とビールとパンケーキ by @sotarok

PHP5の例外機能をどう使えばいいかよく分からないという声をちらほら聞くので、チュートリアルぽく例外の説明をしたいと思います。

従来のエラー処理

まずは従来の一般的なエラー処理のやり方から。
例外機能のない従来のPHP4プログラミングでは、ある関数の中でエラーを返したい場合は、そのエラーを表す何がしかの値を関数の戻り値として返すことになります。

そのエラーを表す値を毎回決めるのは面倒なので、PEARのエラー処理スキームを利用することが広く行われています。その場合エラーが起きたらPEAR_Errorオブジェクトが返されることになります。

<?php
require_once 'PEAR.php';

function func() {
    // もしエラーだったら
    return PEAR::raiseError('Error occurred');
}

$result = func();
if (PEAR::isError($result)) {
    die($result->getMessage());
}
?>

上記を見て分かるように、エラーは関数の戻り値でのみ示すことになりますので、ちゃんとしたプログラムの場合は関数呼出しの度にエラーチェックをすることになります。また関数呼び出しのボトムで起こったエラーをトップまで伝えるには、生成したエラーオブジェクトを順繰りにreturnする必要があります。

<?php
require_once 'PEAR.php';

function func_sub1() {
    return PEAR::raiseError('func_sub1 error occurred');
}

function func_sub2() {
    return PEAR::raiseError('func_sub2 error occurred');
}

function func_sub3() {
    return PEAR::raiseError('func_sub3 error occurred');
}

function func() {
    $result = func_sub1();
    if (PEAR::isError($result)) {
        return $result;
    }
    $result = func_sub2();
    if (PEAR::isError($result)) {
        return $result;
    }
    $result = func_sub3();
    if (PEAR::isError($result)) {
        return $result;
    }
}

$result = func();
if (PEAR::isError($result)) {
    die($result->getMessage());
}
?>

関数func()では、関数を3つ呼ぶだけなのに毎回エラーチェックが必要になり、面倒な上に、なにより見にくいコードになってしまいます。

PHP5の例外を使ってみる

そこでエラーを例外を使って処理してみましょう。
まずは例外の基本的な使い方から。

<?php
function func() {
    // もしエラーが起きたら例外をthrow
    throw new Exception('Error occurred');
}

try {
    func();
} catch (Exception $e) {
    die($e->getMessage());
}
?>

PHPの例外機能はC++Javaなどと同様に、try ブロックで発生した例外をcatchで受け取るという文法になっています。

throwできるものはオブジェクトに限られ、PHPではExceptionクラスかそのサブクラスのオブジェクトをthrowする慣習になっています。

では先ほどの例を例外を使って書き直してみます。

<?php
function func_sub1() {
    throw new Exception('func_sub1 error occurred');
}

function func_sub2() {
    throw new Exception('func_sub2 error occurred');
}

function func_sub3() {
    throw new Exception('func_sub3 error occurred');
}

function func() {
    func_sub1();
    func_sub2();
    func_sub3();
}

try {
    func();
} catch (Exception $e) {
    die($e->getMessage());
}
?>

この例で分かるように、例外なしでは毎回エラーチェックをしていた関数func()がシンプルに関数を呼ぶだけになりました。func_sub1()で発生した例外はfunc()を飛び越えて、最も発生元の呼び出しレベルに近い(この場合はトップレベル)catchブロックに直接届きます。

特定の例外のみ処理を変更する

tryブロックは入れ子にすることもできます。特定のエラー条件の場合だけ処理を変更したい場合は、その場所にtryブロックを作って対応したcatch内で処理を切り替えればOKです。

以下の例では関数func()にtryブロックを作り、func_sub1()で発生したエラーのみ無視しています。

<?php
define('ERR_SUB1', 1);
define('ERR_SUB2', 2);
define('ERR_SUB3', 3);

function func_sub1() {
    throw new Exception('func_sub1 error occurred', ERR_SUB1);
}

function func_sub2() {
    throw new Exception('func_sub2 error occurred', ERR_SUB2);
}

function func_sub3() {
    throw new Exception('func_sub3 error occurred', ERR_SUB3);
}

function func() {
    try {
        func_sub1();
        func_sub2();
        func_sub3();
    } catch (Exception $e) {
        if ($e->getCode() == ERR_SUB1) {
           // func_sub1()の例外だけ無視
        } else {
           // その他の場合は例外を投げ直す
           throw $e;
        }
    }
}

try {
    func();
} catch (Exception $e) {
    die($e->getMessage());
}
?>

他にもcatchブロックを受け取るクラスごとに複数並べたりできますが、よく使われる形は上記のいずれかのパターンだと思います。

第2回CakePHP勉強会の動画を今更公開

またもや今更公開シリーズ。

2008年2月20日に開催された第2回CakePHP勉強会に参加して動画を撮ってきたので公開します。

yandoさんのエントリに各発表スライドへのリンクがあります。

CakePHP 1.2 Email Component調査報告書 suzukiさん

http://video.google.com/videoplay?docid=9106988618756925482

独習CakePHP 〜最初の7日間の過ごし方〜 田口さん

  • ご本人の希望により動画は撮っていません。


あのオープンソースソフトウェアをCakePHPに移植する id:p4lifeさん

  • ご本人の希望により非公開。

PHPと(Perl|Python|Ruby)の違い

それは、PHPユーザーは他の言語から学んでいるのか、という点。

PHPユーザーが他の言語ユーザーよりも弱いのは、そこなのではないか。

404 Blog Not Found:「PHPなめんな」と「(Perl|Python|Ruby)をなめんな」の違い

弾さんから説得力のあるPHPへの批判記事が初めて出てきたので、それに対して自分なりに考えたことを記録に残しておきたくなりました。


つい先頃まで、PHP開発者のML internals@lists.php.net で起こっていた議論がちょうど良い例題に思えるので、まずそれを紹介しましょう。


id:rskyさんといえば、PHP拡張機能&Zend Engineハッカーとして日本のPHP界ではつとに知られている方ですが、昨年の11月末にPHPに無名関数を導入するZend Engineに対するパッチを作成されました。


Zend Engineをハックしてみた - 讃容日記


時を同じくしてちょうど internals でも無名関数導入の議論が行われていたのを知ったid:rskyさんは、自分のパッチをinternalsに提案します。


php.internals: Re: PATCH: Implementing closures in PHP


一部の人の肯定的な反響はあったものの、その後の議論は続いていない状態です。というのもid:rskyさんが投げたもうひとつのパッチ、

array(1, 2, 3)

[1, 2, 3]

のようにも記述できるようにするパッチが大激論を呼んでしまったので、無名関数+クロージャパッチの方は現在放置状態になっています。arrayパッチのほうも、逆に閲覧性が悪くなるという反対意見が案外多く、現在はこう着状態です。


それで、そのarrayパッチへの反応やそれ以前の無名関数の議論を読み直していて私が感じたことは、

言語としての利点が多いことが、すべての人にとって良いことではない。

ということです。


おそらくPHPの文化ではその辺のバランスを、現状を変えない方向に振るように、暗黙の圧力が働くようです。そこに独裁者は存在せず、ゆるい合議で物事が決まっていきます。実際に上記の例でも、PHPの作者として名前が挙がるRasmus Lerdorfはarrayパッチに対して賛成の意見を表明していますが、議論はこう着状態ののまま結論は出ていません。そのような圧力の中では、多くの人が明確に利点と思わなければ、ドラスティックな改善はなかなか受け入れられません。しかし逆に考えれば、一般の人から一見とっつきの悪い、プログラミングの難解な概念を習得しなくても、そこそこには使える言語として成熟していくことになります。


弾さんのように、プログラミング言語は欠点が少ないほど良いと考える「学び続けたい」人にとっては、PHPは物足りなさが炸裂している言語だというのはしっくり来ます。でもそれは目指す方向が違うだけで、どちらが悪いわけではないのではないかと私は思います。


もう一点。

PHPの「サクセスストーリー」をよく見ると、初心者が腕を磨いてというのではなくて、むしろ他の言語でならした手練がPHPのいいところだけを使ったというものが目立つ。ニコニコ動画なんてその最たるものだろう。そしてこういうユーザーがPHPに「再投資」するというケースはあまり目立たない。ここで言う「再投資」は、ある言語に助けられた人が、その言語に対して「利用」以上の貢献するということである。PerlにはCPANが、RubyにはGemsが「再投資の受け皿」としてある。それでも満足できない人は、開発者MLが待っている。Pythonはこの点がPerlRubyに比べて弱いように見受けられるが、それでもPHPほど「使いっぱなし」は目立たない。

404 Blog Not Found:「PHPなめんな」と「(Perl|Python|Ruby)をなめんな」の違い

この「PHPに「再投資」するケースはあまり目立たない」というのは、アプリケーションやサービスを開発した人が、PEARPECLなどにコードを公開していないとか、開発者MLに入って発言していないということを言いたいわけでしょうか?


それは認識が大きく私とは異なりますね。


CPANほどではありませんが、PEARPECLはソースリポジトリとしてはずいぶん大きなものに成長していますし、PEARプロトコルに準拠している野良リポジトリはそれこそ世界中にあります。すべてがphp.netに集中しているわけではありませんが、各自がおのおのの手段でソースを公開してコミュニティに貢献しているわけで、「再投資の受け皿」はふんだんに用意されています。PHPが「使いっぱなし」というのは少し違うでしょう。


今回の議論で、PHPの文化というものが自分の中にもようやく明確になってきた気がします。自分も個人的にはもっとストイックに利点を追求する文化の方が好みではあるので、その辺の楽しみは他の言語で追求することにしましょう。例えばActionScriptとか。

PostgreSQLのUNIXドメインソケットとcreoleのDSN

AMNの広告配信サーバを、レンタルサーバから、ハウジングスペースの自前サーバに移行作業をしていますが、今回ようやくWeb1台+DB1台の構成になりました。(冗長性?なにそ(ry)


ついでにpgpoolを間に入れて、PHP(Apache)からはUNIXドメインソケットで接続するようにしました。DSNは設定ファイルに書いておいてそれをPHPプログラムから読み込むようになっていましたが、DB抽象化レイヤとして使用しているcreoleでは単一の文字列DSNからPostgreSQLUNIXドメインソケットによる接続はできないようです。


例えば以下のDSNがあったとして

pgsql://username:password@localhost/dbname

これはTCP接続でlocalhostにつなぎにいきます。PHPpg_connect関数はhostパラメータがなければ自動的にUNIXドメインソケットになりますが、じゃぁ

pgsql://username:password@/dbname

としてみても、creoleの中で呼び出しているparse_urlでやっぱりエラーになります。そらそうか。


ということで単一文字列のDSNをあきらめ、設定ファイルに

[db_dsn]
phptype = pgsql
username = hoge
password = fuga
port = 5432
database = moge

と記述して、これを読み込んだ配列をCreole::getConnection()に渡すようにしました。一部を抜き出すとこんな感じ。

<?php
        try {
            // string-type DSN
            $dsn = BM_Config::get('dsn', 'db');
        } catch (Exception $e) {
            if ($e->getCode() == BM_ERR_NO_CONFIG_NAME) {
                // array-type DSN
                $dsn = BM_Config::getCategory('db_dsn');
            }
        }
        $db = Creole::getConnection($dsn);

無事にUNIXドメインソケットでつながってはっぴー。

追記:コメントでid:tsukimiyaさんに教えてもらったとおり、

pgsql://user:pass@unix+localhost:port/dbname

でいけますね。ソースちゃんと読まんとあかんなー。

PHPカンファレンス2007 ライトニングトーク募集

現在、9月1日(土)に行われる「PHPカンファレンス2007」のセッションとして予定されている、ライトニングトークの発表者を募集しております。

http://www.php.gr.jp/seminar/20070901/lt_offer.php

PHPカンファレンスでもライトニングトーク発表者を募集中です。
私はこのイベントの運営側スタッフな訳ですが、今のところは発表者として出るわけではないので、何かネタを考えて応募したいと思っています。


現在のところ応募が少なく競争率が低いので、我こそはと思う方はどんどんご応募ください。

Zend Extensionとextension_dir

zend extension をロードする関数に渡ってきた文字列が絶対パスでない場合は、extension_dir を先頭に付加してやるという単純なパッチです。

ベイエリア情報局: Zend extension を extension_dir からロードできるようにしてみた

Zend/zend_extensions.cを直接いじるより、main/php_ini.cのphp_load_zend_extension_cb()をふがほげした方がきれいかなぁと思ったり。

あとZend Engineの中では、通常のextensionはmoduleと呼ばれるのに対して、zend extensionはextensionと呼ばれて明確に区別されています。追いかけてないけどたぶんzend extensionの方がEngineの低いレベルまでいじることができるのでしょう。

ロード時にextension_dirを見ないのは、勝手にやばいモジュールを突っ込まれないようにというセキュリティ対策なんじゃないでしょうか。