[HttpFoundation] Introduced session storage base class and interfaces.

Session object now implements SessionInterface to make it more portable.

AbstractSessionStorage and SessionSaveHandlerInterface now makes implementation
of session storage drivers simple and easy to write for both custom save handlers
and native php save handlers and respect the PHP session workflow.
This commit is contained in:
Drak 2011-11-29 10:20:09 +05:45
parent c9694237d2
commit 3a263dc088
7 changed files with 1188 additions and 371 deletions

View File

@ -12,62 +12,45 @@
namespace Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\SessionStorage\SessionStorageInterface;
use Symfony\Component\HttpFoundation\FlashBagInterface;
/**
* Session.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Drak <drak@zikula.org>
*
* @api
*/
class Session implements \Serializable
class Session implements SessionInterface
{
/**
* Storage driver.
*
* @var SessionStorageInterface
*/
protected $storage;
protected $started;
protected $attributes;
protected $flashes;
protected $oldFlashes;
protected $closed;
/**
* Constructor.
*
* @param SessionStorageInterface $storage A SessionStorageInterface instance
* @param SessionStorageInterface $storage A SessionStorageInterface instance.
*/
public function __construct(SessionStorageInterface $storage)
{
$this->storage = $storage;
$this->flashes = array();
$this->oldFlashes = array();
$this->attributes = array();
$this->started = false;
$this->closed = false;
}
/**
* Starts the session storage.
*
* @return boolean True if session started.
*
* @api
*/
public function start()
{
if (true === $this->started) {
return;
}
$this->storage->start();
$attributes = $this->storage->read('_symfony2');
if (isset($attributes['attributes'])) {
$this->attributes = $attributes['attributes'];
$this->flashes = $attributes['flashes'];
// flag current flash messages to be removed at shutdown
$this->oldFlashes = $this->flashes;
}
$this->started = true;
return $this->storage->start();
}
/**
@ -81,7 +64,7 @@ class Session implements \Serializable
*/
public function has($name)
{
return array_key_exists($name, $this->attributes);
return $this->storage->getAttributes()->has($name);
}
/**
@ -96,7 +79,7 @@ class Session implements \Serializable
*/
public function get($name, $default = null)
{
return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
return $this->storage->getAttributes()->get($name, $default);
}
/**
@ -109,11 +92,7 @@ class Session implements \Serializable
*/
public function set($name, $value)
{
if (false === $this->started) {
$this->start();
}
$this->attributes[$name] = $value;
$this->storage->getAttributes()->set($name, $value);
}
/**
@ -125,7 +104,7 @@ class Session implements \Serializable
*/
public function all()
{
return $this->attributes;
return $this->storage->getAttributes()->all();
}
/**
@ -137,11 +116,7 @@ class Session implements \Serializable
*/
public function replace(array $attributes)
{
if (false === $this->started) {
$this->start();
}
$this->attributes = $attributes;
$this->storage->getAttributes()->replace($attributes);
}
/**
@ -153,13 +128,7 @@ class Session implements \Serializable
*/
public function remove($name)
{
if (false === $this->started) {
$this->start();
}
if (array_key_exists($name, $this->attributes)) {
unset($this->attributes[$name]);
}
return $this->storage->getAttributes()->remove($name);
}
/**
@ -169,190 +138,193 @@ class Session implements \Serializable
*/
public function clear()
{
if (false === $this->started) {
$this->start();
}
$this->attributes = array();
$this->flashes = array();
$this->storage->getAttributes()->clear();
}
/**
* Invalidates the current session.
*
* Clears all session attributes and flashes and regenerates the
* session and deletes the old session from persistence.
*
* @return boolean True if session invalidated, false if error.
*
* @api
*/
public function invalidate()
{
$this->clear();
$this->storage->regenerate(true);
$this->storage->clear();
return $this->storage->regenerate(true);
}
/**
* Migrates the current session to a new session id while maintaining all
* session attributes.
*
* @param boolean $destroy Whether to delete the old session or leave it to garbage collection.
*
* @return boolean True if session migrated, false if error
*
* @api
*/
public function migrate()
public function migrate($destroy = false)
{
$this->storage->regenerate();
return $this->storage->regenerate($destroy);
}
/**
* {@inheritdoc}
*/
public function save()
{
$this->storage->save();
}
/**
* Returns the session ID
*
* @return mixed The session ID
* @return mixed The session ID
*
* @api
*/
public function getId()
{
if (false === $this->started) {
$this->start();
}
return $this->storage->getId();
}
/**
* Gets the flash messages.
* Implements the \Serialize interface.
*
* @return array
* @return SessionStorageInterface
*/
public function getFlashes()
{
return $this->flashes;
}
/**
* Sets the flash messages.
*
* @param array $values
*/
public function setFlashes($values)
{
if (false === $this->started) {
$this->start();
}
$this->flashes = $values;
$this->oldFlashes = array();
}
/**
* Gets a flash message.
*
* @param string $name
* @param string|null $default
*
* @return string
*/
public function getFlash($name, $default = null)
{
return array_key_exists($name, $this->flashes) ? $this->flashes[$name] : $default;
}
/**
* Sets a flash message.
*
* @param string $name
* @param string $value
*/
public function setFlash($name, $value)
{
if (false === $this->started) {
$this->start();
}
$this->flashes[$name] = $value;
unset($this->oldFlashes[$name]);
}
/**
* Checks whether a flash message exists.
*
* @param string $name
*
* @return Boolean
*/
public function hasFlash($name)
{
if (false === $this->started) {
$this->start();
}
return array_key_exists($name, $this->flashes);
}
/**
* Removes a flash message.
*
* @param string $name
*/
public function removeFlash($name)
{
if (false === $this->started) {
$this->start();
}
unset($this->flashes[$name]);
}
/**
* Removes the flash messages.
*/
public function clearFlashes()
{
if (false === $this->started) {
$this->start();
}
$this->flashes = array();
$this->oldFlashes = array();
}
public function save()
{
if (false === $this->started) {
$this->start();
}
$this->flashes = array_diff_key($this->flashes, $this->oldFlashes);
$this->storage->write('_symfony2', array(
'attributes' => $this->attributes,
'flashes' => $this->flashes,
));
}
/**
* This method should be called when you don't want the session to be saved
* when the Session object is garbaged collected (useful for instance when
* you want to simulate the interaction of several users/sessions in a single
* PHP process).
*/
public function close()
{
$this->closed = true;
}
public function __destruct()
{
if (true === $this->started && !$this->closed) {
$this->save();
}
}
public function serialize()
{
return serialize($this->storage);
}
/**
* Implements the \Serialize interface.
*
* @throws \InvalidArgumentException If the passed string does not unserialize to an instance of SessionStorageInterface
*/
public function unserialize($serialized)
{
$this->storage = unserialize($serialized);
$this->attributes = array();
$this->started = false;
$storage = unserialize($serialized);
if (!$storage instanceof SessionStorageInterface) {
throw new \InvalidArgumentException('Serialized data did not return a valid instance of SessionStorageInterface');
}
$this->storage = $storage;
}
/**
* Adds a flash to the stack for a given type.
*
* @param string $message
* @param string $type
*/
public function addFlash($message, $type = FlashBagInterface::NOTICE)
{
$this->storage->getFlashes()->add($message, $type);
}
/**
* Gets flash messages for a given type.
*
* @param string $type Message category type.
*
* @return array
*/
public function getFlashes($type = FlashBagInterface::NOTICE)
{
return $this->storage->getFlashes()->get($type);
}
/**
* Pops flash messages off th stack for a given type.
*
* @param string $type Message category type.
*
* @return array
*/
public function popFlashes($type = FlashBagInterface::NOTICE)
{
return $this->storage->getFlashes()->pop($type);
}
/**
* Pop all flash messages from the stack.
*
* @return array Empty array or indexed array of arrays.
*/
public function popAllFlashes()
{
return $this->storage->getFlashes()->popAll();
}
/**
* Sets an array of flash messages for a given type.
*
* @param string $type
* @param array $array
*/
public function setFlashes($type, array $array)
{
$this->storage->getFlashes()->set($type, $array);
}
/**
* Has flash messages for a given type?
*
* @param string $type
*
* @return boolean
*/
public function hasFlashes($type)
{
return $this->storage->getFlashes()->has($type);
}
/**
* Returns a list of all defined types.
*
* @return array
*/
public function getFlashKeys()
{
return $this->storage->getFlashes()->keys();
}
/**
* Gets all flash messages.
*
* @return array
*/
public function getAllFlashes()
{
return $this->storage->getFlashes()->all();
}
/**
* Clears flash messages for a given type.
*
* @param string $type
*
* @return array Returns an array of what was just cleared.
*/
public function clearFlashes($type)
{
return $this->storage->getFlashes()->clear($type);
}
/**
* Clears all flash messages.
*
* @return array Empty array or indexed arrays or array if none.
*/
public function clearAllFlashes()
{
return $this->storage->getFlashes()->clearAll();
}
}

