session_set_save_handler()でカスタムセッションハンドラ設定したらFatal error: Class not found

複数のwebサーバーでセッション情報共有するのにmemcached使うためにカスタムセッションハンドラ設定したら
ばっちり定義してるはずのクラスがなかったことになっててびびった話。

問題のあったスクリプト

  • クラスHogeを定義
  • writeハンドラでnew Hoge()
<?php
class Hoge {}
class MySessionHandler {
    public function open($save_path, $session_name){ /*処理*/ }
    public function close(){ /*処理*/ }
    public function read($id){ /*処理*/ }
    public function write($id, $sess_data){
        $hoge = new Hoge();
    }
    public function destroy($id){ /*処理*/ }
    public function gc($maxlifetime){ /*処理*/ }
}

$s = new MySessionHandler();
session_set_save_handler(array($s,'open'), array($s,'close'),
	array($s,'read'), array($s,'write'), array($s,'destroy'), array($s,'gc'));

session_start();
$_SESSION['fuga'] = 'abc';

ごらんの有様だよ

Fatal error: Class 'Hoge' not found in /xxx/xxx/sessiontest.php on line 8

Hogeってすぐそこに定義してるじゃん! Σ(・ω・;)

エラーの原因

どこでエラーになってるか

session_set_save_handler()の引数(callback $write)で指定した処理の実行時にエラーが起きています。

なんでエラーになってるか

http://php.mirror.camelnetwork.com/manual/ja/function.session-set-save-handler.php#refsect1-function.session-set-save-handler-notes

PHP 5.0.5 以降、write ハンドラおよび close ハンドラはオブジェクトが破棄されたあとにコールされます。 そのため、セッション内でデストラクタを使用可能ですが、 ハンドラ内ではオブジェクトを使用できません。

オブジェクトが破棄されて、定義されていたクラスやインスタンスがなかったことになっています。
(requireの状態は残ってるようで、require_onceしても対象のクラスファイルが再度読み込まれたりはしない)

解決方法

デストラクタでsession_write_close()

オブジェクト破棄の前にwriteハンドラコールすればよい。
http://php.mirror.camelnetwork.com/manual/ja/function.session-set-save-handler.php#refsect1-function.session-set-save-handler-notes

この「ニワトリが先かタマゴが先か」の問題を解決するために、 デストラクタから session_write_close() を コールすることが可能です。

こんな感じでOK
<?php
class MySessionHandler {
	// 省略 //

	// ↓追加
	public function __destruct(){
		session_write_close();
	}
}