Zend SessionでDBを利用したカスタム保存ハンドラ

概要

PHP5のセッション管理機能は、デフォルトではファイルで管理しているため、負 荷分散などでWebサーバを利用する場合は、高価なZendPlatformを導入し、セッションをWebサーバ間で共有させる必要があります。
ただし、セッション管理部分はユーザー定義関数で上書きすることができるため、セッションの保存にはDBなりMemcachedなりの好きな媒体を利用す ることが可能です。
→ session_set_save_handler
しかし、Zend Frameworkはこのような関数を直で使うことをよしとしていないため、Zend_Sessionに用意されたラッパークラスを使わなければいけませ ん。
一応、マ ニュアルには「Zend_Db 互換の保存ハンドラは、このメーリングリストに投稿されます。」と書いてありますが、残念ながらZend_Dbはそのまま利用していないので、半手動で実 装したメモを残しておきます。PostgreSQL利用。

こんな人にお勧め

・ZendPlatformに頼らず、DBにセッションを持たせたい人

動作環境

PHP5.2とZendFramework1.00RC2で動作確認しています。以下からダウンロードしてください。
PostgreSQL8.2.4を使っていますが、まぁ他のDBでも基本はやることは同じです。
PHP5.2
ZendFramework
PostgreSQL8.2.4

解説らしきもの

ユーザー定義のセッション管理を利用するには、Zend_Session:: setSaveHandlerメソッドにセッション保存ハンドラクラスのインスタンスを渡す必要があります。

ini_set("session.save_handler""user");
Zend_Session::setSaveHandler(new SessionHandler);
Zend_Session::start();

一行目は念のため。php.iniでuserに設定してあるのならばあらためて宣言する必要はありません。
二行目は、setSaveHandlerメソッドにSessionHandlerのインスタンスを渡しています。
DBに対してSession情報を保存するためのメソッドなどを記述するのはこのSessionHandlerクラスです。
(もちろんクラス名はなんでもかまいませんが)

あとはこんな感じで、Zend_Session_SaveHandler_Interfaceを実装したクラスを定義しておきましょう。
何に使われるのかよくわからないメソッドはとりあえずTrueを返すようにしてあります(いいのか?)

Class 
SessionHandler Implements Zend_Session_SaveHandler_Interface
{
    public function 
__construct(){
    }

    // 何に使うのかよくわからないのでスルー
    public function open($save_path$name){
        return true;
    }

    // これも何に使うのかよくわからないのでスルー
    public function close() {
        return true;
    }

     // セッション読み込み
     // SQLやクエリを投げる関数は自前なので、ここらへんは各環境に合わせて適当に。
    public function read($id){
        $sql .= "select data from session where session = " DB::SqlString($id);
        if( 
DB::SelectQuery($sql,$rs) ){
            if( 
$rs->Fetch() ){
                // sessionテーブルから指定のsessionの行のdataを返す
                return $rs->data;
            }
        }
        return 
"";
    }

    // セッション書き込み
    public function write($id$data){
        $sql " select p_session(" DB::SqlString($id) . "," DB::SqlString($data) . ") as result";
        if( 
DB::SelectQuery($sql,$rs) ){
            // 新しい接続により、新規にセッションが発行された場合も呼び出されるため、
            // ストアドファンクションで追加と更新に両対応
            return true;
        }
        return 
false;
    }

    public function destroy($id){
       // セッション破棄
       // なんらかのメソッドを使うとコールされる・・はず
        $sql .= "delete from session where session = " DB::SqlString($id);
        if( 
DB::Execute($sql) ){
            return 
true;
        }
        return 
false;
    }

    public function 
gc($maxlifetime){
       // もちろんセッションのガベージコレクタも手動で定義してやらないとダメ
       // 期限切れのセッション情報を消すSQLのハズですが、割と適当。
        $sql .= "delete from session where updt <= " DB::SqlString(date("Y/m/d H:i:s"mktime() - $maxlifetime ));
        if( 
DB::Execute($sql) ){
            return 
true;
        }
        return 
false;
    }
}

・・と、PHP側はこんな感じ。DBのセッション保存テーブル定義はというと

CREATE TABLE session(
    session char(32) ,  -- PHPSESSID
    data varchar(8000)  , --セッションの実データ
    rgdt timestamp not null default now()    ,        -- 登録日(Insert
    updt timestamp not null default now()            -- 更新日(Update
);
alter table session add constraint session_pkey primary key (session);

こんな感じ。
PHPSESSIDは32桁なのでchar。
セッションのデータは場合によってはvarcharの限界にぶちあたると思うので、出来ればtext型にしておいた方がいいかも?

おまけ。
    public function write($id$data){
で呼び出されているplpgsqlによるストアドファンクション。MS SQLServerのTransactSQLみたいな書き方が出来ないのでわざわざ定義。
値を返す必要あるのか?とかツッコミどころ満載ですがそのあたりは割と適当なのであしからず。
上述のセッションデータフィールドをテキストにする場合は下も直す必要アリ。
これ書いた当初は、新規セッションなら1、上書きなら0、とかやりたかったのだろうけど、プログラム側で一切使っていないのです。
まぁ普通にプログラム側からトランザクション使って更新、追加するっていうんでも良いんですが。

create or replace function p_session(id char(32), indata varchar(8000)) returns int as
'
declare
 sdata varchar(8000);
 ret int;

begin
   sdata = '''';

   select data into sdata from session where session = id;
   if sdata is not null then
     update session set data = indata , updt = now() where session = id;
     ret := 0;
   else
     insert into session (session, data ) values (id, indata );
     ret := 1;
   end if;

   return ret;
end;
'
language 'plpgsql'


上述のように、

Zend_Session
::start();

でセッションを開始できます。起動ファイルの最初の方に記述しておきましょう。
また、プログラムの終了前には

Zend_Session::writeClose();

も忘れずに。これを記述しないとエラーが吐かれます、っていうかたぶんセッション変数の更新がコミットされません。
必ず実行される、終了処理として記述しておくと良いでしょう。
あとはマニュアルにあるように、startされてからwriteCloseされるまでの処理として

$defaultNamespace 
= new Zend_Session_Namespace('Default');
$defaultNamespace->numberOfPageRequests++;

・・とか記述してやれば自動的にテーブルが更新されます。
ブラウザとクエリクライアントを開きながら確認してみてください。

利用方法

適当にシステムに組み込んでください。
ZendFrameworkへパスを通して、Zend_Sessionは予めRequire Onceしておく必要があります。

ダウンロード

無し。

ソースコード

上述。