LL PlanetsのIPv6ハッカソンとfilter_var()

LL Planets

先週の土曜日 8月20日は文京シビックホールLL Planetsでした。自分も実行委員会の一人だったので、当日は朝早く起きて荷物を運んだり、会場の映像周りのセッティングをしたりしてました。


LL Planets の出し物の一つである「IPv6ハッカソン」参加者として召喚されていたので、当日のセッションはほとんど聞けなかったのですが、参加された方々のブログ等を読むに、まるまる JavaScript 一色に近い内容だったようです。聞きたかったなー。

IPv6ハッカソン

ささけん(@)から召喚されてIPv6ハッカソンの参加者となったので、ほぼ一日裏のハック部屋でこもって作業してました。久々に触って慣れてない FreeBSD や慣れない Ubuntu に苦労しながらも、午前中でなんとか環境構築を終わらせて、私がやってみたのはIPv6環境で、実際にPHPフレームワークを動かしてみる」でした。


PHPの使われ方、特に mod_phpApache HTTPD に組み込まれて使われる方法だと、ApacheIPv6 対応さえしていれば特に何もすることはありません。ただフレームワークによっては IPv4 に依存したコードが入っているかも知れないので、そこをあぶり出してみようということです。


対象となるフレームワークは、どうせだから一度使ってみたいと思っていながら時間がなくてなかなかいじれないものにしてみました。結局以下の4つになりました。

当日発表したプレゼン資料は Slideshare に上げてあります。

Lithiumとfilter_var( )

さて、その作業中に気づいたのが Lithium の IP アドレスバリデーションをするコードの短さです。

/libraries/lithium/util/Validator.php

<?php
        'ip' => function($value, $format = null, array $options = array()) {
                $options += array('flags' => array());
                return (boolean) filter_var($value, FILTER_VALIDATE_IP, $options);
        },

実質以下だけといっても良いです。

<?php
filter_var($value, FILTER_VALIDATE_IP);

これは何だろうということで、この FILTER_VALIDATE_IP で Lithium のコードを grep したのですが、ここ以外何もヒットしません。もしやと思って PHP マニュアルを見てみると、filter_var( ) は PHP 5.2.0 から入った標準関数でした。

さまざまな type と option を渡して、その名の如く値をフィルターする関数のなのですが、前述のFILTER_VALIDATE_IPを type に指定することで、IP アドレスのバリデータとして使用できます。

<?php
$addrs = array(
    '192.168.0',
    '192.168.0.0',
    '2001:2e8:408:10::49',
    '2001:2e8:408:10:0:0:49',
    '2001:2e8:408:10::0:49',
    '2001:2e8:408:10::49:',
    '2001:2e8:408::0::49',
    '::ffff:192.0.2.128', // ipv4 mapped
    '::192.0.2.128', // ipv4 compatible
    );
foreach ($addrs as $addr) {
    $result = filter_var($addr, FILTER_VALIDATE_IP);
    var_dump($result);
}

IPv4 / IPv6 両方に対応していますし、IPv4 mapped IPv6 アドレスや、 IPv4 compatible IPv6 アドレスも対応しています。

PHP: Validate filters - Manualを見ると、以下のフラグを options に指定することでそれぞれの on/off ができるようになっているようです。

  • FILTER_FLAG_IPV4
  • FILTER_FLAG_IPV6
  • FILTER_FLAG_NO_PRIV_RANGE
  • FILTER_FLAG_NO_RES_RANGE

他のフレームワークが、様々なテクニックを駆使して IP アドレスのバリデーションを複雑なコードで実現しているのに比べて、Lithium の洗練具合は半端ないなと思いましたw

PDO_pgsqlでハマった件 (PHP Advent Calendar 2010 17日目)

12/17 が終了して数時間が経ちましたが、16日目「匿名関数と無名関数 (PHP Advent Calendar 2010 16日目) | dogmap.jp」の wokamoto さんからバトンを渡されたPHP Advent Calendarの 17日目です。


さて PHP 5.2.X も終了が宣言された昨今、みなさんの PHP-5.3 への移行は順調に進んでいますでしょうか? 私も自分の環境やメンテナンス対象のコードを徐々に 5.3 対応に修正しているところです。今回はその中で DB アクセスライブラリを Creole から PDO に変更した際にハマった事を書こうと思います。