View File

@ -0,0 +1,138 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\SessionStorage\AttributeInterface;
use Symfony\Component\HttpFoundation\FlashBagInterface;
/**
* Interface for the session.
*
* @author Drak <drak@zikula.org>
*/
interface SessionInterface extends AttributeInterface, \Serializable
{
/**
* Starts the session storage.
*
* @throws \RuntimeException If session fails to start.
*/
function start();
/**
* Invalidates the current session.
*
* @return boolean True if session invalidated, false if error.
*/
function invalidate();
/**
* Migrates the current session to a new session id while maintaining all
* session attributes.
*
* @param boolean $destroy Whether to delete the old session or leave it to garbage collection.
*
* @return boolean True if session migrated, false if error.
*
* @api
*/
function migrate($destroy = false);
/**
* Force the session to be saved and closed.
*
* This method is generally not required for real sessions as
* the session will be automatically saved at the end of
* code execution.
*/
function save();
/**
* Adds a flash to the stack for a given type.
*
* @param string $message
* @param string $type
*/
function addFlash($message, $type = FlashBagInterface::NOTICE);
/**
* Gets flash messages for a given type.
*
* @param string $type Message category type.
*
* @return array
*/
function getFlashes($type = FlashBagInterface::NOTICE);
/**
* Pops flash messages off th stack for a given type.
*
* @param string $type Message category type.
*
* @return array
*/
function popFlashes($type = FlashBagInterface::NOTICE);
/**
* Pop all flash messages from the stack.
*
* @return array Empty array or indexed array of arrays.
*/
function popAllFlashes();
/**
* Sets an array of flash messages for a given type.
*
* @param string $type
* @param array $array
*/
function setFlashes($type, array $array);
/**
* Has flash messages for a given type?
*
* @param string $type
*
* @return boolean
*/
function hasFlashes($type);
/**
* Returns a list of all defined types.
*
* @return array
*/
function getFlashKeys();
/**
* Gets all flash messages.
*
* @return array
*/
function getAllFlashes();
/**
* Clears flash messages for a given type.
*
* @param string $type
*
* @return array Returns an array of what was just cleared.
*/
function clearFlashes($type);
/**
* Clears all flash messages.
*
* @return array Array of arrays or array if none.
*/
function clearAllFlashes();
}

