*/ class sfCacheSessionStorage extends sfStorage { protected $id = null, $context = null, $dispatcher = null, $request = null, $response = null, $cache = null, $data = array(), $dataChanged = false; /** * Initialize this Storage. * * @param array $options An associative array of initialization parameters. * session_name [required] name of session to use * session_cookie_path [required] cookie path * session_cookie_domain [required] cookie domain * session_cookie_lifetime [required] liftime of cookie * session_cookie_secure [required] send only if secure connection * session_cookie_http_only [required] accessible only via http protocol * * @return bool true, when initialization completes successfully. * * @throws sfInitializationException If an error occurs while initializing this Storage. */ public function initialize($options = array()) { // initialize parent // bc with a slightly different name formerly used here, let's be // compatible with the base class name for it from here on out if (isset($options['session_cookie_http_only'])) { $options['session_cookie_httponly'] = $options['session_cookie_http_only']; } parent::initialize(array_merge(array('session_name' => 'sfproject', 'session_cookie_lifetime' => '+30 days', 'session_cookie_path' => '/', 'session_cookie_domain' => null, 'session_cookie_secure' => false, 'session_cookie_httponly' => true, 'session_cookie_secret' => 'sf$ecret'), $options)); // create cache instance if (isset($this->options['cache']) && $this->options['cache']['class']) { $this->cache = new $this->options['cache']['class'](is_array($this->options['cache']['param']) ? $this->options['cache']['param'] : array()); } else { throw new InvalidArgumentException('sfCacheSessionStorage requires cache option.'); } $this->context = sfContext::getInstance(); $this->dispatcher = $this->context->getEventDispatcher(); $this->request = $this->context->getRequest(); $this->response = $this->context->getResponse(); $cookie = $this->request->getCookie($this->options['session_name']); if(strpos($cookie, ':') !== false) { // split cookie data id:signature(id+secret) list($id, $signature) = explode(':', $cookie, 2); if($signature == sha1($id.':'.$this->options['session_cookie_secret'])) { // cookie is valid $this->id = $id; } else { // cookie signature broken $this->id = null; } } else { // cookie format wrong $this->id = null; } if(empty($this->id)) { $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'localhost'; $ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'ua'; // generate new id based on random # / ip / user agent / secret $this->id = md5(rand(0, 999999).$ip.$ua.$this->options['session_cookie_secret']); if(sfConfig::get('sf_logging_enabled')) { $this->dispatcher->notify(new sfEvent($this, 'application.log', array('New session created'))); } // only send cookie when id is issued $this->response->setCookie($this->options['session_name'], $this->id.':'.sha1($this->id.':'.$this->options['session_cookie_secret']), $this->options['session_cookie_lifetime'], $this->options['session_cookie_path'], $this->options['session_cookie_domain'], $this->options['session_cookie_secure'], $this->options['session_cookie_httponly']); $this->data = array(); } else { // load data from cache. Watch out for the default case. We could // serialize(array()) as the default to the call but that would be a performance hit $raw = $this->cache->get($this->id, null); if (is_null($raw)) { $this->data = array(); } elseif (is_array($raw)) { // probably an old cached value (BC) $this->data = $raw; } else { $this->data = unserialize($raw); } if(sfConfig::get('sf_logging_enabled')) { $this->dispatcher->notify(new sfEvent($this, 'application.log', array('Restored previous session'))); } } session_id($this->id); $this->response->addCacheControlHttpHeader('private'); return true; } /** * Write data to this storage. * * The preferred format for a key is directory style so naming conflicts can be avoided. * * @param string $key A unique key identifying your data. * @param mixed $data Data associated with your key. * * @return void */ public function write($key, $data) { $this->dataChanged = true; $this->data[$key] =& $data; } /** * Read data from this storage. * * The preferred format for a key is directory style so naming conflicts can be avoided. * * @param string $key A unique key identifying your data. * * @return mixed Data associated with the key. */ public function read($key) { $retval = null; if (isset($this->data[$key])) { $retval =& $this->data[$key]; } return $retval; } /** * Remove data from this storage. * * The preferred format for a key is directory style so naming conflicts can be avoided. * * @param string $key A unique key identifying your data. * * @return mixed Data associated with the key. */ public function remove($key) { $retval = null; if (isset($this->data[$key])) { $this->dataChanged = true; $retval =& $this->data[$key]; unset($this->data[$key]); } return $retval; } /** * Regenerates id that represents this storage. * * @param boolean $destroy Destroy session when regenerating? * * @return boolean True if session regenerated, false if error * * @throws sfStorageException If an error occurs while regenerating this storage */ public function regenerate($destroy = false) { if($destroy) { $this->data = array(); $this->cache->remove($this->id); } // generate session id $ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'ua'; $this->id = md5(rand(0, 999999).$_SERVER['REMOTE_ADDR'].$ua.$this->options['session_cookie_secret']); // save data to cache $this->cache->set($this->id, serialize($this->data)); // update session id in signed cookie $this->response->setCookie($this->options['session_name'], $this->id.':'.sha1($this->id.':'.$this->options['session_cookie_secret']), $this->options['session_cookie_lifetime'], $this->options['session_cookie_path'], $this->options['session_cookie_domain'], $this->options['session_cookie_secure'], $this->options['session_cookie_httponly']); session_id($this->id); return true; } /** * Expires the session storage instance. */ public function expire() { // destroy data and regenerate id $this->regenerate(true); if(sfConfig::get('sf_logging_enabled')) { $this->dispatcher->notify(new sfEvent($this, 'application.log', array('new session created due to expiraton'))); } } /** * Executes the shutdown procedure. * * @throws sfStorageException If an error occurs while shutting down this storage */ public function shutdown() { // only update cache if session has changed if($this->dataChanged === true) { $this->cache->set($this->id, serialize($this->data)); if(sfConfig::get('sf_logging_enabled')) { $this->dispatcher->notify(new sfEvent($this, 'application.log', array('Storing session to cache'))); } } } }