PHPでは独自のセッションハンドラをプログラマが実装することができます。具体的には、session_set_save_handler関数を使うことで、標準のセッションハンドラが対応していないようなストレージにセッション情報を保存することができます。
ところで、PHP5まではカスタムセッションハンドラの返り値の扱いが間違っていました。マニュアルではtrue
かfalse
を返すことになっているのですが、実際には失敗なら-1
を、成功ならそれ以外を返すというインターフェースになっていました。
これが、PHP7ではマニュアル通りになるよう修正されます。
以下、PHP5でのセッションハンドラの利用例です。
<?php
ini_set('session.gc_divisor',1);
/* PHP5用のカスタムセッションハンドラ */
session_set_save_handler(
/* open */
function ($savePath, $sessionName) { return 0; }, /* -1だとFatal error */
/* close */
function () { return 0; }, /* -1でもエラーにならない */
/* read */
function ($sessionId) { return ['foo' => 'bar']; },
/* write */
function ($sessionId, $data) { return 0; }, /* -1だとWarning */
/* destroy */
function ($sessionId) { return 0; }, /* -1だとWarning */
/* gc */
function ($lifetime) { return 0; }, /* -1でもエラーにならない */
/* create_sid */
function () { return 'baz'; }
);
session_id('baaz');
session_start();
session_regenerate_id(TRUE);
session_write_close();
このように、セッションハンドラ内でエラーが起きたときは-1
を返す必要がありました。驚くべきことに、マニュアル通りにtrue
を返そうがfalse
を返そうが成功として扱われていたのです(true
とfalse
を整数にキャストしても1
と0
なので、失敗ではない=成功として扱われる)。
一方、PHP7では次のように記述できます。
<?php
ini_set('session.gc_divisor',1);
/* PHP7用のカスタムセッションハンドラ */
session_set_save_handler(
/* open */
function ($savePath, $sessionName) { return true; }, /* falseだとFatal error */
/* close */
function () { return true; }, /* falseでもエラーにならない */
/* read */
function ($sessionId) { return ['foo' => 'bar']; },
/* write */
function ($sessionId, $data) { return true; }, /* falseだとWarning */
/* destroy */
function ($sessionId) { return true; }, /* falseだとWarning */
/* gc */
function ($lifetime) { return true; }, /* falseでもエラーにならない */
/* create_sid */
function () { return 'baz'; }
);
session_id('baaz');
session_start();
session_regenerate_id(TRUE);
session_write_close();
成功ならtrue
を、失敗ならfalse
を返すというわけです。平和ですね。
どうしてこうなった
PHP内部のCの関数は次のような返り値で成功失敗を受け渡しています。
typedef enum {
SUCCESS = 0,
FAILURE = -1, /* this MUST stay a negative number, or it may affect functions! */
} ZEND_RESULT_CODE;
今回のセッションハンドラはbooleanを返すPHP関数ですが、この返り値をそのままC関数の返り値に利用していたために意味が変わっていたというわけです。Cでありがちなうっかりミスと言えなくもないですが、今まで誰も気がつかなかったのがビックリですね。