From a6ce6c8f920e9c2fb7996221c93094d817385b95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Terje=20Br=C3=A5ten?= Date: Sun, 16 Dec 2012 23:38:20 +0100 Subject: [PATCH] Do not start the session unless it is (or has been) written to --- .../Session/Attribute/EmptyAttributeBag.php | 114 +++++++++++ .../HttpFoundation/Session/EmptyBag.php | 133 ++++++++++++ .../Session/Flash/EmptyFlashBag.php | 147 ++++++++++++++ .../HttpFoundation/Session/Session.php | 12 ++ .../Session/SessionInterface.php | 13 ++ .../Session/Storage/EmptyStorage.php | 192 ++++++++++++++++++ .../Session/Storage/EmptyStorageInterface.php | 26 +++ .../Storage/MockArraySessionStorage.php | 10 + .../Session/Storage/NativeSessionStorage.php | 12 ++ .../Storage/SessionStorageInterface.php | 13 ++ 10 files changed, 672 insertions(+) create mode 100644 src/Symfony/Component/HttpFoundation/Session/Attribute/EmptyAttributeBag.php create mode 100644 src/Symfony/Component/HttpFoundation/Session/EmptyBag.php create mode 100644 src/Symfony/Component/HttpFoundation/Session/Flash/EmptyFlashBag.php create mode 100644 src/Symfony/Component/HttpFoundation/Session/Storage/EmptyStorage.php create mode 100644 src/Symfony/Component/HttpFoundation/Session/Storage/EmptyStorageInterface.php diff --git a/src/Symfony/Component/HttpFoundation/Session/Attribute/EmptyAttributeBag.php b/src/Symfony/Component/HttpFoundation/Session/Attribute/EmptyAttributeBag.php new file mode 100644 index 0000000000000..1eac39887d3fb --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Attribute/EmptyAttributeBag.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\EmptyBag; +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\EmptyStorageInterface; + +/** + * A wrapper class for an empty attribute bag + * + * @author Terje Bråten + */ +class EmptyAttributeBag extends EmptyBag implements AttributeBagInterface, \IteratorAggregate, \Countable +{ + /** + * {@inheritdoc} + */ + public function has($name) + { + if ($this->isEmpty) { + return false; + } + + return $this->realBag->has($name); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + if ($this->isEmpty) { + return $default; + } + + return $this->realBag->get($name, $default); + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $this->startSession(); + $this->realBag->set($name, $value); + } + + /** + * {@inheritdoc} + */ + public function all() + { + if ($this->isEmpty) { + return array(); + } + + return $this->realBag->all(); + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->startSession(); + $this->realBag->replace($attributes); + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + if ($this->isEmpty) { + return null; + } + + return $this->realBag->remove($name); + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->all()); + } + + /** + * Returns the number of attributes. + * + * @return int The number of attributes + */ + public function count() + { + if ($this->isEmpty) { + return 0; + } + + return $this->realBag->count(); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/EmptyBag.php b/src/Symfony/Component/HttpFoundation/Session/EmptyBag.php new file mode 100644 index 0000000000000..e8aab057bc732 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/EmptyBag.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; +use Symfony\Component\HttpFoundation\Session\Attribute\EmptyAttributeBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\Flash\EmptyFlashBag; +use Symfony\Component\HttpFoundation\Session\Storage\EmptyStorageInterface; + +/** + * Abstract base class for Empty Session bags + * + * @author Terje Bråten + */ +class EmptyBag implements SessionBagInterface +{ + /** + * Flag if the bag is still empty + * + * @var boolean $isEmpty + */ + protected $isEmpty = true; + + /** + * The empty storage containing this bag + * + * @var EmptyStorageInterface $storage + */ + protected $storage; + + /** + * The session bag this empty bag is a proxy for + * + * @var SessionBagInterface $realBag + */ + protected $realBag; + + /** + * Constructor. + * + * @param EmptyStorageInterface $storage + * @param SessionBagInterface $realBag + */ + public function __construct(EmptyStorageInterface $storage, SessionBagInterface $realBag) + { + $this->storage = $storage; + $this->realBag = $realBag; + } + + /** + * Create a new empty bag + * + * @param EmptyStorageInterface $storage + * @param SessionBagInterface $realBag + */ + public static function create(EmptyStorageInterface $storage, + SessionBagInterface $realBag) + { + if ($realBag instanceof AttributeBagInterface) { + return new EmptyAttributeBag($storage, $realBag); + } + if ($realBag instanceof FlashBagInterface) { + return new EmptyFlashBag($storage, $realBag); + } + + throw new \LogicException('Unknown bag interface'); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->realBag->getName(); + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$array) + { + $this->startSession(); + + return $this->realBag->initialize($array); + } + + /** + * Start the session + * Something has been written to the bag, + * and the storage must be informed + */ + protected function startSession() + { + if ($this->isEmpty) { + $this->isEmpty = false; + $this->realBag = $this->storage->getRealBag($this->getName()); + } + } + + /** + * Gets the storage key for this bag. + * + * @return string + */ + public function getStorageKey() + { + return $this->realBag->getStorageKey(); + } + + /** + * Clears out data from bag. + * + * @return mixed Whatever data was contained. + */ + public function clear() + { + if ($this->isEmpty) { + return array(); + } + + return $this->realBag->clear(); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Flash/EmptyFlashBag.php b/src/Symfony/Component/HttpFoundation/Session/Flash/EmptyFlashBag.php new file mode 100644 index 0000000000000..7194058adc034 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Flash/EmptyFlashBag.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\EmptyBag; +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\EmptyStorageInterface; + +/** + * A wrapper class for an empty FlashBag flash message container. + * + * @author Terje Bråten + */ +class EmptyFlashBag extends EmptyBag implements FlashBagInterface, \IteratorAggregate, \Countable +{ + /** + * {@inheritdoc} + */ + public function add($type, $message) + { + $this->startSession(); + $this->realBag->add($type, $message); + } + + /** + * {@inheritdoc} + */ + public function peek($type, array $default = array()) + { + if ($this->isEmpty) { + return $default; + } + + return $this->realBag->peek($type, $default); + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + if ($this->isEmpty) { + return array(); + } + + return $this->realBag->peekAll(); + } + + /** + * {@inheritdoc} + */ + public function get($type, array $default = array()) + { + if ($this->isEmpty) { + return $default; + } + + return $this->realBag->get($type, $default); + } + + /** + * {@inheritdoc} + */ + public function all() + { + if ($this->isEmpty) { + return array(); + } + + return $this->realBag->all(); + } + + /** + * {@inheritdoc} + */ + public function set($type, $messages) + { + $this->startSession(); + $this->realBag->set($type, $messages); + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->startSession(); + $this->realBag->setAll($messages); + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + if ($this->isEmpty) { + return false; + } + + return $this->realBag->has($type); + } + + /** + * {@inheritdoc} + */ + public function keys() + { + if ($this->isEmpty) { + return array(); + } + + return $this->realBag->keys(); + } + + /** + * Returns an iterator for flashes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->all()); + } + + /** + * Returns the number of flashes. + * + * @return int The number of flashes + */ + public function count() + { + if ($this->isEmpty) { + return 0; + } + + return $this->realBag->count(); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Session.php b/src/Symfony/Component/HttpFoundation/Session/Session.php index b0b3ff3d0bff0..e041b21164c59 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Session.php +++ b/src/Symfony/Component/HttpFoundation/Session/Session.php @@ -18,6 +18,7 @@ use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; use Symfony\Component\HttpFoundation\Session\SessionBagInterface; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\EmptyStorage; /** * Session. @@ -56,6 +57,9 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) { $this->storage = $storage ?: new NativeSessionStorage(); + if (!$this->wasStarted()) { + $this->storage = new EmptyStorage($this->storage); + } $attributes = $attributes ?: new AttributeBag(); $this->attributeName = $attributes->getName(); @@ -138,6 +142,14 @@ public function isStarted() return $this->storage->isStarted(); } + /** + * {@inheritdoc} + */ + public function wasStarted() + { + return $this->storage->wasStarted(); + } + /** * Returns an iterator for attributes. * diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php b/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php index a94fad00d6f0f..36a7032d74fea 100644 --- a/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php +++ b/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php @@ -183,6 +183,19 @@ public function clear(); */ public function isStarted(); + /** + * Checks if the session was started in an earlier request. + * Will also always return true if the session has already been + * started in this request. + * When returning false it is taken as a guarantee that if the session + * is to be started in this request it will be a fresh and empty session. + * + * @return boolean True if the session was started earlier + * (and may contain data), + * False if the session has not been started earlier. + */ + public function wasStarted(); + /** * Registers a SessionBagInterface with the session. * diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/EmptyStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/EmptyStorage.php new file mode 100644 index 0000000000000..c6236c1139eb9 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/EmptyStorage.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\EmptyBag; + +/** + * This is a wrapper class for the session storage when it is empty + * + * @author Terje Bråten + */ +class EmptyStorage implements EmptyStorageInterface +{ + /** + * The real storage that this class is a wrapper for + * + * @var SessionStorageInterface + */ + protected $realStorage; + + /** + * @var Boolean + */ + protected $isEmpty = true; + + /** + * Array of SessionBagInterface + * + * @var SessionBagInterface[] + */ + protected $bags; + + /** + * Constructor. + * + * @param SessionStorageInterface $realStorage The real stoarge object + */ + public function __construct(SessionStorageInterface $realStorage) + { + $this->realStorage = $realStorage; + $this->isEmpty = true; + } + + /** + * {@inheritdoc} + */ + public function start() + { + $this->isEmpty = false; + + return $this->realStorage->start(); + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + if ($this->isEmpty) { + return false; + } + + return $this->realStorage->isStarted(); + } + + /** + * {@inheritdoc} + */ + public function wasStarted() + { + return $this->realStorage->wasStarted(); + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->realStorage->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + $this->realStorage->setId($id); + if ($this->isEmpty && $this->realStorage->wasStarted()) { + $this->isEmpty = false; + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->realStorage->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->realStorage->setName($name); + if ($this->isEmpty && $this->realStorage->wasStarted()) { + $this->isEmpty = false; + } + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + return $this->realStorage->regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $this->realStorage->save(); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + if ($this->isEmpty) { + return; + } + + $this->realStorage->clear(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->realStorage->registerBag($bag); + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + if (!$this->isEmpty) { + return $this->realStorage->getBag($name); + } + + if (!array_key_exists($name, $this->bags)) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); + } + + return EmptyBag::create($this, $this->bags[$name]); + } + + /** + * {@inheritdoc} + */ + public function getRealBag($name) + { + $this->isEmpty = false; + + return $this->realStorage->getBag($name); + } + + /** + * {@inheritdoc} + */ + public function getMetadataBag() + { + return $this->realStorage->getMetadataBag(); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/EmptyStorageInterface.php b/src/Symfony/Component/HttpFoundation/Session/Storage/EmptyStorageInterface.php new file mode 100644 index 0000000000000..3a78cb3915670 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/EmptyStorageInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * EmptyStorageInterface. + * + * @author Terje Bråten + */ +interface EmptyStorageInterface extends SessionStorageInterface +{ + /** + * Used by the empty bag to signal that we now need to get the real bag + * when the time is right (somthing has been written to the bag) + */ + public function getRealBag($name); +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php index a1fcf539f8fda..846bf7ecdcdb5 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php @@ -215,6 +215,16 @@ public function isStarted() return $this->started; } + /** + * {@inheritdoc} + */ + public function wasStarted() + { + // No need to optimize the use of this storage + // so we just always return true + return true; + } + /** * Sets the MetadataBag. * diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php index f23ca3a7fd3f5..b01b5d1754799 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -289,6 +289,18 @@ public function getMetadataBag() return $this->metadataBag; } + /** + * {@inheritdoc} + */ + public function wasStarted() + { + if ($this->started) { + return true; + } + + return array_key_exists($this->getName(), $_COOKIE); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php index 711eaa29a3125..b6baea4491f07 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php @@ -42,6 +42,19 @@ public function start(); */ public function isStarted(); + /** + * Checks if the session was started in an earlier request. + * Will also always return true if the session has already been + * started in this request. + * When returning false it is taken as a guarantee that if the session + * is to be started in this request it will be a fresh and empty session. + * + * @return boolean True if the session was started earlier + * (and may contain data), + * False if the session has not been started earlier. + */ + public function wasStarted(); + /** * Returns the session ID *