最近はフレームワークを使ったプログラミングが一般的なので、DBアクセスもフレームワークの Model クラスや、フレームワークがサポートしている Doctrine などの O/R マッパーを使うのが普通でしょうね。私は独自のアクセスライブラリを自前で作ってまして、フレームワークを使うまでもないプログラムにはそれを使っています。夏に開催された PHP Matsuri に参加して作成・公開した Keires_DB は、その独自ライブラリから他への依存部分を外して単独で使用できるようにしたものです。以前は DB 抽象化レイヤに Creole を使ってたのですが、Creole も開発が終了してからずいぶん経ちますし、5.3 に移行するこの機会に PDO に置き換えてみました。

Keires_DBの使い方を簡単に書いておくとこんな感じです。

  CREATE TABLE hogetbl                                                         
      col1    INTEGER PRIMARY KEY,                                              
      col2    TEXT,                                                             
      col3    TIMESTAMP,                                                        
      col4    BOOLEAN                                                           
  );   

というテーブルがあったとして、こんなクラスを作ります。

<?php
require_onde 'Keires/DB.php';
class DB_hoge extends Keires_DB_Abstract {
    static public $table_name = 'hogetbl';
    static public $tupple_info = array(
        'col1' => array(
            'type' => 'integer',
            'primary' => true,
            ),
        'col2' => array(
            'type' => 'text',
            ),
        'col3' => array(
            'type' => 'timestamp',
            ),
        'col4' => array(
            'type' => 'boolean',
            ),
        );
}

あとは初期化をしてアクセス。

<?php
$options = array(
    'types' => array(
        'default' => array(
            'dsn' => 'pgsql:host=localhost;user=username;dbname=dbname',
            ),
        ),
    );
Keires_DB::init($options);
// 書き込み
$data = array(
    'col1' => 1,
    'col2' => 'fugahoge',
    'col3' => date('c'),
    'col4' => false,
    );
DB_hoge::store($data);
// 読み込み
$col1 = 1;
$val = DB_hoge::getInfo($col1);
// 削除
DB_hoge::delete($col1);

さて上手く動いていたと思われていた Keires_DB ですが、最近不具合を発見しました。PostgreSQL との組み合わせで BOOLEAN なカラムに FALSE を保存しようとすると

"ERROR:  invalid input syntax for type boolean: """

というエラーがサーバから返るのです。実行されたSQLを調べると PDO::prepare()には想定通りのSQLが、PDOStatement::execute()にもちゃんと FALSE 値が渡ってました。該当部分の一部はこんな感じです。

<?php
    // $sql には ? プレースホルダ形式のSQL
    // $params にはバインドされる値が入ってます
     public function execute($sql, $params = array()) {
         $stmt = $this->_db->prepare($sql);
         return $stmt->execute($params);
     }

なぜこれで BOOLEAN で FALSE 値の場合のみ問題になるのか、ずいぶん悩んだんですが、マニュアルを熟読していてはたと気づきました。

実行される SQL 文の中のバインドパラメータと同数の要素からなる、 値の配列。すべての値は PDO::PARAM_STR として扱われます。

http://jp2.php.net/manual/ja/pdostatement.execute.php

PDOStatement::execute()に値を配列で渡す場合は、すべての値が文字列として解釈されるので FALSE 値は空文字列に変換されていたのです。


そこでexecute()メソッドに一気に値を渡すのを止めて、値を一つずつbindするメソッドを作りました。

<?php
    protected function _execStatement($stmt, $params) {
        $idx = 1;
        foreach ($params as $param) {
            $btype = PDO::PARAM_STR;
            if (is_int($param)) {
                $btype = PDO::PARAM_INT;
            } else if (is_bool($param)) {
                $btype = PDO::PARAM_BOOL;
            } else if (is_null($param)) {
                $btype = PDO::PARAM_NULL;
            }
            $bresult = $stmt->bindValue($idx, $param, $btype);
            if (!$bresult) {
                $errinfo = $stmt->errorInfo();
                throw new Exception($errinfo[2], $errinfo[0]);
            }
            ++$idx;
        }
        $result = $stmt->execute();
        if (!$result) {
            $errinfo = $stmt->errorInfo();
            throw new Exception($errinfo[2], $errinfo[0]);
        }
        return $result;
    }

    public function execute($sql, $params = array()) {
        $stmt = $this->_db->prepare($sql);
        return $this->_execStatement($stmt, $params);
    }

