Skip to content

Instantly share code, notes, and snippets.

@smazurov
Created June 7, 2012 20:50
Show Gist options
  • Save smazurov/2891455 to your computer and use it in GitHub Desktop.
Save smazurov/2891455 to your computer and use it in GitHub Desktop.
<?php
require 'external/php-on-couch/lib/couch.php';
require 'external/php-on-couch/lib/couchClient.php';
require 'external/php-on-couch/lib/couchDocument.php';
/**
* This class replaces built in php session handling with a much more robust
* couchdb backed variant, great for multi-box session handling.
* Uses php-on-couch <https://github.com/dready92/PHP-on-Couch> library for
* couch communication.
*
* TODO: cache "reads" in memory store such as apc
*
* @version 0.0.1
* @author Stepan Mazurov
**/
class couchSessionStore {
private static $couch;
private static $session_name;
/**
* Method: couch
* Description: this function creates and assigns a local couch connection
* also selects and creates an appropriate database for session handling
* Name is chosen based on name of the session set by php or session_name()
*
* @param string $couch
* @return couchClient object
* @author Stepan Mazurov
*/
public static function couch($couch = null) {
if ($couch == null) {
self::$couch = new couchClient($config['couchdb_server'], $config['couchdb_db']);
} elseif (!($couch instanceof couchClient)) {
throw new Exception('wrong instance of couchClient');
} else {
self::$couch = $couch;
}
if (!empty(self::$session_name)) {
self::$couch->useDatabase(self::$session_name);
}
if (self::$couch->databaseExists() === false) {
self::$couch->createDatabase();
}
return self::$couch;
}
/**
* Method: open
* Description: The open callback works like a constructor in classes and
* is executed when the session is being opened. It is the first callback
* function executed when the session is started automatically or manually
* with session_start(). Return value is TRUE for success, FALSE for failure.
*
* The couch implementation adds verification that a view that we use for
* garbage collection of old sessions exists.
*
* @param string $session_path
* @param string $session_name
* @return bool
* @author Stepan Mazurov
*/
public static function open($session_path, $session_name) {
self::$session_name = strtolower($session_name);
//make sure we have the right couch object
self::couch(self::$couch);
//check to see if we have an appropriate design doc
try {
$response = self::$couch->getViewInfos("purge");
if ($response) return true;
} catch (Exception $e) {
$view_fn = 'function(doc) { emit(doc.createdAt, null); }';
$design_doc = new stdClass();
$design_doc->_id = '_design/purge';
$design_doc->views = array ('by_date'=> array ('map' => $view_fn ));
try {
self::$couch->storeDoc($design_doc);
} catch (Exception $e) {
return false;
}
}
return true;
}
/**
* Method: close
* Description: resets session name and the couch object.
*
* @return bool true
* @author Stepan Mazurov
*/
public static function close() {
self::$couch = null;
self::$session_name = null;
return true;
}
/**
* Method: read
* Description: returns a session encoded (serialized) string, or an
* empty string if there is no data to read.
*
* @param string $id
* @return string
* @author Stepan Mazurov
*/
public static function read($id) {
try {
$doc = self::$couch->getDoc($id);
return $doc->data;
} catch (Exception $e) {
return '';
}
}
/**
* Method: write
* Description: writes data to couch store. Sets session id as document id
* and data as data.
*
* @param string $id
* @param string $data
* @return void
* @author Stepan Mazurov
*/
public static function write($id, $data) {
try {
$doc = self::$couch->getDoc($id);
} catch (Exception $e) {
if($e->getCode() != '404') {
return false;
}
$doc = new stdClass();
$doc->_id = $id;
}
$doc->data = $data;
$doc->createdAt = time();
try {
self::$couch->storeDoc($doc);
} catch (Exception $e) {
return false;
}
return true;
}
/**
* Method: destroy
* Description: deletes specified session.
*
* @param string $id
* @return bool
* @author Stepan Mazurov
*/
public static function destroy($id) {
try {
self::$couch->deleteDoc($id);
} catch (Exception $e) {
return false;
}
return true;
}
/**
* Method: gc
* Description: The garbage collector callback is invoked internally by PHP
* periodically in order to purge old session data.
*
* @param string $lifetime in seconds
* @return void
* @author Stepan Mazurov
*/
public static function gc($lifetime) {
$key = time() - $lifetime;
$docs_to_delete = array();
try {
$rows = self::$couch->endkey($key)->include_docs(true)
->getView('purge','by_date');
//Fancy foreach that shows the middle finger to php < 5.3.0
array_walk($rows->rows, function($key) use(&$docs_to_delete) {
$docs_to_delete[] = $key->doc;
});
if(sizeof($docs_to_delete) > 0) {
self::$couch->deleteDocs($docs_to_delete);
return true;
}
} catch (Exception $e) {
return false;
}
return true;
}
}
session_set_save_handler(
array("couchSessionStore", "open"),
array("couchSessionStore", "close"),
array("couchSessionStore", "read"),
array("couchSessionStore", "write"),
array("couchSessionStore", "destroy"),
array("couchSessionStore", "gc"));
// the following prevents unexpected effects when using objects as save handlers
register_shutdown_function('session_write_close');
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment