Created
June 7, 2012 20:50
-
-
Save smazurov/2891455 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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