補足(2010-08-28追記):
下記 mongoSession クラスは一部のPHPバージョンでAPCを使用している場合、正常に動作しない場合があるようです。(静的メソッドがキャッシュに乗らない場合がある?) PHPとAPCでバグ報告がされており、現行バージョンでは解決済み、とのレスもありますが、その解決済みバージョンを使用しても直らない、という人もいるようです。
前回、MongoDBとPHP mongoモジュールのインストールをしたので、PHPからmongoを操作してみたいと思います。
CentOS5.5にMongoDBをインストールしてみる - 今日も適当ダイアリー
何がいいかなぁ、と考えてみたのですが、セッションハンドラを書いてみる事にしてみます。
PHPのセッションは、デフォルトではファイル管理となりますが、それだと、冗長化構成時にネットワークディスクを使ったりしなければならなかったり問題になる場合があります。
やっぱ、同一の情報に複数のサーバーからアクセスするんなら、DBだよね。ってわけで、DBで、セッション管理をすることになるのですが、今回はMongoDBでセッション情報を保存するようなハンドラを作りたいと思います。
PHP Mongoエクステンションの詳細については、http://jp.php.net/mongoで確認してください。
MongoDBへのアクセス方法
では、基本的なMongoDBの操作方法を見ていきます。
接続する
<?php $mongo = new Mongo('mongodb://localhost:27017');
オプションを指定する場合は、下記のように。
ここでは、コンストラクタで接続を行い、持続的な接続を有効にしてみます。
'persist' => 'foobar' で初期化した Mongo のインスタンスがふたつあれば、それらは同じデータベース接続になります。
<?php $mongo = new Mongo('mongodb://localhost:27017', array('connect' => true, 'persist' => 'foobar'));
接続を確認したい場合は、こんな感じ。
<?php if ($mongo->connected) { echo '接続されてるよ!'; }
ちなみに、特に理由がない限り、切断は必要ありません。
データの保存
こんな感じ。
<?php $data = array( 'text' => 'テストデータです。', 'insertdate' => new MongoDate() // 日時は MongoDate を使う ); $col = $mongo->selectDB('testDb')->selectCollection('testCollection'); $col->save($data); var_dump($data); echo $data['_id'];
saveメソッドで渡した$dataには挿入されたデータが入ります。
(_idは自動割り振りですが、_idにMongoId情報も入ります。)
出力は下記のような感じ。
array(3) { ["text"]=> string(27) "テストデータです。" ["insertdate"]=> object(MongoDate)#2 (2) { ["sec"]=> int(1278667026) ["usec"]=> int(160485) } ["_id"]=> object(MongoId)#5 (0) { } } 4c36e9129d5c882710000000
同じIDを指定すれば、上書きされます。
※arrayの内容で上書きされます。
<?php $data = array( // MongoID を使います '_id' => new MongoId('4c36e9129d5c882710000000'), 'text' => '上書きされます。' ); $col = $mongo->selectDB('testDb')->selectCollection('testCollection'); $col->save($data);
上記の場合、以前に入れたinsertdateは消えてしまいます。
insertdateを残したい場合は、updateするのがよさそうです。
<?php $col = $mongo->selectDB('testDb')->selectCollection('testCollection'); $col->update( array('_id' => new MongoId('4c36e9129d5c882710000000')), array('$set' => array('text' => '更新します。')) );
もちろん、_idを指定しての挿入も可能です。
<?php $col = $mongo->selectDB('testDb')->selectCollection('testCollection'); $col->save( array( '_id' => 1, 'text' => 'テストデータです。', 'insertdate' => new MongoDate() ) );
データの読み込み
一件のみ見つける。
<?php $col = $mongo->selectDB('testDb')->selectCollection('testCollection'); $item = $col->findOne(array('_id' => new MongoId('4c36e9129d5c882710000000')));
複数件見つける。
<?php $col = $mongo->selectDB('testDb')->selectCollection('testCollection'); $cur = $col->find(array()); $items = array(); while ($cur->hasNext()) { $items[] = $cur->getNext(); }
findOne/findともに、返される結果のフィールドを指定出来ます。
下記では、textフィールドのみを取得します。
(フィールド指定の有無を問わず、_idは必ず返ります。)
<?php $col = $mongo->selectDB('testDb')->selectCollection('testCollection'); $item = $col->findOne( array('_id' => new MongoId('4c36e9129d5c882710000000')), array('text' => true) );
データの削除
特筆すべきことも無いですね。
<?php $col = $mongo->selectDB('testDb')->selectCollection('testCollection'); $col->remove(array('_id' => new MongoId('4c36e9129d5c882710000000')));
1件だけ削除を明示したい場合は、オプションとしてjustOneを指定します。
<?php $col = $mongo->selectDB('testDb')->selectCollection('testCollection'); $col->remove( array('_id' => new MongoId('4c36e9129d5c882710000000')), array('justOne' => true) );
詳細は、PHPマニュアル、MongoDBマニュアルで確認してください。
(かなりはしょりました。)
セッションハンドラ
で、上記程度の知識で書いてみたセッションハンドラはこんな感じです。
<?php /** * MongoDB 用セッションハンドラ */ class mongoSession { /** * @var string Mongoサーバー名 */ private static $server; /** * @var string 持続的な接続を行うかどうか */ private static $persist; /** * @var string DB名 */ private static $db; /** * @var string セッション保存用コレクション名 */ private static $collection; /** * @var object Mongoクラスのインスタンス */ private static $mongo = null; /** * @var object MongoCollectionクラスのインスタンス */ private static $col = null; /** * mongoSessionの初期化とカスタムセッションハンドラの登録 * * @param string $db セッション保存用DB名 * @param string $collection セッション保存用コレクション名 * @param string $server サーバ名 * @param string $persist 持続的な接続を行うかどうか * @return bool セッションハンドラの登録に成功した場合に TRUE を、 失敗した場合に FALSE を返す */ public static function init($db = 'php', $collection = 'sessions', $server = 'mongodb://localhost:27017', $persist = null) { // それぞれのオプションを設定 self::$db = $db; self::$collection = $collection; self::$server = $server; self::$persist = $persist; // カスタムセッションハンドラとして登録 return session_set_save_handler( array(__CLASS__, 'open'), array(__CLASS__, 'close'), array(__CLASS__, 'read'), array(__CLASS__, 'write'), array(__CLASS__, 'destroy'), array(__CLASS__, 'gc') ); } /** * セッションがオープンした際に実行されます * * @param string $save_path 保存パス * @param string $session_name セッション名 * @return bool 成功した場合に TRUE を、失敗した場合に FALSE を返す */ public static function open($save_path, $session_name) { // すでにインスタンスがあれば何もしない if (!is_null(self::$mongo)) { return; } // MongoDB接続オプションの設定 $options = array('connect' => true); if (self::$persist) $options['persist'] = self::$persist; // MongoDBへ接続 self::$mongo = new Mongo(self::$server, $options); if (self::$mongo->connected) { self::$col = self::$mongo->selectDB(self::$db)->selectCollection(self::$collection); } return self::$mongo->connected; } /** * セッションの操作が終了した際に実行されます * * @return bool 成功した場合に TRUE を、失敗した場合に FALSE を返す */ public static function close() { return true; } /** * 保存されたセッションデータを読み込みます * * @param string $id セッションID * @return string セッションデータを返します */ public static function read($id) { if (!self::$col) return ''; $item = self::$col->findOne( array('_id' => $id), array('data' => true) ); return (string) $item['data']; } /** * セッションデータを保存します * * @param string $id セッションID * @param string $sess_data セッションデータ * @return bool 成功した場合に TRUE を、失敗した場合に FALSE を返す */ public static function write($id, $sess_data) { if (!self::$col) return false; return self::$col->save(array( '_id' => $id, 'data' => $sess_data, 'ts' => new MongoDate() // タイムスタンプ ), array('safe' => true)); } /** * セッションが session_destroy() で破棄された際に実行されます。 * * @param string $id セッションID * @return bool 成功した場合に TRUE を、失敗した場合に FALSE を返す */ public static function destroy($id) { return self::$col->remove( array('_id' => $id), array('justOne' => true) ); } /** * ガベージコレクタが実行されたときに実行されます。 * * @param int $maxlifetime 最大有効期間 * @return bool 成功した場合に TRUE を、失敗した場合に FALSE を返す */ public static function gc($maxlifetime) { return self::$col->remove( array( // 現在日時より $maxlifetime 以上古いデータは無効 'ts' => array('$lt' => new MongoDate(time() - $maxlifetime)) ) ); } }
次のスクリプトでmongoSessionが正常に動作するか試して見ます。
<?php mongoSession::init('test', 'phpsess', 'mongodb://localhost:27017', 'cPHPSess'); session_start(); $backsess = $_SESSION; if (isset($_GET['m']) && $_GET['m'] == 'logout') { // セッション変数を全て解除する $_SESSION = array(); $params = session_get_cookie_params(); setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"]); // 最終的に、セッションを破壊する session_destroy(); } else { $_SESSION['count'] = (isset($_SESSION['count']) && $_SESSION['count']) ? ($_SESSION['count']) + 1 : 1; $_SESSION['time'] = date('Y-m-d H:i:s', time()); $_SESSION['rand'] = mt_rand(); } ?> <h2>古い</h2> <pre> <?php var_dump($backsess); ?></pre> <h2>新しい</h2> <pre> <?php var_dump($_SESSION); ?></pre> <a href="?m=<?=$_SESSION['rand']?>">インクリメント</a> <a href="?m=logout">ログアウト</a>
うん。ちゃんと動いているみたいです。
あらかじめDB、テーブルを作っておかなくていいのは、とっても楽ちんです。
間違いなどありましたら、ご指摘ください。m(_ _)m