Refactoring Cache and Database session handlers into a separate class, and adding an interface for custom session handlers. Tests updated.

This commit is contained in:
mark_story 2010-07-25 11:42:05 -04:00
parent a8b05c2fbc
commit f5d81e53fc
2 changed files with 177 additions and 33 deletions

View file

@ -194,9 +194,9 @@ class CakeSession {
if (Configure::read('Session.defaults') !== 'database') { if (Configure::read('Session.defaults') !== 'database') {
return; return;
} }
$modelName = Configure::read('Session.model'); $modelName = Configure::read('Session.handler.model');
$database = Configure::read('Session.database'); $database = Configure::read('Session.handler.database');
$table = Configure::read('Session.table'); $table = Configure::read('Session.handler.table');
if (empty($database)) { if (empty($database)) {
$database = 'default'; $database = 'default';
@ -543,7 +543,10 @@ class CakeSession {
if (!empty($sessionConfig['ini']) && is_array($sessionConfig['ini'])) { if (!empty($sessionConfig['ini']) && is_array($sessionConfig['ini'])) {
foreach ($sessionConfig['ini'] as $setting => $value) { foreach ($sessionConfig['ini'] as $setting => $value) {
if (ini_set($setting, $value) === false) { if (ini_set($setting, $value) === false) {
throw new Exception(__('Unable to configure the session.')); throw new Exception(sprintf(
__('Unable to configure the session, setting %s failed.'),
$setting
));
} }
} }
} }
@ -551,6 +554,21 @@ class CakeSession {
if (!empty($sessionConfig['handler']) && !isset($sessionConfig['handler']['engine'])) { if (!empty($sessionConfig['handler']) && !isset($sessionConfig['handler']['engine'])) {
call_user_func_array('session_set_save_handler', $sessionConfig['handler']); call_user_func_array('session_set_save_handler', $sessionConfig['handler']);
} }
if (!empty($sessionConfig['handler']['engine'])) {
$class = $sessionConfig['handler']['engine'];
$reflect = new ReflectionClass($class);
if (!$reflect->implementsInterface('CakeSessionHandlerInterface')) {
throw new Exception(__('Chosen SessionHandler does not implement CakeSessionHandlerInterface'));
}
session_set_save_handler(
array($class, 'open'),
array($class, 'close'),
array($class, 'read'),
array($class, 'write'),
array($class, 'destroy'),
array($class, 'gc')
);
}
/* /*
switch (Configure::read('Session.save')) { switch (Configure::read('Session.save')) {
@ -610,12 +628,7 @@ class CakeSession {
'session.save_handler' => 'user', 'session.save_handler' => 'user',
), ),
'handler' => array( 'handler' => array(
array('CakeSession','__open'), 'engine' => 'CacheSession'
array('CakeSession', '__close'),
array('Cache', 'read'),
array('Cache', 'write'),
array('Cache', 'delete'),
array('Cache', 'gc')
) )
), ),
'database' => array( 'database' => array(
@ -632,12 +645,7 @@ class CakeSession {
'session.serialize_handler' => 'php', 'session.serialize_handler' => 'php',
), ),
'handler' => array( 'handler' => array(
array('CakeSession','__open'), 'engine' => 'DatabaseSession'
array('CakeSession', '__close'),
array('CakeSession', '__read'),
array('CakeSession', '__write'),
array('CakeSession', '__destroy'),
array('CakeSession', '__gc')
) )
) )
); );
@ -731,14 +739,80 @@ class CakeSession {
self::$error[$errorNumber] = $errorMessage; self::$error[$errorNumber] = $errorMessage;
self::$lastError = $errorNumber; self::$lastError = $errorNumber;
} }
}
/**
* Interface for Session handlers. Custom session handler classes should implement
* this interface as it allows CakeSession know how to map methods to session_set_save_handler()
*
* @package cake.libs
*/
interface CakeSessionHandlerInterface {
/**
* Method called on open of a session.
*
* @return boolean Success
*/
public static function open();
/**
* Method called on close of a session.
*
* @return boolean Success
*/
public static function close();
/**
* Method used to read from a session.
*
* @param mixed $id The key of the value to read
* @return mixed The value of the key or false if it does not exist
*/
public static function read($id);
/**
* Helper function called on write for sessions.
*
* @param integer $id ID that uniquely identifies session in database
* @param mixed $data The value of the data to be saved.
* @return boolean True for successful write, false otherwise.
*/
public static function write($id, $data);
/**
* Method called on the destruction of a session.
*
* @param integer $id ID that uniquely identifies session in database
* @return boolean True for successful delete, false otherwise.
*/
public static function destroy($id);
/**
* Run the Garbage collection on the session storage. This method should vacuum all
* expired or dead sessions.
*
* @param integer $expires Timestamp (defaults to current time)
* @return boolean Success
*/
public static function gc($expires = null);
}
/**
* CacheSession provides method for saving sessions into a Cache engine. Used as
*
* @package cake.libs
*/
class CacheSession implements CakeSessionHandlerInterface {
/** /**
* Method called on open of a database session. * Method called on open of a database session.
* *
* @return boolean Success * @return boolean Success
* @access private * @access private
*/ */
function __open() { public static function open() {
return true; return true;
} }
@ -748,17 +822,10 @@ class CakeSession {
* @return boolean Success * @return boolean Success
* @access private * @access private
*/ */
function __close() { public static function close() {
$probability = mt_rand(1, 150); $probability = mt_rand(1, 150);
if ($probability <= 3) { if ($probability <= 3) {
switch (Configure::read('Session.save')) {
case 'cache':
Cache::gc(); Cache::gc();
break;
default:
CakeSession::__gc();
break;
}
} }
return true; return true;
} }
@ -770,7 +837,83 @@ class CakeSession {
* @return mixed The value of the key or false if it does not exist * @return mixed The value of the key or false if it does not exist
* @access private * @access private
*/ */
function __read($id) { public static function read($id) {
return Cache::read($id);
}
/**
* Helper function called on write for database sessions.
*
* @param integer $id ID that uniquely identifies session in database
* @param mixed $data The value of the data to be saved.
* @return boolean True for successful write, false otherwise.
* @access private
*/
public static function write($id, $data) {
return Cache::write($id, $data);
}
/**
* Method called on the destruction of a database session.
*
* @param integer $id ID that uniquely identifies session in database
* @return boolean True for successful delete, false otherwise.
* @access private
*/
public static function destroy($id) {
return Cache::delete($id);
}
/**
* Helper function called on gc for database sessions.
*
* @param integer $expires Timestamp (defaults to current time)
* @return boolean Success
* @access private
*/
public static function gc($expires = null) {
return Cache::gc();
}
}
/**
* DatabaseSession provides methods to be used with CakeSession.
*
* @package cake.libs
*/
class DatabaseSession implements CakeSessionHandlerInterface {
/**
* Method called on open of a database session.
*
* @return boolean Success
* @access private
*/
public static function open() {
return true;
}
/**
* Method called on close of a database session.
*
* @return boolean Success
* @access private
*/
public static function close() {
$probability = mt_rand(1, 150);
if ($probability <= 3) {
DatabaseSession::gc();
}
return true;
}
/**
* Method used to read from a database session.
*
* @param mixed $id The key of the value to read
* @return mixed The value of the key or false if it does not exist
* @access private
*/
public static function read($id) {
$model =& ClassRegistry::getObject('Session'); $model =& ClassRegistry::getObject('Session');
$row = $model->find('first', array( $row = $model->find('first', array(
@ -792,7 +935,7 @@ class CakeSession {
* @return boolean True for successful write, false otherwise. * @return boolean True for successful write, false otherwise.
* @access private * @access private
*/ */
function __write($id, $data) { public static function write($id, $data) {
$expires = time() + Configure::read('Session.timeout') * Security::inactiveMins(); $expires = time() + Configure::read('Session.timeout') * Security::inactiveMins();
$model =& ClassRegistry::getObject('Session'); $model =& ClassRegistry::getObject('Session');
$return = $model->save(compact('id', 'data', 'expires')); $return = $model->save(compact('id', 'data', 'expires'));
@ -806,7 +949,7 @@ class CakeSession {
* @return boolean True for successful delete, false otherwise. * @return boolean True for successful delete, false otherwise.
* @access private * @access private
*/ */
function __destroy($id) { public static function destroy($id) {
$model =& ClassRegistry::getObject('Session'); $model =& ClassRegistry::getObject('Session');
$return = $model->delete($id); $return = $model->delete($id);
@ -820,7 +963,7 @@ class CakeSession {
* @return boolean Success * @return boolean Success
* @access private * @access private
*/ */
function __gc($expires = null) { public static function gc($expires = null) {
$model =& ClassRegistry::getObject('Session'); $model =& ClassRegistry::getObject('Session');
if (!$expires) { if (!$expires) {

View file

@ -578,10 +578,11 @@ class CakeSessionTest extends CakeTestCase {
* @return void * @return void
*/ */
function testReadAndWriteWithDatabaseStorage() { function testReadAndWriteWithDatabaseStorage() {
Configure::write('Session.table', 'sessions');
Configure::write('Session.model', 'Session');
Configure::write('Session.database', 'test_suite');
Configure::write('Session.defaults', 'database'); Configure::write('Session.defaults', 'database');
Configure::write('Session.handler.table', 'sessions');
Configure::write('Session.handler.model', 'Session');
Configure::write('Session.handler.database', 'test_suite');
TestCakeSession::init(); TestCakeSession::init();
TestCakeSession::destroy(); TestCakeSession::destroy();