これでPHPの値の型に合わせてPDOStatement::bindValue()に適切なフラグをセットするようになったので、BOOLEANのFALSE値も問題なく動作するようになりました。

PDO対応にしてよかったこと

速くなった
  • PDO は Extension ベースのライブラリですから、ピュア PHP で書かれた Creole より数割〜数倍スピードが速くなりました。
BOOLEAN (TRUE / FALSE) と NULL 値がきちんと取れるようになった
  • PHP の pgsql モジュールは、BOOLEAN 値を取得するとあろうことか 't' / 'f' という文字列で返ってきます。PDO_pgsql ではきちんと型情報を見て PHP の bool 値に変換されて返ります。
  • 同様に pgsql モジュールでは NULL 値と空文字列の判別ができませんでしたが、PDO_pgsql ではきちんと NULL は NULL として返ります。

おまけ

MySQL では BOOLEAN は TINYINT で実装されているので、そもそもこの問題は発生しないのでした、ちゃんちゃん。

次(もう今日だ)はid:heavenshellさんです。どうもお待たせしましたw

Wozozoまつり in openpearの舞台裏

今openpearの中で密かに起きている祭り | この先生きのこるには

openpearでひっそり盛り上がっていたWozozoまつりがついに白日の下にさらされました。


ということで、なぜこんなことになってしまったのか、#openpear@freenode の当日のログをさらしとこうと思います。
そこそこ長いので、「続きを読む」で。

続きを読む

LOCAL DEVELOPER DAY '09 /Winterで喋ります

熱い活動を続けている北海道の地域コミュニティLOCALからお誘いを受けまして、「LOCAL DEVELOPER DAY '09 /Winter」でPHPIPv6対応について喋ることになりました。昨年のInternetWeek 2008や楽天テクノロジーカンファレンスで話した内容から、多少バージョンアップする予定です。


久しぶりの北海道で今から楽しみです。

splitの研究 in PHP

高林さんがsplit の研究というエントリを書かれています。それに対してtwkさんが

split の研究・・・ PHP 募集中 > http://tinyurl.com/5qlu66

http://twitter.com/twk/statuses/905375092

とつぶやいていたので調べてみました。ちなみにphp-5.2.5です。


最初よく確認もせずにtwkさんを高林さんと勘違いしてました。すいません。

普通に分割

var_dump(split(',', 'a,b,c'));
array(3) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "b"
  [2]=>
  string(1) "c"
}

末尾の空要素は省略されない

var_dump(split(',', 'a,,c,,'));
array(5) {
  [0]=>
  string(1) "a"
  [1]=>
  string(0) ""
  [2]=>
  string(1) "c"
  [3]=>
  string(0) ""
  [4]=>
  string(0) ""
}

空文字列だと結果も空

var_dump(split(',', ''));
array(1) {
  [0]=>
  string(0) ""
}

空文字列に対して分割パターンも空文字

実はPHPのsplitが受け付ける分割パターンは正規表現のみ。分割パターンがない場合はWarning発生 & falseが返る。

var_dump(split('', ''));
Warning: split(): REG_EMPTY in /path/to/split.php on line XX

bool(false)

分割パターン省略時

引数の順番から分割パターンは省略できない。

分割パターンは文字列か?

正規表現のみ。文字列で分割したいときはexplodeを使う。

分割パターンが空の文字列の場合

Warning発生 & false

分割パターンが正規表現

var_dump(split('[:;]', 'a:b;c.d'));
array(3) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "b"
  [2]=>
  string(3) "c.d"
}

ちなみに正規表現エンジンとしてPCREを用いるpreg_splitもあります。

分割パターンの正規表現が . だったら?

JavaScriptEmacsと同じになった。