View File

@ -0,0 +1,326 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\SessionStorage;
use Symfony\Component\HttpFoundation\FlashBag;
use Symfony\Component\HttpFoundation\FlashBagInterface;
use Symfony\Component\HttpFoundation\AttributeBag;
use Symfony\Component\HttpFoundation\AttributeBagInterface;
/**
* This provides a base class for session attribute storage.
*
* @author Drak <drak@zikula.org>
*/
abstract class AbstractSessionStorage implements SessionStorageInterface
{
/**
* @var \Symfony\Component\HttpFoundation\FlashBagInterface
*/
protected $flashBag;
/**
* @var \Symfony\Component\HttpFoundation\AttributeBagInterface
*/
protected $attributeBag;
/**
* @var array
*/
protected $options;
/**
* @var boolean
*/
protected $started = false;
/**
* @var boolean
*/
protected $closed = false;
/**
* Constructor.
*
* Depending on how you want the storage driver to behave you probably
* want top override this constructor entirely.
*
* List of options for $options array with their defaults.
* @see http://www.php.net/manual/en/session.configuration.php for options
* but we omit 'session.' from the beginning of the keys.
*
* auto_start, "0"
* cookie_domain, ""
* cookie_httponly, ""
* cookie_lifetime, "0"
* cookie_path, "/"
* cookie_secure, ""
* entropy_file, ""
* entropy_length, "0"
* gc_divisor, "100"
* gc_maxlifetime, "1440"
* gc_probability, "1"
* hash_bits_per_character, "4"
* hash_function, "0"
* name, "PHPSESSID"
* referer_check, ""
* save_path, ""
* serialize_handler, "php"
* use_cookies, "1"
* use_only_cookies, "1"
* use_trans_sid, "0"
* upload_progress.enabled, "1"
* upload_progress.cleanup, "1"
* upload_progress.prefix, "upload_progress_"
* upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS"
* upload_progress.freq, "1%"
* upload_progress.min-freq, "1"
* url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
*
* @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag)
* @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag)
* @param array $options Session configuration options.
*/
public function __construct(AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null, array $options = array())
{
$this->attributeBag = $attributes ? $attributes : new AttributeBag();
$this->flashBag = $flashes ? $flashes : new FlashBag();
$this->setOptions($options);
$this->registerSaveHandlers();
$this->registerShutdownFunction();
}
/**
* {@inheritdoc}
*/
public function getFlashes()
{
if (!$this->started) {
$this->start();
}
return $this->flashBag;
}
/**
* {@inheritdoc}
*/
public function getAttributes()
{
if (!$this->started) {
$this->start();
}
return $this->attributeBag;
}
/**
* {@inheritdoc}
*/
public function start()
{
if ($this->started && !$this->closed) {
return true;
}
// start the session
if (!session_start()) {
throw new \RuntimeException('Failed to start the session');
}
$this->loadSession();
$this->started = true;
$this->closed = false;
return true;
}
/**
* {@inheritdoc}
*/
public function getId()
{
if (!$this->started) {
return ''; // returning empty is consistent with session_id() behaviour
}
return session_id();
}
/**
* Regenerates the session.
*
* This method will regenerate the session ID and optionally
* destroy the old ID. Session regeneration should be done
* periodically and for example, should be done when converting
* an anonymous session to a logged in user session.
*
* @param boolean $destroy
*
* @return boolean Returns true on success or false on failure.
*/
public function regenerate($destroy = false)
{
return session_regenerate_id($destroy);
}
/**
* {@inheritdoc}
*/
public function save()
{
session_write_close();
$this->closed = true;
}
/**
* {@inheritdoc}
*/
public function clear()
{
// clear out the bags
$this->attributeBag->clear();
$this->flashBag->clearAll();
// clear out the session
$_SESSION = array();
// reconnect the bags to the session
$this->loadSession();
}
/**
* Sets session.* ini variables.
*
* For convenience we omit 'session.' from the beginning of the keys.
* Explicitly ignores other ini keys.
*
* session_get_cookie_params() overrides values.
*
* @param array $options
*
* @see http://www.php.net/manual/en/session.configuration.php
*/
protected function setOptions(array $options)
{
$cookieDefaults = session_get_cookie_params();
$this->options = array_merge(array(
'cookie_lifetime' => $cookieDefaults['lifetime'],
'cookie_path' => $cookieDefaults['path'],
'cookie_domain' => $cookieDefaults['domain'],
'cookie_secure' => $cookieDefaults['secure'],
'cookie_httponly' => isset($cookieDefaults['httponly']) ? $cookieDefaults['httponly'] : false,
), $options);
// Unless session.cache_limiter has been set explicitly, disable it
// because this is managed by HeaderBag directly (if used).
if (!array_key_exists('cache_limiter', $this->options)) {
$this->options['cache_limiter'] = 0;
}
foreach ($this->options as $key => $value) {
if (in_array($key, array(
'auto_start', 'cookie_domain', 'cookie_httponly',
'cookie_lifetime', 'cookie_path', 'cookie_secure',
'entropy_file', 'entropy_length', 'gc_divisor',
'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
'hash_function', 'name', 'referer_check',
'save_path', 'serialize_handler', 'use_cookies',
'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags'))) {
ini_set('session.'.$key, $value);
}
}
}
/**
* Registers this storage device for PHP session handling.
*
* PHP requires session save handlers to be set, either it's own, or custom ones.
* There are some defaults set automatically when PHP starts, but these can be overriden
* using this command if you need anything other than PHP's default handling.
*
* When the session starts, PHP will call the sessionRead() handler which should return an array
* of any session attributes. PHP will then populate these into $_SESSION.
*
* When PHP shuts down, the sessionWrite() handler is called and will pass the $_SESSION contents
* to be stored.
*
* When a session is specifically destroyed, PHP will call the sessionDestroy() handler with the
* session ID. This happens when the session is regenerated for example and th handler
* MUST delete the session by ID from the persistent storage immediately.
*
* PHP will call sessionGc() from time to time to expire any session records according to the
* set max lifetime of a session. This routine should delete all records from persistent
* storage which were last accessed longer than the $lifetime.
*
* PHP sessionOpen() and sessionClose() are pretty much redundant and can just return true.
*
* NOTE:
*
* To use PHP native save handlers, override this method using ini_set with
* session.save_handlers and session.save_path e.g.
*
* ini_set('session.save_handlers', 'files');
* ini_set('session.save_path', /tmp');
*
* @see http://php.net/manual/en/function.session-set-save-handler.php
* @see SessionSaveHandlerInterface
*/
protected function registerSaveHandlers()
{
// note this can be reset to PHP's control using ini_set('session.save_handler', 'files');
// so long as ini_set() is called before the session is started.
if ($this instanceof SessionSaveHandlerInterface) {
session_set_save_handler(
array($this, 'sessionOpen'),
array($this, 'sessionClose'),
array($this, 'sessionRead'),
array($this, 'sessionWrite'),
array($this, 'sessionDestroy'),
array($this, 'sessionGc')
);
}
}
/**
* Registers PHP shutdown function.
*
* This method is required to avoid strange issues when using PHP objects as
* session save handlers.
*/
protected function registerShutdownFunction()
{
register_shutdown_function('session_write_close');
}
/**
* Load the session with attributes.
*
* After starting the session, PHP retrieves the session from whatever handlers
* are set to (either PHP's internal, custom set with session_set_save_handler()).
* PHP takes the return value from the sessionRead() handler, unserializes it
* and populates $_SESSION with the result automatically.
*/
protected function loadSession()
{
$key = $this->attributeBag->getStorageKey();
$_SESSION[$key] = isset($_SESSION[$key]) ? $_SESSION[$key] : array();
$this->attributeBag->initialize($_SESSION[$key]);
$key = $this->flashBag->getStorageKey();
$_SESSION[$key] = isset($_SESSION[$key]) ? $_SESSION[$key] : array();
$this->flashBag->initialize($_SESSION[$key]);
}
}

View File

@ -0,0 +1,157 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\SessionStorage;
/**
* Session Savehandler Interface.
*
* This interface is for implementing methods required for the
* session_set_save_handler() function.
*
* @see http://php.net/session_set_save_handler
*
* These are methods called by PHP when the session is started
* and closed and for various house-keeping tasks required
* by session management.
*
* PHP requires session save handlers. There are some defaults set
* automatically when PHP starts, but these can be overriden using
* this command if you need anything other than PHP's default handling.
*
* When the session starts, PHP will call the sessionRead() handler
* which should return a string extactly as stored (which will have
* been encoded by PHP using a special session serializer session_decode()
* which is different to the serialize() function. PHP will then populate
* these into $_SESSION.
*
* When PHP shuts down, the sessionWrite() handler is called and will pass
* the $_SESSION contents already serialized (using session_encode()) to
* be stored.
*
* When a session is specifically destroyed, PHP will call the
* sessionDestroy() handler with the session ID. This happens when the
* session is regenerated for example and th handler MUST delete the
* session by ID from the persistent storage immediately.
*
* PHP will call sessionGc() from time to time to expire any session
* records according to the set max lifetime of a session. This routine
* should delete all records from persistent storage which were last
* accessed longer than the $lifetime.
*
* PHP sessionOpen() and sessionClose() are pretty much redundant and
* can return true.
*
* @author Drak <drak@zikula.org>
*/
interface SessionSaveHandlerInterface
{
/**
* Open session.
*
* This method is for internal use by PHP and must not be called manually.
*
* @param string $savePath Save path.
* @param string $sessionName Session Name.
*
* @throws \RuntimeException If something goes wrong starting the session.
*
* @return boolean
*/
function sessionOpen($savePath, $sessionName);
/**
* Close session.
*
* This method is for internal use by PHP and must not be called manually.
*
* @return boolean
*/
function sessionClose();
/**
* Read session.
*
* This method is for internal use by PHP and must not be called manually.
*
* This method is called by PHP itself when the session is started.
* This method should retrieve the session data from storage by the
* ID provided by PHP. Return the string directly as is from storage.
* If the record was not found you must return an empty string.
*
* The returned data will be unserialized automatically by PHP using a
* special unserializer method session_decode() and the result will be used
* to populate the $_SESSION superglobal. This is done automatically and
* is not configurable.
*
* @param string $sessionId Session ID.
*
* @throws \RuntimeException On fatal error but not "record not found".
*
* @return string String as stored in persistent storage or empty string in all other cases.
*/
function sessionRead($sessionId);
/**
* Commit session to storage.
*
* This method is for internal use by PHP and must not be called manually.
*
* PHP will call this method when the session is closed. It sends
* the session ID and the contents of $_SESSION to be saved in a lightweight
* serialized format (which PHP does automatically using session_encode()
* which should be stored exactly as is given in $data.
*
* Note this method is normally called by PHP after the output buffers
* have been closed.
*
* @param string $sessionId Session ID.
* @param string $data Session serialized data to save.
*
* @throws \RuntimeException On fatal error.
*
* @return boolean
*/
function sessionWrite($sessionId, $data);
/**
* Destroys this session.
*
* This method is for internal use by PHP and must not be called manually.
*
* PHP will call this method when the session data associated
* with the session ID provided needs to be immediately
* deleted from the permanent storage.
*
* @param string $sessionId Session ID.
*
* @throws \RuntimeException On fatal error.
*
* @return boolean
*/
function sessionDestroy($sessionId);
/**
* Garbage collection for storage.
*
* This method is for internal use by PHP and must not be called manually.
*
* This method is called by PHP periodically and passes the maximum
* time a session can exist for before being deleted from permanent storage.
*
* @param integer $lifetime Max lifetime in seconds to keep sessions stored.
*
* @throws \RuntimeException On fatal error.
*
* @return boolean
*/
function sessionGc($lifetime);
}

View File

@ -11,10 +11,14 @@
namespace Symfony\Component\HttpFoundation\SessionStorage;
use Symfony\Component\HttpFoundation\FlashBagInterface;
use Symfony\Component\HttpFoundation\AttributeBagInterface;
/**
* SessionStorageInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Drak <drak@zikula.org>
*
* @api
*/
@ -23,6 +27,10 @@ interface SessionStorageInterface
/**
* Starts the session.
*
* @throws \RuntimeException If something goes wrong starting the session.
*
* @return boolean True if started.
*
* @api
*/
function start();
@ -30,61 +38,20 @@ interface SessionStorageInterface
/**
* Returns the session ID
*
* @return mixed The session ID
*
* @throws \RuntimeException If the session was not started yet
* @return mixed The session ID or false if the session has not started.
*
* @api
*/
function getId();
/**
* Reads 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
*
* @throws \RuntimeException If an error occurs while reading data from this storage
*
* @api
*/
function read($key);
/**
* Removes 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
*
* @throws \RuntimeException If an error occurs while removing data from this storage
*
* @api
*/
function remove($key);
/**
* Writes 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
*
* @throws \RuntimeException If an error occurs while writing to this storage
*
* @api
*/
function write($key, $data);
/**
* Regenerates id that represents this storage.
*
* This method must invoke session_regenerate_id($destroy) unless
* this interface is used for a storage object designed for unit
* or functional testing where a real PHP session would interfere
* with testing.
*
* @param Boolean $destroy Destroy session when regenerating?
*
* @return Boolean True if session regenerated, false if error
@ -94,4 +61,33 @@ interface SessionStorageInterface
* @api
*/
function regenerate($destroy = false);
/**
* Force the session to be saved and closed.
*
* This method must invoke session_write_close() unless this interface is
* used for a storage object design for unit or functional testing where
* a real PHP session would interfere with testing, in which case it
* it should actually persist the session data if required.
*/
function save();
/**
* Clear all session data in memory.
*/
function clear();
/**
* Gets the FlashBagInterface driver.
*
* @return FlashBagInterface
*/
function getFlashes();
/**
* Gets the AttributeBagInterface driver.
*
* @return AttributeBagInterface
*/
function getAttributes();
}

View File

