Storing Sessions in Memcached with Zend Framework
Zend Framework has an excellent API for sessions in order to build out your own session save handler. By taking this approach I can still leverage the cache manager and share the same connection to memcached for other things in my application. If you need to expire contents during a deployment this can still handle it utilizing the “clone” keyword.On to the code! See the link above for the Libmemcached implementation if you do not have the latest RC release. Next we need to build out the session save handler. We will name this Zend_Session_SaveHandler_Cache as it will support any cache that you would like to throw at it. The benefit of this really comes down to the lower environments and the ability to simply store sessions in APC for a time or even going to a simple file based for development / testing.
Zend_Session_SaveHandler_Cache
You will want to store this file in library/Zend/Session/SaveHandler/Cache.php. This handles the main setup for doing the Cache save handler./** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-webat this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Session * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** * @see Zend_Session */ require_once 'Zend/Session.php'; /** * @see Zend_Config */ require_once 'Zend/Config.php'; /** * @see Zend_Cache */ require_once 'Zend/Cache.php'; /** * Zend_Session_SaveHandler_Cache * * @category Zend * @package Zend_Session * @subpackage SaveHandler * @copyright Copyright (c) 2010 CaringBridge. (http://www.caringbridge.org) */ class Zend_Session_SaveHandler_Cache implements Zend_Session_SaveHandler_Interface { /** * Zend Cache * @var Zend_Cache */ protected $_cache; /** * Destructor * * @return void */ public function __destruct() { Zend_Session::writeClose(); } /** * Set Cache * * @param Zend_Cache_Core $cache * @return Zend_Session_SaveHandler_Cache */ public function setCache(Zend_Cache_Core $cache) { $this->_cache = $cache; } /** * Get Cache *- * @return Zend_Cache_Core */ public function getCache() { return $this->_cache; } /** * Open Session * * @param string $save_path * @param string $name * @return boolean */ public function open($save_path, $name) { $this->_sessionSavePath = $save_path; $this->_sessionName = $name; return true; } /** * Close session * * @return boolean */ public function close() { return true; } /** * Read session data * * @param string $id * @return string */ public function read($id) { if (!$data = $this->_cache->load($id)) { return null; } return $data; } /** * Write session data * * @param string $id * @param string $data * @return boolean */ public function write($id, $data) { return $this->_cache->save( $data, $id, array(), Zend_Session::getOptions('gc_maxlifetime') ); } /** * Destroy session * * @param string $id * @return boolean */ public function destroy($id) { return $this->_cache->remove($id); } /** * Garbage Collection * * @param int $maxlifetime * @return true */ public function gc($maxlifetime) { return true; }
// add by phuc*************************************************
/**
* Set whether or not the lifetime of an existing session should be overridden
*
* @param boolean $overrideLifetime
* @return Zend_Session_SaveHandler_DbTable
*/
public function setOverrideLifetime($overrideLifetime)
{
$this->_overrideLifetime = (boolean) $overrideLifetime;
return $this;
}
/**
* Retrieve whether or not the lifetime of an existing session should be overridden
*
* @return boolean
*/
public function getOverrideLifetime()
{
return $this->_overrideLifetime;
}
/**
* Set session lifetime and optional whether or not the lifetime of an existing session should be overridden
*
* $lifetime === false resets lifetime to session.gc_maxlifetime
*
* @param int $lifetime
* @param boolean $overrideLifetime (optional)
* @return Zend_Session_SaveHandler_DbTable
*/
public function setLifetime($lifetime, $overrideLifetime = null)
{
if ($lifetime < 0) {
/**
* @see Zend_Session_SaveHandler_Exception
*/
require_once 'Zend/Session/SaveHandler/Exception.php';
throw new Zend_Session_SaveHandler_Exception();
} else if (empty($lifetime)) {
$this->_lifetime = (int) ini_get('session.gc_maxlifetime');
} else {
$this->_lifetime = (int) $lifetime;
}
if ($overrideLifetime != null) {
$this->setOverrideLifetime($overrideLifetime);
}
return $this;
}
}
Setting up the Application
To set up the application to work correctly, you will need to modify the application.ini file if you are using Zend_Application as well as modifying the bootstrap process to set the cache.Application Configuration
This file is located at application/configs/application.ini; setup the cache resources.cachemanager.memcached.frontend.name = Core resources.cachemanager.memcached.frontend.options.automatic_serialization = On resources.cachemanager.memcached.backend.name = Libmemcached resources.cachemanager.memcached.backend.options.servers.one.host = localhost resources.cachemanager.memcached.backend.options.servers.one.port = 11211 resources.cachemanager.memcached.backend.options.servers.one.persistent = On ; session savehandler class resources.session.name = phpsessionname resources.session.saveHandler.class = Zend_Session_SaveHandler_Cache resources.session.gc_maxlifetime = 7200
Setting up the Bootstrap
This file is located at: application/Bootstrap.php. Since the Zend_Session_SaveHandler_Cache requires a cache to be set, we need to place this in the bootstrap, also you will likely want to ensure no possibility of session hijacking in the bootstrap as well./** * Initialize the Session Id * This code initializes the session and then * will ensure that we force them into an id to * prevent session fixation / hijacking. * * @return void */ protected function _initSessionId() { $this->bootstrap('session'); $opts = $this->getOptions(); if ('Zend_Session_SaveHandler_Cache' == $opts['resources']['session']['saveHandler']['class']) { $cache = $this->bootstrap('cachemanager') ->getResource('cachemanager') ->getCache('memcached'); Zend_Session::getSaveHandler()->setCache($cache); } $defaultNamespace = new Zend_Session_Namespace(); if (!isset($defaultNamespace->initialized)) { Zend_Session::regenerateId(); $defaultNamespace->initialized = true; } }
Sharing Libmemcached between Content and Sessions
Say we want to share the cache within our code base but utilize an SVN revision number to keep a prefix for the cache but not the sessions. Here is an implementation to do so in the Bootstrap.php file based on the above configuration./** * Initialize the Cache Manager * Initializes the memcached cache into * the registry and returns the cache manager. * * @return Zend_Cache_Manager */ protected function _initCachemanager() { $cachemanager = $this->getPluginResource('cachemanager') ->init(); // fetch the current revision from svn and use it as a prefix // why: we do not want to restart memcached, or you will lose sessions. if (!$appVersion = apc_fetch('progsite_version')) { $dir = getcwd(); chdir(dirname(__FILE__)); $appVersion = filter_var(`svn info | grep "Revision"`, FILTER_SANITIZE_NUMBER_INT); chdir($dir); unset($dir); if (!$appVersion) { $appVersion = mt_rand(0, 99999); // simply handles an export instead of checkout } apc_store('progsite_version', $appVersion); } $memcached = $cachemanager->getCache('memcached'); $memcached->setOption('cache_id_prefix', APPLICATION_ENV . '_' . $appVersion); return $cachemanager; } /** * Initialize the Session Id * This code initializes the session and then * will ensure that we force them into an id to * prevent session fixation / hijacking. * * @return void */ protected function _initSessionId() { $this->bootstrap('session'); $opts = $this->getOptions(); if ('Zend_Session_SaveHandler_Cache' == $opts['resources']['session']['saveHandler']['class']) { $cache = $this->bootstrap('cachemanager') ->getResource('cachemanager') ->getCache('memcached'); $cache = clone $cache; $cache->setOption('cache_id_prefix', APPLICATION_ENV); Zend_Session::getSaveHandler()->setCache($cache); } $defaultNamespace = new Zend_Session_Namespace(); if (!isset($defaultNamespace->initialized)) { Zend_Session::regenerateId(); $defaultNamespace->initialized = true; } }
No comments:
Post a Comment