var_dump(split('.', 'a:b;c.d'));
array(8) {
  [0]=>
  string(0) ""
  [1]=>
  string(0) ""
  [2]=>
  string(0) ""
  [3]=>
  string(0) ""
  [4]=>
  string(0) ""
  [5]=>
  string(0) ""
  [6]=>
  string(0) ""
  [7]=>
  string(0) ""
}

文字としての . にマッチさせるにはエスケープが必要

var_dump(split('\.', 'a:b;c.d'));
array(2) {
  [0]=>
  string(5) "a:b;c"
  [1]=>
  string(1) "d"
}

8/7にPHP4さよなら宴会前夜祭をやるの巻

http://serif.hatelabo.jp/27a0399c002b8ea64ea966183fc15aa2b97cfc7d/36921bf74b6a441406010de719dbc29094fb0c01


PHP4送別会の日程が熾烈なアンケートバトルにより8/8に決定したようです。同日開催の某技評イベントに申し込んでしまった人が大量発生涙目状態なので、せっかくだから8/7にも宴会を執り行いたいと思います。


参加希望者はこのエントリにコメントしてください。これから店を探して予約するので、〆切は7/31 17:00とします。

参加者リスト: 締め切りました、以下で確定。

  1. id:koyhoge
  2. id:sakaik
  3. id:nazone
  4. id:i_ogi
  5. id:zatsubun
  6. id:a98
  7. id:kaz_29
  8. id:shiotty
  9. id:masatobito
  10. id:aked0n
  11. id:elf
  12. lllnorikolll

(土壇場で2名追加)

2008/08/02追記:
店が決まりました。

神田 肉家焼肉 ゑびす本廛 8/7 19:00から。

小山で予約してます。

PHPの@によるエラー抑制機能の遅さ

PHPで@によるエラー抑制は実行が遅いという話は聞いていましたが、実際にベンチマークを取られた方がいました。

で、この処理が「遅い」という噂やツッコミを度々受けるので調べてみました。

「ほら、そんな気にするほどでも無いよ」という結果を期待していたのですが、、、なんじゃこりゃ。。。
劇遅じゃないですか、これ。

(中略)

細かい処理とはいえ、最大10倍違うわけですから・・・

「@」でエラー抑制すると PHP が遅くなるという噂について : 管理人@Yoski

これは配列のkeyチェックをするコードを対象としているので、@の効果のみによる遅さはよく分かりません。ということで、もっとシンプルに単純な代入のみで比べてみました。

ベンチマークプログラムはこんな感じ。

<?php
require_once 'Benchmark/Timer.php';
define('COUNT', 1000000);

$t = new Benchmark_Timer;

$t->start();
for ($i = 0; $i < COUNT; ++$i) {
    @$a = '1234';
}

$t->setMarker('@finish');

for ($i = 0; $i < COUNT; ++$i) {
    $b = '1234';
}

$t->stop();
$t->display();
?>

php-5.2.5で実行した結果はこんな感じです。

------------------------------------------------------
marker    time index            ex time         perct
------------------------------------------------------
Start     1210323360.32298600   -                0.00%
------------------------------------------------------
@finish   1210323362.32918100   2.006195        69.02%
------------------------------------------------------
Stop      1210323363.22948700   0.900306        30.98%
------------------------------------------------------
total     -                     2.906501       100.00%
------------------------------------------------------

シンプルな代入の場合、倍くらい遅い感じですかね。一行に複数の処理を記述していると、その分遅くなりそうです。

5.3からはarray_get()が入る?

元記事にあるような配列のkeyチェックは、私はarray_val()を使って

<?php
  $b = array_val($a, 'hoge');

のように書きますが、誰もが自分で書いていそうなarray_val()な関数が、5.3からarray_get()として標準関数に入るような記事をずいぶん前に読んだ記憶があります。でもまだマニュアルにもないみたいだし、どうなったんかな。

(22:31追記)

array_get()を標準関数にという話は、一番新しいところでは[PHP-DEV] [PATCH] array_get()で議論がされたあと、またもや放置状態のようです。ifsetor()とか ?: 演算子との絡みで不要論がでてます。確かに

<?php
  $b = ifsetor($a['hoge']);

でE_NOTICEがでないならば、こちらの方が良い気もします。でもifsetorって名前はよくないなー。もう少し短く3文字以内くらいならもっといいのに。