@ -0,0 +1,118 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\SessionStorage;
use Symfony\Component\HttpFoundation\AttributeBag;
use Symfony\Component\HttpFoundation\FlashBag;
use Symfony\Component\HttpFoundation\SessionStorage\AbstractSessionStorage;
use Symfony\Component\HttpFoundation\SessionStorage\SessionSaveHandlerInterface;
/**
* Turn AbstractSessionStorage into something concrete because
* certain mocking features are broken in PHPUnit 3.6.4
*/
class ConcreteSessionStorage extends AbstractSessionStorage
{
}
class CustomHandlerSessionStorage extends AbstractSessionStorage implements SessionSaveHandlerInterface
{
public function sessionOpen($path, $id)
{
}
public function sessionClose()
{
}
public function sessionRead($id)
{
}
public function sessionWrite($id, $data)
{
}
public function sessionDestroy($id)
{
}
public function sessionGc($lifetime)
{
}
}
/**
* Test class for AbstractSessionStorage.
*
* @author Drak <drak@zikula.org>
*
* These tests require separate processes.
*
* @runTestsInSeparateProcesses
*/
class AbstractSessionStorageTest extends \PHPUnit_Framework_TestCase
{
/**
* @return AbstractSessionStorage
*/
protected function getStorage()
{
return new CustomHandlerSessionStorage(new AttributeBag(), new FlashBag());
}
public function testGetFlashBag()
{
$storage = $this->getStorage();
$this->assertInstanceOf('Symfony\Component\HttpFoundation\FlashBagInterface', $storage->getFlashes());
}
public function testGetAttributeBag()
{
$storage = $this->getStorage();
$this->assertInstanceOf('Symfony\Component\HttpFoundation\AttributeBagInterface', $storage->getAttributes());
}
public function testGetId()
{
$storage = $this->getStorage();
$this->assertEquals('', $storage->getId());
$storage->start();
$this->assertNotEquals('', $storage->getId());
}
public function testRegenerate()
{
$storage = $this->getStorage();
$storage->start();
$id = $storage->getId();
$storage->getAttributes()->set('lucky', 7);
$storage->regenerate();
$this->assertNotEquals($id, $storage->getId());
$this->assertEquals(7, $storage->getAttributes()->get('lucky'));
}
public function testRegenerateDestroy()
{
$storage = $this->getStorage();
$storage->start();
$id = $storage->getId();
$storage->getAttributes()->set('legs', 11);
$storage->regenerate(true);
$this->assertNotEquals($id, $storage->getId());
$this->assertEquals(11, $storage->getAttributes()->get('legs'));
}
public function testCustomSaveHandlers()
{
$storage = new CustomHandlerSessionStorage(new AttributeBag(), new FlashBag());
$this->assertEquals('user', ini_get('session.save_handler'));
}
public function testNativeSaveHandlers()
{
$storage = new ConcreteSessionStorage(new AttributeBag(), new FlashBag());
$this->assertNotEquals('user', ini_get('session.save_handler'));
}
}

View File

