diff --git a/framework/web/CacheSession.php b/framework/web/CacheSession.php new file mode 100644 index 0000000..9309849 --- /dev/null +++ b/framework/web/CacheSession.php @@ -0,0 +1,113 @@ +<?php +/** + * DbSession class file. + * + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\web; + +/** + * CacheSession implements a session component using cache as storage medium. + * + * The cache being used can be any cache application component implementing {@link ICache} interface. + * The ID of the cache application component is specified via {@link cacheID}, which defaults to 'cache'. + * + * Beware, by definition cache storage are volatile, which means the data stored on them + * may be swapped out and get lost. Therefore, you must make sure the cache used by this component + * is NOT volatile. If you want to use {@link CDbCache} as storage medium, use {@link CDbHttpSession} + * is a better choice. + * + * @property boolean $useCustomStorage Whether to use custom storage. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @package system.web + * @since 1.0 + */ +class CacheSession extends Session +{ + /** + * Prefix to the keys for storing cached data + */ + const CACHE_KEY_PREFIX = 'Yii.CacheSession.'; + /** + * @var string the ID of the cache application component. Defaults to 'cache' (the primary cache application component.) + */ + public $cacheID = 'cache'; + + /** + * @var ICache the cache component + */ + private $_cache; + + /** + * Initializes the application component. + * This method overrides the parent implementation by checking if cache is available. + */ + public function init() + { + $this->_cache = Yii::app()->getComponent($this->cacheID); + if (!($this->_cache instanceof ICache)) { + throw new CException(Yii::t('yii', 'CacheSession.cacheID is invalid. Please make sure "{id}" refers to a valid cache application component.', + array('{id}' => $this->cacheID))); + } + parent::init(); + } + + /** + * Returns a value indicating whether to use custom session storage. + * This method overrides the parent implementation and always returns true. + * @return boolean whether to use custom storage. + */ + public function getUseCustomStorage() + { + return true; + } + + /** + * Session read handler. + * Do not call this method directly. + * @param string $id session ID + * @return string the session data + */ + public function readSession($id) + { + $data = $this->_cache->get($this->calculateKey($id)); + return $data === false ? '' : $data; + } + + /** + * Session write handler. + * Do not call this method directly. + * @param string $id session ID + * @param string $data session data + * @return boolean whether session write is successful + */ + public function writeSession($id, $data) + { + return $this->_cache->set($this->calculateKey($id), $data, $this->getTimeout()); + } + + /** + * Session destroy handler. + * Do not call this method directly. + * @param string $id session ID + * @return boolean whether session is destroyed successfully + */ + public function destroySession($id) + { + return $this->_cache->delete($this->calculateKey($id)); + } + + /** + * Generates a unique key used for storing session data in cache. + * @param string $id session variable name + * @return string a safe cache key associated with the session variable name + */ + protected function calculateKey($id) + { + return self::CACHE_KEY_PREFIX . $id; + } +} diff --git a/framework/web/DbSession.php b/framework/web/DbSession.php new file mode 100644 index 0000000..feb828a --- /dev/null +++ b/framework/web/DbSession.php @@ -0,0 +1,268 @@ +<?php +/** + * DbSession class file. + * + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\web; + +/** + * DbSession extends {@link CHttpSession} by using database as session data storage. + * + * DbSession stores session data in a DB table named 'YiiSession'. The table name + * can be changed by setting {@link sessionTableName}. If the table does not exist, + * it will be automatically created if {@link autoCreateSessionTable} is set true. + * + * The following is the table structure: + * + * <pre> + * CREATE TABLE YiiSession + * ( + * id CHAR(32) PRIMARY KEY, + * expire INTEGER, + * data BLOB + * ) + * </pre> + * Where 'BLOB' refers to the BLOB-type of your preffered database. + * + * DbSession relies on {@link http://www.php.net/manual/en/ref.pdo.php PDO} to access database. + * + * By default, it will use an SQLite3 database named 'session-YiiVersion.db' under the application runtime directory. + * You can also specify {@link connectionID} so that it makes use of a DB application component to access database. + * + * When using DbSession in a production server, we recommend you pre-create the session DB table + * and set {@link autoCreateSessionTable} to be false. This will greatly improve the performance. + * You may also create a DB index for the 'expire' column in the session table to further improve the performance. + * + * @property boolean $useCustomStorage Whether to use custom storage. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @package system.web + * @since 1.0 + */ +class DbSession extends Session +{ + /** + * @var string the ID of a {@link CDbConnection} application component. If not set, a SQLite database + * will be automatically created and used. The SQLite database file is + * is <code>protected/runtime/session-YiiVersion.db</code>. + */ + public $connectionID; + /** + * @var string the name of the DB table to store session content. + * Note, if {@link autoCreateSessionTable} is false and you want to create the DB table manually by yourself, + * you need to make sure the DB table is of the following structure: + * <pre> + * (id CHAR(32) PRIMARY KEY, expire INTEGER, data BLOB) + * </pre> + * @see autoCreateSessionTable + */ + public $sessionTableName = 'YiiSession'; + /** + * @var boolean whether the session DB table should be automatically created if not exists. Defaults to true. + * @see sessionTableName + */ + public $autoCreateSessionTable = true; + /** + * @var CDbConnection the DB connection instance + */ + private $_db; + + + /** + * Returns a value indicating whether to use custom session storage. + * This method overrides the parent implementation and always returns true. + * @return boolean whether to use custom storage. + */ + public function getUseCustomStorage() + { + return true; + } + + /** + * Updates the current session id with a newly generated one. + * Please refer to {@link http://php.net/session_regenerate_id} for more details. + * @param boolean $deleteOldSession Whether to delete the old associated session file or not. + * @since 1.1.8 + */ + public function regenerateID($deleteOldSession = false) + { + $oldID = session_id(); + + // if no session is started, there is nothing to regenerate + if (empty($oldID)) { + return; + } + + parent::regenerateID(false); + $newID = session_id(); + $db = $this->getDbConnection(); + + $row = $db->createCommand() + ->select() + ->from($this->sessionTableName) + ->where('id=:id', array(':id' => $oldID)) + ->queryRow(); + if ($row !== false) { + if ($deleteOldSession) { + $db->createCommand()->update($this->sessionTableName, array( + 'id' => $newID + ), 'id=:oldID', array(':oldID' => $oldID)); + } else { + $row['id'] = $newID; + $db->createCommand()->insert($this->sessionTableName, $row); + } + } else { + // shouldn't reach here normally + $db->createCommand()->insert($this->sessionTableName, array( + 'id' => $newID, + 'expire' => time() + $this->getTimeout(), + )); + } + } + + /** + * Creates the session DB table. + * @param CDbConnection $db the database connection + * @param string $tableName the name of the table to be created + */ + protected function createSessionTable($db, $tableName) + { + $driver = $db->getDriverName(); + if ($driver === 'mysql') { + $blob = 'LONGBLOB'; + } elseif ($driver === 'pgsql') { + $blob = 'BYTEA'; + } else { + $blob = 'BLOB'; + } + $db->createCommand()->createTable($tableName, array( + 'id' => 'CHAR(32) PRIMARY KEY', + 'expire' => 'integer', + 'data' => $blob, + )); + } + + /** + * @return CDbConnection the DB connection instance + * @throws CException if {@link connectionID} does not point to a valid application component. + */ + protected function getDbConnection() + { + if ($this->_db !== null) { + return $this->_db; + } elseif (($id = $this->connectionID) !== null) { + if (($this->_db = Yii::app()->getComponent($id)) instanceof CDbConnection) { + return $this->_db; + } else { + throw new CException(Yii::t('yii', 'DbSession.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.', + array('{id}' => $id))); + } + } else { + $dbFile = Yii::app()->getRuntimePath() . DIRECTORY_SEPARATOR . 'session-' . Yii::getVersion() . '.db'; + return $this->_db = new CDbConnection('sqlite:' . $dbFile); + } + } + + /** + * Session open handler. + * Do not call this method directly. + * @param string $savePath session save path + * @param string $sessionName session name + * @return boolean whether session is opened successfully + */ + public function openSession($savePath, $sessionName) + { + if ($this->autoCreateSessionTable) { + $db = $this->getDbConnection(); + $db->setActive(true); + try { + $db->createCommand()->delete($this->sessionTableName, 'expire<:expire', array(':expire' => time())); + } catch (Exception $e) { + $this->createSessionTable($db, $this->sessionTableName); + } + } + return true; + } + + /** + * Session read handler. + * Do not call this method directly. + * @param string $id session ID + * @return string the session data + */ + public function readSession($id) + { + $data = $this->getDbConnection()->createCommand() + ->select('data') + ->from($this->sessionTableName) + ->where('expire>:expire AND id=:id', array(':expire' => time(), ':id' => $id)) + ->queryScalar(); + return $data === false ? '' : $data; + } + + /** + * Session write handler. + * Do not call this method directly. + * @param string $id session ID + * @param string $data session data + * @return boolean whether session write is successful + */ + public function writeSession($id, $data) + { + // exception must be caught in session write handler + // http://us.php.net/manual/en/function.session-set-save-handler.php + try { + $expire = time() + $this->getTimeout(); + $db = $this->getDbConnection(); + if ($db->createCommand()->select('id')->from($this->sessionTableName)->where('id=:id', array(':id' => $id))->queryScalar() === false) { + $db->createCommand()->insert($this->sessionTableName, array( + 'id' => $id, + 'data' => $data, + 'expire' => $expire, + )); + } else { + $db->createCommand()->update($this->sessionTableName, array( + 'data' => $data, + 'expire' => $expire + ), 'id=:id', array(':id' => $id)); + } + } catch (Exception $e) { + if (YII_DEBUG) { + echo $e->getMessage(); + } + // it is too late to log an error message here + return false; + } + return true; + } + + /** + * Session destroy handler. + * Do not call this method directly. + * @param string $id session ID + * @return boolean whether session is destroyed successfully + */ + public function destroySession($id) + { + $this->getDbConnection()->createCommand() + ->delete($this->sessionTableName, 'id=:id', array(':id' => $id)); + return true; + } + + /** + * Session GC (garbage collection) handler. + * Do not call this method directly. + * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up. + * @return boolean whether session is GCed successfully + */ + public function gcSession($maxLifetime) + { + $this->getDbConnection()->createCommand() + ->delete($this->sessionTableName, 'expire<:expire', array(':expire' => time())); + return true; + } +} diff --git a/framework/web/Session.php b/framework/web/Session.php index 6db2873..db03909 100644 --- a/framework/web/Session.php +++ b/framework/web/Session.php @@ -1,15 +1,19 @@ <?php /** - * CHttpSession class file. + * Session class file. * - * @author Qiang Xue <qiang.xue@gmail.com> * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC + * @copyright Copyright © 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ +namespace yii\web; + +use yii\base\Component; +use yii\base\InvalidParamException; + /** - * CHttpSession provides session-level data management and the related configurations. + * Session provides session-level data management and the related configurations. * * To start the session, call {@link open()}; To complete and send out session data, call {@link close()}; * To destroy the session, call {@link destroy()}. @@ -17,9 +21,9 @@ * If {@link autoStart} is set true, the session will be started automatically * when the application component is initialized by the application. * - * CHttpSession can be used like an array to set and get session data. For example, + * Session can be used like an array to set and get session data. For example, * <pre> - * $session=new CHttpSession; + * $session=new Session; * $session->open(); * $value1=$session['name1']; // get session variable 'name1' * $value2=$session['name2']; // get session variable 'name2' @@ -42,13 +46,13 @@ * See the corresponding setter and getter documentation for more information. * Note, these properties must be set before the session is started. * - * CHttpSession can be extended to support customized session storage. + * Session can be extended to support customized session storage. * Override {@link openSession}, {@link closeSession}, {@link readSession}, * {@link writeSession}, {@link destroySession} and {@link gcSession} - * and set {@link useCustomStorage} to true. + * and set [[useCustomStorage]] to true. * Then, the session data will be stored and retrieved using the above methods. * - * CHttpSession is a Web application component that can be accessed via + * Session is a Web application component that can be accessed via * {@link CWebApplication::getSession()}. * * @property boolean $useCustomStorage Whether to use custom storage. @@ -58,26 +62,23 @@ * @property string $savePath The current session save path, defaults to '/tmp'. * @property array $cookieParams The session cookie parameters. * @property string $cookieMode How to use cookie to store session ID. Defaults to 'Allow'. - * @property integer $gCProbability The probability (percentage) that the gc (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance. + * @property float $gCProbability The probability (percentage) that the gc (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance. * @property boolean $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to false. * @property integer $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up, defaults to 1440 seconds. - * @property CHttpSessionIterator $iterator An iterator for traversing the session variables. + * @property SessionIterator $iterator An iterator for traversing the session variables. * @property integer $count The number of session variables. * @property array $keys The list of session variable names. * * @author Qiang Xue <qiang.xue@gmail.com> - * @version $Id$ - * @package system.web - * @since 1.0 + * @since 2.0 */ -class CHttpSession extends CApplicationComponent implements IteratorAggregate, ArrayAccess, Countable +class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable { /** - * @var boolean whether the session should be automatically started when the session application component is initialized, defaults to true. + * @var boolean whether the session should be automatically started when the session component is initialized. */ public $autoStart = true; - /** * Initializes the application component. * This method is required by IApplicationComponent and is invoked by application. @@ -93,7 +94,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A /** * Returns a value indicating whether to use custom session storage. - * This method should be overriden to return true if custom session storage handler should be used. + * To use custom session storage, override this method and return This method should be overridden to return true if custom session storage handler should be used. * If returning true, make sure the methods {@link openSession}, {@link closeSession}, {@link readSession}, * {@link writeSession}, {@link destroySession}, and {@link gcSession} are overridden in child * class, because they will be used as the callback handlers. @@ -116,14 +117,14 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A @session_start(); if (YII_DEBUG && session_id() == '') { - $message = Yii::t('yii|Failed to start session.'); + $message = Yii::t('yii', 'Failed to start session.'); if (function_exists('error_get_last')) { $error = error_get_last(); if (isset($error['message'])) { $message = $error['message']; } } - Yii::log($message, CLogger::LEVEL_WARNING, 'system.web.CHttpSession'); + Yii::log($message, CLogger::LEVEL_WARNING, 'system.web.Session'); } } @@ -216,7 +217,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A if (is_dir($value)) { session_save_path($value); } else { - throw new CException(Yii::t('yii|CHttpSession.savePath "{path}" is not a valid directory.', + throw new CException(Yii::t('yii', 'Session.savePath "{path}" is not a valid directory.', array('{path}' => $value))); } } @@ -250,65 +251,65 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A } /** - * @return string how to use cookie to store session ID. Defaults to 'Allow'. + * Returns the value indicating whether cookies should be used to store session IDs. + * @return boolean|null the value indicating whether cookies should be used to store session IDs. + * @see setUseCookies() */ - public function getCookieMode() + public function getUseCookies() { if (ini_get('session.use_cookies') === '0') { - return 'none'; + return false; + } elseif (ini_get('session.use_only_cookies') === '1') { + return true; } else { - if (ini_get('session.use_only_cookies') === '0') { - return 'allow'; - } else { - return 'only'; - } + return null; } } /** - * @param string $value how to use cookie to store session ID. Valid values include 'none', 'allow' and 'only'. + * Sets the value indicating whether cookies should be used to store session IDs. + * Three states are possible: + * + * - true: cookies and only cookies will be used to store session IDs. + * - false: cookies will not be used to store session IDs. + * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter) + * + * @param boolean|null $value the value indicating whether cookies should be used to store session IDs. */ - public function setCookieMode($value) + public function setUseCookies($value) { - if ($value === 'none') { + if ($value === false) { ini_set('session.use_cookies', '0'); ini_set('session.use_only_cookies', '0'); + } elseif ($value === true) { + ini_set('session.use_cookies', '1'); + ini_set('session.use_only_cookies', '1'); } else { - if ($value === 'allow') { - ini_set('session.use_cookies', '1'); - ini_set('session.use_only_cookies', '0'); - } else { - if ($value === 'only') { - ini_set('session.use_cookies', '1'); - ini_set('session.use_only_cookies', '1'); - } else { - throw new CException(Yii::t('yii|CHttpSession.cookieMode can only be "none", "allow" or "only".')); - } - } + ini_set('session.use_cookies', '1'); + ini_set('session.use_only_cookies', '0'); } } /** - * @return integer the probability (percentage) that the gc (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance. + * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance. */ public function getGCProbability() { - return (int)ini_get('session.gc_probability'); + return (float)(ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100); } /** - * @param integer $value the probability (percentage) that the gc (garbage collection) process is started on every session initialization. - * @throws CException if the value is beyond [0,100] + * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization. + * @throws InvalidParamException if the value is not between 0 and 100. */ public function setGCProbability($value) { - $value = (int)$value; if ($value >= 0 && $value <= 100) { - ini_set('session.gc_probability', $value); - ini_set('session.gc_divisor', '100'); + // percent * 21474837 / 2147483647 ≈ percent * 0.01 + ini_set('session.gc_probability', floor($value * 21474836.47)); + ini_set('session.gc_divisor', 2147483647); } else { - throw new CException(Yii::t('yii|CHttpSession.gcProbability "{value}" is invalid. It must be an integer between 0 and 100.', - array('{value}' => $value))); + throw new InvalidParamException('GCProbability must be a value between 0 and 100.'); } } @@ -329,7 +330,8 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A } /** - * @return integer the number of seconds after which data will be seen as 'garbage' and cleaned up, defaults to 1440 seconds. + * @return integer the number of seconds after which data will be seen as 'garbage' and cleaned up. + * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini). */ public function getTimeout() { @@ -346,7 +348,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A /** * Session open handler. - * This method should be overridden if {@link useCustomStorage} is set true. + * This method should be overridden if [[useCustomStorage]] is set true. * Do not call this method directly. * @param string $savePath session save path * @param string $sessionName session name @@ -359,7 +361,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A /** * Session close handler. - * This method should be overridden if {@link useCustomStorage} is set true. + * This method should be overridden if [[useCustomStorage]] is set true. * Do not call this method directly. * @return boolean whether session is closed successfully */ @@ -370,7 +372,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A /** * Session read handler. - * This method should be overridden if {@link useCustomStorage} is set true. + * This method should be overridden if [[useCustomStorage]] is set true. * Do not call this method directly. * @param string $id session ID * @return string the session data @@ -382,7 +384,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A /** * Session write handler. - * This method should be overridden if {@link useCustomStorage} is set true. + * This method should be overridden if [[useCustomStorage]] is set true. * Do not call this method directly. * @param string $id session ID * @param string $data session data @@ -395,7 +397,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A /** * Session destroy handler. - * This method should be overridden if {@link useCustomStorage} is set true. + * This method should be overridden if [[useCustomStorage]] is set true. * Do not call this method directly. * @param string $id session ID * @return boolean whether session is destroyed successfully @@ -407,7 +409,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A /** * Session GC (garbage collection) handler. - * This method should be overridden if {@link useCustomStorage} is set true. + * This method should be overridden if [[useCustomStorage]] is set true. * Do not call this method directly. * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up. * @return boolean whether session is GCed successfully @@ -417,16 +419,14 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A return true; } - //------ The following methods enable CHttpSession to be CMap-like ----- - /** * Returns an iterator for traversing the session variables. * This method is required by the interface IteratorAggregate. - * @return CHttpSessionIterator an iterator for traversing the session variables. + * @return SessionIterator an iterator for traversing the session variables. */ public function getIterator() { - return new CHttpSessionIterator; + return new SessionIterator; } /** @@ -449,21 +449,11 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A } /** - * @return array the list of session variable names - */ - public function getKeys() - { - return array_keys($_SESSION); - } - - /** * Returns the session variable value with the session variable name. - * This method is very similar to {@link itemAt} and {@link offsetGet}, - * except that it will return $defaultValue if the session variable does not exist. - * @param mixed $key the session variable name + * If the session variable does not exist, the `$defaultValue` will be returned. + * @param string $key the session variable name * @param mixed $defaultValue the default value to be returned when the session variable does not exist. * @return mixed the session variable value, or $defaultValue if the session variable does not exist. - * @since 1.1.2 */ public function get($key, $defaultValue = null) { @@ -471,17 +461,6 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate, A } /** - * Returns the session variable value with the session variable name. - * This method is exactly the same as {@link offsetGet}. - * @param mixed $key the session variable name - * @return mixed the session variable value, null if no such variable exists - */ - public function itemAt($key) - { - return isset($_SESSION[$key]) ? $_SESSION[$key] : null; - } - - /** * Adds a session variable. * Note, if the specified name already exists, the old value will be removed first. * @param mixed $key session variable name diff --git a/framework/web/SessionIterator.php b/framework/web/SessionIterator.php new file mode 100644 index 0000000..be8e0f3 --- /dev/null +++ b/framework/web/SessionIterator.php @@ -0,0 +1,86 @@ +<?php +/** + * SessionIterator class file. + * + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\web; + +/** + * SessionIterator implements an iterator for traversing session variables managed by [[Session]]. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @since 2.0 + */ +class SessionIterator implements \Iterator +{ + /** + * @var array list of keys in the map + */ + private $_keys; + /** + * @var mixed current key + */ + private $_key; + + /** + * Constructor. + */ + public function __construct() + { + $this->_keys = array_keys($_SESSION); + } + + /** + * Rewinds internal array pointer. + * This method is required by the interface Iterator. + */ + public function rewind() + { + $this->_key = reset($this->_keys); + } + + /** + * Returns the key of the current array element. + * This method is required by the interface Iterator. + * @return mixed the key of the current array element + */ + public function key() + { + return $this->_key; + } + + /** + * Returns the current array element. + * This method is required by the interface Iterator. + * @return mixed the current array element + */ + public function current() + { + return isset($_SESSION[$this->_key]) ? $_SESSION[$this->_key] : null; + } + + /** + * Moves the internal pointer to the next array element. + * This method is required by the interface Iterator. + */ + public function next() + { + do { + $this->_key = next($this->_keys); + } while (!isset($_SESSION[$this->_key]) && $this->_key !== false); + } + + /** + * Returns whether there is an element at current position. + * This method is required by the interface Iterator. + * @return boolean + */ + public function valid() + { + return $this->_key !== false; + } +}