@ -12,6 +12,10 @@
namespace Symfony\Tests\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\Session;
use Symfony\Component\HttpFoundation\FlashBag;
use Symfony\Component\HttpFoundation\FlashBagInterface;
use Symfony\Component\HttpFoundation\AttributeBag;
use Symfony\Component\HttpFoundation\AttributeBagInterface;
use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage;
/**
@ -19,16 +23,24 @@ use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Drak <drak@zikula.org>
*/
class SessionTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \Symfony\Component\HttpFoundation\SessionStorage\SessionStorageInterface
*/
protected $storage;
/**
* @var \Symfony\Component\HttpFoundation\SessionInterface
*/
protected $session;
public function setUp()
{
$this->storage = new ArraySessionStorage();
$this->session = $this->getSession();
$this->storage = new ArraySessionStorage(new AttributeBag(), new FlashBag());
$this->session = new Session($this->storage);
}
protected function tearDown()
@ -37,99 +49,106 @@ class SessionTest extends \PHPUnit_Framework_TestCase
$this->session = null;
}
public function testFlash()
public function testStart()
{
$this->session->clearFlashes();
$this->assertSame(array(), $this->session->getFlashes());
$this->assertFalse($this->session->hasFlash('foo'));
$this->session->setFlash('foo', 'bar');
$this->assertTrue($this->session->hasFlash('foo'));
$this->assertSame('bar', $this->session->getFlash('foo'));
$this->session->removeFlash('foo');
$this->assertFalse($this->session->hasFlash('foo'));
$flashes = array('foo' => 'bar', 'bar' => 'foo');
$this->session->setFlashes($flashes);
$this->assertSame($flashes, $this->session->getFlashes());
$this->assertEquals('', $this->session->getId());
$this->assertTrue($this->session->start());
$this->assertNotEquals('', $this->session->getId());
}
public function testFlashesAreFlushedWhenNeeded()
public function testGet()
{
$this->session->setFlash('foo', 'bar');
$this->session->save();
$this->session = $this->getSession();
$this->assertTrue($this->session->hasFlash('foo'));
$this->session->save();
$this->session = $this->getSession();
$this->assertFalse($this->session->hasFlash('foo'));
}
public function testAll()
{
$this->assertFalse($this->session->has('foo'));
// tests defaults
$this->assertNull($this->session->get('foo'));
$this->session->set('foo', 'bar');
$this->assertTrue($this->session->has('foo'));
$this->assertSame('bar', $this->session->get('foo'));
$this->session = $this->getSession();
$this->session->remove('foo');
$this->session->set('foo', 'bar');
$this->session->remove('foo');
$this->assertFalse($this->session->has('foo'));
$attrs = array('foo' => 'bar', 'bar' => 'foo');
$this->session = $this->getSession();
$this->session->replace($attrs);
$this->assertSame($attrs, $this->session->all());
$this->session->clear();
$this->assertSame(array(), $this->session->all());
$this->assertEquals(1, $this->session->get('foo', 1));
}
public function testMigrateAndInvalidate()
/**
* @dataProvider setProvider
*/
public function testSet($key, $value)
{
$this->session->set('foo', 'bar');
$this->session->setFlash('foo', 'bar');
$this->session->set($key, $value);
$this->assertEquals($value, $this->session->get($key));
}
$this->assertSame('bar', $this->session->get('foo'));
$this->assertSame('bar', $this->session->getFlash('foo'));
public function testReplace()
{
$this->session->replace(array('happiness' => 'be good', 'symfony' => 'awesome'));
$this->assertEquals(array('happiness' => 'be good', 'symfony' => 'awesome'), $this->session->all());
$this->session->replace(array());
$this->assertEquals(array(), $this->session->all());
}
$this->session->migrate();
/**
* @dataProvider setProvider
*/
public function testAll($key, $value, $result)
{
$this->session->set($key, $value);
$this->assertEquals($result, $this->session->all());
}
$this->assertSame('bar', $this->session->get('foo'));
$this->assertSame('bar', $this->session->getFlash('foo'));
/**
* @dataProvider setProvider
*/
public function testClear($key, $value)
{
$this->session->set('hi', 'fabien');
$this->session->set($key, $value);
$this->session->clear();
$this->assertEquals(array(), $this->session->all());
}
$this->session = $this->getSession();
public function setProvider()
{
return array(
array('foo', 'bar', array('foo' => 'bar')),
array('foo.bar', 'too much beer', array('foo.bar' => 'too much beer')),
array('great', 'symfony2 is great', array('great' => 'symfony2 is great')),
);
}
/**
* @dataProvider setProvider
*/
public function testRemove($key, $value)
{
$this->session->set('hi.world', 'have a nice day');
$this->session->set($key, $value);
$this->session->remove($key);
$this->assertEquals(array('hi.world' => 'have a nice day'), $this->session->all());
}
public function testInvalidate()
{
$this->session->set('invalidate', 123);
$this->session->addFlash('OK');
$this->session->invalidate();
$this->assertEquals(array(), $this->session->all());
$this->assertEquals(array(), $this->session->getAllFlashes());
}
$this->assertSame(array(), $this->session->all());
$this->assertSame(array(), $this->session->getFlashes());
public function testMigrate()
{
$this->session->set('migrate', 321);
$this->session->addFlash('HI');
$this->session->migrate();
$this->assertEquals(321, $this->session->get('migrate'));
$this->assertEquals(array('HI'), $this->session->getFlashes(FlashBag::NOTICE));
}
public function testMigrateDestroy()
{
$this->session->set('migrate', 333);
$this->session->addFlash('Bye');
$this->session->migrate(true);
$this->assertEquals(333, $this->session->get('migrate'));
$this->assertEquals(array('Bye'), $this->session->getFlashes(FlashBag::NOTICE));
}
public function testSerialize()
{
$this->session = new Session($this->storage);
$compare = serialize($this->storage);
$this->assertSame($compare, $this->session->serialize());
@ -142,91 +161,182 @@ class SessionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($_storage->getValue($this->session), $this->storage, 'storage match');
}
public function testSave()
/**
* @expectedException \InvalidArgumentException
*/
public function testUnserializeException()
{
$this->storage = new ArraySessionStorage();
$this->session = new Session($this->storage);
$this->session->set('foo', 'bar');
$this->session->save();
$compare = array('_symfony2' => array('attributes' => array('foo' => 'bar'), 'flashes' => array()));
$r = new \ReflectionObject($this->storage);
$p = $r->getProperty('data');
$p->setAccessible(true);
$this->assertSame($p->getValue($this->storage), $compare);
$serialized = serialize(new \ArrayObject());
$this->session->unserialize($serialized);
}
public function testGetId()
{
$this->assertNull($this->session->getId());
}
public function testStart()
{
$this->assertEquals('', $this->session->getId());
$this->session->start();
$this->assertSame(array(), $this->session->getFlashes());
$this->assertSame(array(), $this->session->all());
$this->assertNotEquals('', $this->session->getId());
}
public function testSavedOnDestruct()
/**
* @dataProvider provideFlashes
*/
public function testAddFlash($type, $flashes)
{
$this->session->set('foo', 'bar');
foreach ($flashes as $message) {
$this->session->addFlash($message, $type);
}
$this->session->__destruct();
$this->assertEquals($flashes, $this->session->getFlashes($type));
}
/**
* @dataProvider provideFlashes
*/
public function testGetFlashes($type, $flashes)
{
$this->session->setFlashes($type, $flashes);
$this->assertEquals($flashes, $this->session->getFlashes($type));
}
/**
* @dataProvider provideFlashes
*/
public function testPopFlashes($type, $flashes)
{
$this->session->setFlashes($type, $flashes);
$this->assertEquals($flashes, $this->session->popFlashes($type));
$this->assertEquals(array(), $this->session->popFlashes($type));
}
/**
* @dataProvider provideFlashes
*/
public function testPopAllFlashes($type, $flashes)
{
$this->session->setFlashes(FlashBag::NOTICE, array('First', 'Second'));
$this->session->setFlashes(FlashBag::ERROR, array('Third'));
$expected = array(
'attributes'=>array('foo'=>'bar'),
'flashes'=>array(),
FlashBag::NOTICE => array('First', 'Second'),
FlashBag::ERROR => array('Third'),
);
$saved = $this->storage->read('_symfony2');
$this->assertSame($expected, $saved);
$this->assertEquals($expected, $this->session->popAllFlashes());
$this->assertEquals(array(), $this->session->popAllFlashes());
}
public function testSavedOnDestructAfterManualSave()
public function testSetFlashes()
{
$this->session->set('foo', 'nothing');
$this->session->save();
$this->session->set('foo', 'bar');
$this->session->setFlashes(FlashBag::NOTICE, array('First', 'Second'));
$this->session->setFlashes(FlashBag::ERROR, array('Third'));
$this->assertEquals(array('First', 'Second'), $this->session->getFlashes(FlashBag::NOTICE, false));
$this->assertEquals(array('Third'), $this->session->getFlashes(FlashBag::ERROR, false));
}
$this->session->__destruct();
/**
* @dataProvider provideFlashes
*/
public function testHasFlashes($type, $flashes)
{
$this->assertFalse($this->session->hasFlashes($type));
$this->session->setFlashes($type, $flashes);
$this->assertTrue($this->session->hasFlashes($type));
}
$expected = array(
'attributes'=>array('foo'=>'bar'),
'flashes'=>array(),
/**
* @dataProvider provideFlashes
*/
public function testGetFlashKeys($type, $flashes)
{
$this->assertEquals(array(), $this->session->getFlashKeys());
$this->session->setFlashes($type, $flashes);
$this->assertEquals(array($type), $this->session->getFlashKeys());
}
public function testGetFlashKeysBulk()
{
$this->loadFlashes();
$this->assertEquals(array(
FlashBag::NOTICE, FlashBag::ERROR, FlashBag::WARNING, FlashBag::INFO), $this->session->getFlashKeys()
);
$saved = $this->storage->read('_symfony2');
$this->assertSame($expected, $saved);
}
public function testStorageRegenerate()
public function testGetAllFlashes()
{
$this->storage->write('foo', 'bar');
$this->assertEquals(array(), $this->session->getAllFlashes());
$this->assertTrue($this->storage->regenerate());
$this->session->addFlash('a', FlashBag::NOTICE);
$this->assertEquals(array(
FlashBag::NOTICE => array('a')
), $this->session->getAllFlashes()
);
$this->assertEquals('bar', $this->storage->read('foo'));
$this->session->addFlash('a', FlashBag::ERROR);
$this->assertEquals(array(
FlashBag::NOTICE => array('a'),
FlashBag::ERROR => array('a'),
), $this->session->getAllFlashes());
$this->assertTrue($this->storage->regenerate(true));
$this->session->addFlash('a', FlashBag::WARNING);
$this->assertEquals(array(
FlashBag::NOTICE => array('a'),
FlashBag::ERROR => array('a'),
FlashBag::WARNING => array('a'),
), $this->session->getAllFlashes()
);
$this->assertNull($this->storage->read('foo'));
$this->session->addFlash('a', FlashBag::INFO);
$this->assertEquals(array(
FlashBag::NOTICE => array('a'),
FlashBag::ERROR => array('a'),
FlashBag::WARNING => array('a'),
FlashBag::INFO => array('a'),
), $this->session->getAllFlashes()
);
$this->assertEquals(array(
FlashBag::NOTICE => array('a'),
FlashBag::ERROR => array('a'),
FlashBag::WARNING => array('a'),
FlashBag::INFO => array('a'),
), $this->session->getAllFlashes()
);
}
public function testStorageRemove()
/**
* @dataProvider provideFlashes
*/
public function testClearFlashes($type, $flashes)
{
$this->storage->write('foo', 'bar');
$this->assertEquals('bar', $this->storage->read('foo'));
$this->storage->remove('foo');
$this->assertNull($this->storage->read('foo'));
$this->session->setFlashes($type, $flashes);
$this->session->clearFlashes($type);
$this->assertEquals(array(), $this->session->getFlashes($type));
}
protected function getSession()
public function testClearAllFlashes()
{
return new Session($this->storage);
$this->loadFlashes();
$this->assertNotEquals(array(), $this->session->getAllFlashes());
$this->session->clearAllFlashes();
$this->assertEquals(array(), $this->session->getAllFlashes());
}
protected function loadFlashes()
{
$flashes = $this->provideFlashes();
foreach ($flashes as $data) {
$this->session->setFlashes($data[0], $data[1]);
}
}
public function provideFlashes()
{
return array(
array(FlashBag::NOTICE, array('a', 'b', 'c')),
array(FlashBag::ERROR, array('d', 'e', 'f')),
array(FlashBag::WARNING, array('g', 'h', 'i')),
array(FlashBag::INFO, array('j', 'k', 'l')),
);
}
}