diff --git a/CHANGELOG-2.1.md b/CHANGELOG-2.1.md index bdce39f9250a5..e797ba7d5cb9d 100644 --- a/CHANGELOG-2.1.md +++ b/CHANGELOG-2.1.md @@ -33,6 +33,11 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c * [BC BREAK] assets_base_urls and base_urls merging strategy has changed * changed the default profiler storage to use the filesystem instead of SQLite * added support for placeholders in route defaults and requirements (replaced by the value set in the service container) + * [BC BREAK] changed `session.xml` service name `session.storage.native` to `session.storage.native_file` + * added new session storage drivers to session.xml: `session.storage.native_memcache`, `session.storage.native_memcached`, + `session.storage.native_sqlite`, `session.storage.null`, `session.storage.memcache`, + and `session.storage.memcached`. Added `session.storage.functional_test.file` service for functional session testing. + * removed `session.storage.filesystem` service. ### SecurityBundle @@ -143,6 +148,33 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3 * added ResponseHeaderBag::makeDisposition() (implements RFC 6266) * made mimetype to extension conversion configurable + * [BC BREAK] Moved flash messages out of the `Session` class and into `FlashBagInterface`. + Flashes are now stored as a bucket of messages per `$type` so there can be multiple messages per type. + There are four interface constants for type, `FlashBagInterface::INFO`, `FlashBagInterface::NOTICE`, + `FlashBagInterface::WARNING` and `FlashBagInterface::ERROR`. + * Flash messages are expired when retrieved (with $clear = true) set. This makes the implementation + more flexible and removed some dependencies in the Session management cycle. + * [BC BREAK] Removed the following methods from the Session class: `setFlashes()` + `setFlash()`, `hasFlash()`, `removeFlash()`, and `clearFlashes()`. + * [BC BREAK] Changed `getFlash($clear=false)` now returns flash messages for display, and added + `addFlash($message, $type)` to add flash messages. + `getFlashes()` now returns the `FlashBagInterface` for which there which can be used for deeper + manipulation of the flash message collection. + * `Session` object takes two additional object in the constructor: `AttributeBagInterface` and + `FlashBagInterface` after the `SessionStorageInterface`. + * Added `AbstractSessionStorage` base class for session storage drivers. + * Added `SessionSaveHandler` interface which storage drivers should implement after inheriting from + `AbstractSessionStorage` when writing custom session save handlers. + * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and `remove()`. Added + `getAttributes()`, `getFlashes()`. + * Moved attribute storage to `AttributeBagInterface`. + * Added `AttributeBag` to replicate attributes storage behaviour from 2.0.x + * Added `NamespacedAttributeBag` for namespace session attributes. + * Session now implements `SessionInterface` making implementation customizable and portable. + * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionStorage` + * Added session storage drivers for PHP native Memcache, Memcached and SQLite session save handlers. + * Added session storage drivers for custom Memcache, Memcached and Null session save handlers. + * Removed `FilesystemSessionStorage`, use `FunctionalTestFileSessionStorage` for functional testing instead. ### HttpKernel @@ -169,7 +201,7 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c ### Serializer - * [BC BREAK] convert the `item` XML tag to an array + * [BC BREAK] convert the `item` XML tag to an array ``` xml diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md index a1560677f991e..69e9e2d7067ac 100644 --- a/UPGRADE-2.1.md +++ b/UPGRADE-2.1.md @@ -11,7 +11,7 @@ UPGRADE FROM 2.0 to 2.1 and/or share a common base configuration (i.e. ``config.yml``), merging could yield a set of base URL's for multiple environments. -* moved management of the locale from the Session class to the Request class +* [HttpFoundation] - moved management of the locale from the Session class to the Request class Configuring the default locale: @@ -40,3 +40,63 @@ UPGRADE FROM 2.0 to 2.1 Before: $session->getLocale() After: $request->getLocale() + +* [HttpFoundation] Flash Messages. Moved to own bucket and returns and array based on type. + + Before (PHP): + + hasFlash('notice')): ?> +
+ getFlash('notice') ?> +
+ + + After (PHP): + + flashGet(Symfony\Component\HttpFoundation\FlashBag::NOTICE) as $notice): ?> +
+ +
+ + +.. note:: + + You can of course declare `` at the beginning + of the PHP template so you can use the shortcut `FlashBag::NOTICE`. + + Before (Twig): + + {% if app.session.hasFlash('notice') %} +
+ {{ app.session.flash('notice') }} +
+ {% endif %} + + After (Twig): + + {% for flashMessage in app.session.flashGet(constant(Symfony\Component\HttpFoundation\FlashBag::NOTICE)) %} +
+ {{ flashMessage }} +
+ {% endforeach %} + +* [HttpFoundation] Session object now requires two additional constructor arguments but will default to + sensible defaults for convenience. The methods, `setFlashes()`, `setFlash()`, `hasFlash()`, + `removeFlash()`, and `clearFlashes()` have all been removed from the `Session` object. + You may use `addFlash()` to add flashes. `getFlash()` now returns an array for display. + `getFlashes()` returns the FlashBagInterface if you need to deeply manipulate the flash message + container. + +* [HttpFoundation] Session storage drivers should inherit from + `Symfony\Component\HttpFoundation\SessionStorage\AbstractSessionStorage` + and no longer should implement `read()`, `write()`, `remove()` which were removed from the + `SessionStorageInterface`. + +* [HttpFoundation] Any session storage drive that wants to use custom save handlers should + implement `Symfony\Component\HttpFoundation\SessionStorage\SessionSaveHandlerInterface` + +* [FrameworkBundle] The service session.storage.native is now called `session.storage.native_file` + +* [FrameworkBundle] The service `session.storage.filesystem` is deprecated and should be replaced + `session.storage.native_file` + diff --git a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php index 6fa324f85e5ec..694eae34eb895 100644 --- a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php +++ b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php @@ -3,7 +3,10 @@ namespace Symfony\Bridge\Doctrine\HttpFoundation; use Doctrine\DBAL\Platforms\MySqlPlatform; -use Symfony\Component\HttpFoundation\SessionStorage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\AttributeBagInterface; +use Symfony\Component\HttpFoundation\FlashBagInterface; +use Symfony\Component\HttpFoundation\SessionStorage\AbstractSessionStorage; +use Symfony\Component\HttpFoundation\SessionStorage\SessionSaveHandlerInterface; use Doctrine\DBAL\Driver\Connection; /** @@ -12,39 +15,32 @@ * @author Fabien Potencier * @author Johannes M. Schmitt */ -class DbalSessionStorage extends NativeSessionStorage +class DbalSessionStorage extends AbstractSessionStorage implements SessionSaveHandlerInterface { + /** + * @var Connection + */ private $con; + + /** + * @var string + */ private $tableName; - public function __construct(Connection $con, $tableName = 'sessions', array $options = array()) + /** + * + * @param Connection $con An instance of Connection. + * @param string $tableName Table name. + * @param array $options Session configuration options + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag) + */ + public function __construct(Connection $con, $tableName = 'sessions', array $options = array(), AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) { - parent::__construct($options); - $this->con = $con; $this->tableName = $tableName; - } - - /** - * Starts the session. - */ - public function start() - { - if (self::$sessionStarted) { - return; - } - // use this object as the session handler - session_set_save_handler( - array($this, 'sessionOpen'), - array($this, 'sessionClose'), - array($this, 'sessionRead'), - array($this, 'sessionWrite'), - array($this, 'sessionDestroy'), - array($this, 'sessionGC') - ); - - parent::start(); + parent::__construct($attributes, $flashes, $options); } /** @@ -102,7 +98,7 @@ public function sessionDestroy($id) * * @throws \RuntimeException If any old sessions cannot be cleaned */ - public function sessionGC($lifetime) + public function sessionGc($lifetime) { try { $this->con->executeQuery("DELETE FROM {$this->tableName} WHERE sess_time < :time", array( diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 5762fb4c337be..8875a8677184b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -162,7 +162,7 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) ->canBeUnset() ->children() ->booleanNode('auto_start')->defaultFalse()->end() - ->scalarNode('storage_id')->defaultValue('session.storage.native')->end() + ->scalarNode('storage_id')->defaultValue('session.storage.native_file')->end() ->scalarNode('name')->end() ->scalarNode('lifetime')->end() ->scalarNode('path')->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.php b/src/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.php index 7d6ac49a069c2..243f4441e7478 100644 --- a/src/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.php +++ b/src/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.php @@ -69,10 +69,7 @@ public function onKernelResponse(FilterResponseEvent $event) if ($session = $event->getRequest()->getSession()) { $session->save(); - $session->close(); - $params = session_get_cookie_params(); - $event->getResponse()->headers->setCookie(new Cookie(session_name(), session_id(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'])); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml index c3c2eba0c5605..0a66e4518c0b4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml @@ -6,8 +6,19 @@ Symfony\Component\HttpFoundation\Session - Symfony\Component\HttpFoundation\SessionStorage\NativeSessionStorage - Symfony\Component\HttpFoundation\SessionStorage\FilesystemSessionStorage + Symfony\Component\HttpFoundation\FlashBag + Symfony\Component\HttpFoundation\AttributeBag + Symfony\Component\HttpFoundation\SessionStorage\NativeFileSessionStorage + Symfony\Component\HttpFoundation\SessionStorage\NullSessionStorage + Symfony\Component\HttpFoundation\SessionStorage\NativeMemcacheSessionStorage + Symfony\Component\HttpFoundation\SessionStorage\NativeMemcachedSessionStorage + Symfony\Component\HttpFoundation\SessionStorage\NativeSqliteSessionStorage + Symfony\Component\HttpFoundation\SessionStorage\MemcacheSessionStorage + Symfony\Component\HttpFoundation\SessionStorage\MemcachedSessionStorage + Symfony\Component\HttpFoundation\SessionStorage\FunctionalTestFileSessionStorage + Memcache + Memcached + Symfony\Bundle\FrameworkBundle\EventListener\SessionListener @@ -16,13 +27,65 @@ - - %session.storage.options% + + + + + + + %kernel.cache_dir%/sessions + + - + %kernel.cache_dir%/sessions %session.storage.options% + + + + + + tcp://127.0.0.1:11211?persistent=0 + %session.storage.options% + + + + + + 127.0.0.1:11211 + %session.storage.options% + + + + + + + tcp://127.0.0.1:11211?persistent=0 + %session.storage.options% + + + + + + + tcp://127.0.0.1:11211?persistent=0 + %session.storage.options% + + + + + + %kernel.cache_dir%/sf2_sqlite_sess.db + %session.storage.options% + + + + + + %session.storage.options% + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/SessionHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/SessionHelper.php index 041d9ddff9918..162f1a3f9a08a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/SessionHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/SessionHelper.php @@ -13,6 +13,7 @@ use Symfony\Component\Templating\Helper\Helper; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\FlashBagInterface; /** * SessionHelper provides read-only access to the session attributes. @@ -46,19 +47,19 @@ public function get($name, $default = null) return $this->session->get($name, $default); } - public function getFlash($name, $default = null) + public function getFlashes($type) { - return $this->session->getFlash($name, $default); + return $this->session->getFlashes()->get($type); } - public function getFlashes() + public function getAllFlashes() { - return $this->session->getFlashes(); + return $this->session->getFlashes()->all(); } - public function hasFlash($name) + public function hasFlashes($type) { - return $this->session->hasFlash($name); + return $this->session->getFlashes()->has($type); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index 01361f2280e97..3ab6488493024 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -20,7 +20,7 @@ ), 'session' => array( 'auto_start' => true, - 'storage_id' => 'session.storage.native', + 'storage_id' => 'session.storage.native_file', 'name' => '_SYMFONY', 'lifetime' => 86400, 'path' => '/', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index 2d6a06047e68d..e46a476a96910 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -12,7 +12,7 @@ - + loader.foo loader.bar diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index 3c7db0ee49dc6..126baf2d4e90b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -14,7 +14,7 @@ framework: type: xml session: auto_start: true - storage_id: session.storage.native + storage_id: session.storage.native_file name: _SYMFONY lifetime: 86400 path: / diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 90f5738737e26..b8555ae213644 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -78,7 +78,7 @@ public function testSession() $this->assertTrue($container->hasDefinition('session'), '->registerSessionConfiguration() loads session.xml'); $this->assertEquals('fr', $container->getParameter('kernel.default_locale')); $this->assertTrue($container->getDefinition('session_listener')->getArgument(1)); - $this->assertEquals('session.storage.native', (string) $container->getAlias('session.storage')); + $this->assertEquals('session.storage.native_file', (string) $container->getAlias('session.storage')); $options = $container->getParameter('session.storage.options'); $this->assertEquals('_SYMFONY', $options['name']); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/EventListener/TestSessionListenerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/EventListener/TestSessionListenerTest.php index 7c656ba7e0bc9..b2f2751414cb9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/EventListener/TestSessionListenerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/EventListener/TestSessionListenerTest.php @@ -32,7 +32,7 @@ class TestSessionListenerTest extends \PHPUnit_Framework_TestCase public function setUp() { $this->listener = new TestSessionListener($this->getMock('Symfony\Component\DependencyInjection\ContainerInterface')); - $this->session = $this->getSession(); + $this->session = $this->getSession(); } protected function tearDown() @@ -41,20 +41,6 @@ protected function tearDown() $this->session = null; } - public function testShouldSaveMasterRequestSession() - { - $this->sessionMustBeSaved(); - - $this->filterResponse(new Request()); - } - - public function testShouldNotSaveSubRequestSession() - { - $this->sessionMustNotBeSaved(); - - $this->filterResponse(new Request(), HttpKernelInterface::SUB_REQUEST); - } - public function testDoesNotDeleteCookieIfUsingSessionLifetime() { $params = session_get_cookie_params(); @@ -80,22 +66,33 @@ private function filterResponse(Request $request, $type = HttpKernelInterface::M return $response; } + public function testShouldSaveMasterRequestSession() + { + $this->sessionMustBeSaved(); + $this->filterResponse(new Request()); + } + + public function testShouldNotSaveSubRequestSession() + { + $this->sessionMustNotBeSaved(); + $this->filterResponse(new Request(), HttpKernelInterface::SUB_REQUEST); + } + private function sessionMustNotBeSaved() { - $this->session->expects($this->never()) - ->method('save'); + $this->session->expects($this->never())->method('save'); } private function sessionMustBeSaved() { - $this->session->expects($this->once()) - ->method('save'); + $this->session->expects($this->once())->method('save'); } private function getSession() { return $this->getMockBuilder('Symfony\Component\HttpFoundation\Session') - ->disableOriginalConstructor() - ->getMock(); + ->disableOriginalConstructor() + ->getMock(); } } + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/SessionHelperTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/SessionHelperTest.php index ee400cd7dc135..a27012af135dc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/SessionHelperTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/SessionHelperTest.php @@ -15,6 +15,8 @@ use Symfony\Component\HttpFoundation\Session; use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage; use Symfony\Bundle\FrameworkBundle\Templating\Helper\SessionHelper; +use Symfony\Component\HttpFoundation\FlashBag; +use Symfony\Component\HttpFoundation\AttributeBag; class SessionHelperTest extends \PHPUnit_Framework_TestCase { @@ -24,9 +26,9 @@ public function setUp() { $this->request = new Request(); - $session = new Session(new ArraySessionStorage()); + $session = new Session(new ArraySessionStorage(new AttributeBag(), new FlashBag())); $session->set('foobar', 'bar'); - $session->setFlash('foo', 'bar'); + $session->getFlashes()->add('bar', FlashBag::NOTICE); $this->request->setSession($session); } @@ -40,14 +42,12 @@ public function testFlash() { $helper = new SessionHelper($this->request); - $this->assertTrue($helper->hasFlash('foo')); + $this->assertTrue($helper->hasFlashes(FlashBag::NOTICE)); - $this->assertEquals('bar', $helper->getFlash('foo')); - $this->assertEquals('foo', $helper->getFlash('bar', 'foo')); + $this->assertEquals(array('bar'), $helper->getFlashes(FlashBag::NOTICE)); - $this->assertNull($helper->getFlash('foobar')); + $this->assertEquals(array(FlashBag::NOTICE => array('bar')), $helper->getAllFlashes()); - $this->assertEquals(array('foo' => 'bar'), $helper->getFlashes()); } public function testGet() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/PhpEngineTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/PhpEngineTest.php index cefc38b0efa59..4da48ea6d8636 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/PhpEngineTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/PhpEngineTest.php @@ -19,6 +19,8 @@ use Symfony\Component\Templating\TemplateNameParser; use Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Component\HttpFoundation\FlashBag; +use Symfony\Component\HttpFoundation\AttributeBag; class PhpEngineTest extends TestCase { @@ -64,7 +66,7 @@ protected function getContainer() { $container = new Container(); $request = new Request(); - $session = new Session(new ArraySessionStorage()); + $session = new Session(new ArraySessionStorage(new AttributeBag(), new FlashBag())); $request->setSession($session); $container->set('request', $request); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml index 18ed6f4d87199..4a7520946bba5 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml @@ -10,7 +10,7 @@ framework: default_locale: en session: auto_start: true - storage_id: session.storage.filesystem + storage_id: session.storage.functional_test.file services: logger: { class: Symfony\Component\HttpKernel\Log\NullLogger } diff --git a/src/Symfony/Bundle/TwigBundle/Tests/TwigEngineTest.php b/src/Symfony/Bundle/TwigBundle/Tests/TwigEngineTest.php index 77b79dd545fb5..34561faa08dbb 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/TwigEngineTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/TwigEngineTest.php @@ -13,6 +13,8 @@ use Symfony\Bundle\TwigBundle\TwigEngine; use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\HttpFoundation\FlashBag; +use Symfony\Component\HttpFoundation\AttributeBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session; use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage; @@ -71,7 +73,7 @@ protected function getContainer() { $container = new Container(); $request = new Request(); - $session = new Session(new ArraySessionStorage()); + $session = new Session(new ArraySessionStorage(new AttributeBag(), new FlashBag())); $request->setSession($session); $container->set('request', $request); diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index 3bbffd595612b..812e77eb0c6d9 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -71,11 +71,6 @@ public function onKernelResponse(FilterResponseEvent $event) } if ($response->headers->has('X-Debug-Token') && $response->isRedirect() && $this->interceptRedirects) { - if (null !== $session = $request->getSession()) { - // keep current flashes for one more request - $session->setFlashes($session->getFlashes()); - } - $response->setContent($this->templating->render('WebProfilerBundle:Profiler:toolbar_redirect.html.twig', array('location' => $response->headers->get('Location')))); $response->setStatusCode(200); $response->headers->remove('Location'); diff --git a/src/Symfony/Component/HttpFoundation/AttributeBag.php b/src/Symfony/Component/HttpFoundation/AttributeBag.php new file mode 100644 index 0000000000000..32934aa16a6b8 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/AttributeBag.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * This class relates to session attribute storage + */ +class AttributeBag implements AttributeBagInterface +{ + /** + * @var boolean + */ + private $initialized = false; + + /** + * @var string + */ + private $storageKey; + + /** + * @var array + */ + protected $attributes = array(); + + /** + * Constructor. + * + * @param type $storageKey The key used to store flashes in the session. + */ + public function __construct($storageKey = '_sf2_attributes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$attributes) + { + if ($this->initialized) { + return; + } + + $this->attributes = &$attributes; + $this->initialized = true; + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return array_key_exists($name, $this->attributes); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->attributes = array(); + foreach ($attributes as $key => $value) { + $this->set($key, $value); + } + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + $retval = null; + if (array_key_exists($name, $this->attributes)) { + $retval = $this->attributes[$name]; + unset($this->attributes[$name]); + } + + return $retval; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->attributes = array(); + } +} diff --git a/src/Symfony/Component/HttpFoundation/AttributeBagInterface.php b/src/Symfony/Component/HttpFoundation/AttributeBagInterface.php new file mode 100644 index 0000000000000..371ddf6c09356 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/AttributeBagInterface.php @@ -0,0 +1,39 @@ + + * + * 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\AttributeBagInterface; +use Symfony\Component\HttpFoundation\SessionStorage\AttributeInterface; + +/** + * Attributes store. + * + * @author Drak + * + * @api + */ +interface AttributeBagInterface extends AttributeInterface +{ + /** + * Initializes the AttributeBag + * + * @param array $attributes + */ + function initialize(array &$attributes); + + /** + * Gets the storage key for this bag. + * + * @return string + */ + function getStorageKey(); +} diff --git a/src/Symfony/Component/HttpFoundation/FlashBag.php b/src/Symfony/Component/HttpFoundation/FlashBag.php new file mode 100644 index 0000000000000..fb568fba7efb3 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/FlashBag.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; + +/** + * FlashBag flash message container. + * + * @author Drak + */ +class FlashBag implements FlashBagInterface +{ + /** + * Flash messages. + * + * @var array + */ + private $flashes = array(); + + /** + * @var boolean + */ + private $initialized = false; + + /** + * The storage key for flashes in the session + * + * @var string + */ + private $storageKey; + + /** + * Constructor. + * + * @param type $storageKey The key used to store flashes in the session. + */ + public function __construct($storageKey = '_sf2_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + if ($this->initialized) { + return; + } + + $this->flashes = &$flashes; + $this->initialized = true; + } + + /** + * {@inheritdoc} + */ + public function add($message, $type = self::NOTICE) + { + $this->flashes[$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function get($type, $clear = false) + { + if (!$this->has($type)) { + return array(); + } + + return $clear ? $this->clear($type) : $this->flashes[$type]; + } + + /** + * {@inheritdoc} + */ + public function set($type, array $array) + { + $this->flashes[$type] = $array; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes); + } + + /** + * {@inheritdoc} + */ + public function getTypes() + { + return array_keys($this->flashes); + } + + /** + * {@inheritdoc} + */ + public function all($clear = false) + { + return $clear ? $this->clearAll() : $this->flashes; + } + + /** + * {@inheritdoc} + */ + public function clear($type) + { + $return = array(); + if (isset($this->flashes[$type])) { + $return = $this->flashes[$type]; + unset($this->flashes[$type]); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function clearAll() + { + $return = $this->flashes; + $this->flashes = array(); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } +} diff --git a/src/Symfony/Component/HttpFoundation/FlashBagInterface.php b/src/Symfony/Component/HttpFoundation/FlashBagInterface.php new file mode 100644 index 0000000000000..3c245f5d74f4c --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/FlashBagInterface.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * FlashBagInterface. + * + * @author Drak + */ +interface FlashBagInterface +{ + const INFO = 'info'; + const NOTICE = 'notice'; + const WARNING = 'warning'; + const ERROR = 'error'; + + /** + * Initializes the FlashBag. + * + * @param array &$flashes + */ + function initialize(array &$flashes); + + /** + * Adds a flash to the stack for a given type. + * + * @param string $message + * @param string $type + */ + function add($message, $type = self::NOTICE); + + /** + * Gets flash messages for a given type. + * + * @param string $type Message category type. + * @param boolean $clear Clear the messages after return (default false). + * + * @return array + */ + function get($type, $clear = false); + + /** + * Sets an array of flash messages for a given type. + * + * @param string $type + * @param array $array + */ + function set($type, array $array); + + /** + * Has flash messages for a given type? + * + * @param string $type + * + * @return boolean + */ + function has($type); + + /** + * Returns a list of all defined types. + * + * @return array + */ + function getTypes(); + + /** + * Gets all flash messages. + * + * @param boolean $clear Clear the messages after return (default false). + * + * @return array + */ + function all($clear = false); + + /** + * Clears flash messages for a given type. + * + * @param string $type + * + * @return array Returns an array of what was just cleared. + */ + function clear($type); + + /** + * Clears all flash messages. + * + * @return array Array of arrays or array if none. + */ + function clearAll(); + + /** + * Gets the storage key for this bag. + * + * @return string + */ + function getStorageKey(); +} \ No newline at end of file diff --git a/src/Symfony/Component/HttpFoundation/NamespacedAttributeBag.php b/src/Symfony/Component/HttpFoundation/NamespacedAttributeBag.php new file mode 100644 index 0000000000000..b175b5f233184 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/NamespacedAttributeBag.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * This class provides structured storage of session attributes using + * a name spacing character in the key. + * + * @author Drak + */ +class NamespacedAttributeBag extends AttributeBag +{ + /** + * Namespace character. + * + * @var string + */ + private $namespaceCharacter; + + /** + * Constructor. + * + * @param type $storageKey Session storage key. + * @param type $namespaceCharacter Namespace character to use in keys. + */ + public function __construct($storageKey = '_sf2_attributes', $namespaceCharacter = '/') + { + $this->namespaceCharacter = $namespaceCharacter; + parent::__construct($storageKey); + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + return array_key_exists($name, $attributes); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + return array_key_exists($name, $attributes) ? $attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $attributes = & $this->resolveAttributePath($name, true); + $name = $this->resolveKey($name); + $attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->attributes = array(); + foreach ($attributes as $key => $value) { + $this->set($key, $value); + } + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + $retval = null; + $attributes = & $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + if (array_key_exists($name, $attributes)) { + $retval = $attributes[$name]; + unset($attributes[$name]); + } + + return $retval; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->attributes = array(); + } + + /** + * Resolves a path in attributes property and returns it as a reference. + * + * This method allows structured namespacing of session attributes. + * + * @param string $name Key name + * @param boolean $writeContext Write context, default false + * + * @return array + */ + protected function &resolveAttributePath($name, $writeContext = false) + { + $array = & $this->attributes; + $name = (strpos($name, $this->namespaceCharacter) === 0) ? substr($name, 1) : $name; + + // Check if there is anything to do, else return + if (!$name) { + return $array; + } + + $parts = explode($this->namespaceCharacter, $name); + if (count($parts) < 2) { + if (!$writeContext) { + return $array; + } + + $array[$parts[0]] = array(); + + return $array; + } + + unset($parts[count($parts)-1]); + + foreach ($parts as $part) { + if (!array_key_exists($part, $array)) { + if (!$writeContext) { + return $array; + } + + $array[$part] = array(); + } + + $array = & $array[$part]; + } + + return $array; + } + + /** + * Resolves the key from the name. + * + * This is the last part in a dot separated string. + * + * @param string $name + * + * @return string + */ + protected function resolveKey($name) + { + if (strpos($name, $this->namespaceCharacter) !== false) { + $name = substr($name, strrpos($name, $this->namespaceCharacter)+1, strlen($name)); + } + + return $name; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index d029901b54f0d..42ce428867269 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -75,62 +75,62 @@ class Request * @var string */ protected $content; - + /** * @var string */ protected $languages; - + /** * @var string */ protected $charsets; - + /** * @var string */ protected $acceptableContentTypes; - + /** * @var string */ protected $pathInfo; - + /** * @var string */ protected $requestUri; - + /** * @var string */ protected $baseUrl; - + /** * @var string */ protected $basePath; - + /** * @var string */ protected $method; - + /** * @var string */ protected $format; - + /** - * @var \Symfony\Component\HttpFoundation\Session + * @var \Symfony\Component\HttpFoundation\SessionInterface */ protected $session; - + /** * @var string */ protected $locale; - + /** * @var string */ @@ -461,7 +461,7 @@ public function get($key, $default = null, $deep = false) /** * Gets the Session. * - * @return Session|null The session + * @return SessionInterface|null The session * * @api */ @@ -499,11 +499,11 @@ public function hasSession() /** * Sets the Session. * - * @param Session $session The Session + * @param SessionInterface $session The Session * * @api */ - public function setSession(Session $session) + public function setSession(SessionInterface $session) { $this->session = $session; } @@ -956,16 +956,16 @@ public function setRequestFormat($format) * * @api */ - public function getContentType() + public function getContentType() { return $this->getFormat($this->server->get('CONTENT_TYPE')); } /** * Sets the default locale. - * - * @param string $locale - * + * + * @param string $locale + * * @api */ public function setDefaultLocale($locale) @@ -975,9 +975,9 @@ public function setDefaultLocale($locale) /** * Sets the locale. - * - * @param string $locale - * + * + * @param string $locale + * * @api */ public function setLocale($locale) @@ -987,7 +987,7 @@ public function setLocale($locale) /** * Get the locale. - * + * * @return string */ public function getLocale() @@ -1233,8 +1233,8 @@ protected function prepareRequestUri() /** * Prepares the base URL. - * - * @return string + * + * @return string */ protected function prepareBaseUrl() { @@ -1373,8 +1373,8 @@ static protected function initializeFormats() /** * Sets the default PHP locale. - * - * @param string $locale + * + * @param string $locale */ private function setPhpDefaultLocale($locale) { diff --git a/src/Symfony/Component/HttpFoundation/Session.php b/src/Symfony/Component/HttpFoundation/Session.php index 721a6c7240b99..3b8c0cd0d50fb 100644 --- a/src/Symfony/Component/HttpFoundation/Session.php +++ b/src/Symfony/Component/HttpFoundation/Session.php @@ -12,36 +12,33 @@ namespace Symfony\Component\HttpFoundation; use Symfony\Component\HttpFoundation\SessionStorage\SessionStorageInterface; +use Symfony\Component\HttpFoundation\FlashBagInterface; /** * Session. * * @author Fabien Potencier + * @author Drak * * @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; } /** @@ -51,23 +48,7 @@ public function __construct(SessionStorageInterface $storage) */ 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; } /** @@ -81,14 +62,14 @@ public function start() */ public function has($name) { - return array_key_exists($name, $this->attributes); + return $this->storage->getAttributes()->has($name); } /** * Returns an attribute. * - * @param string $name The attribute name - * @param mixed $default The default value + * @param string $name The attribute name + * @param mixed $default The default value * * @return mixed * @@ -96,7 +77,7 @@ public function has($name) */ 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 +90,7 @@ public function get($name, $default = null) */ public function set($name, $value) { - if (false === $this->started) { - $this->start(); - } - - $this->attributes[$name] = $value; + $this->storage->getAttributes()->set($name, $value); } /** @@ -125,7 +102,7 @@ public function set($name, $value) */ public function all() { - return $this->attributes; + return $this->storage->getAttributes()->all(); } /** @@ -137,11 +114,7 @@ public function all() */ public function replace(array $attributes) { - if (false === $this->started) { - $this->start(); - } - - $this->attributes = $attributes; + $this->storage->getAttributes()->replace($attributes); } /** @@ -153,13 +126,7 @@ public function replace(array $attributes) */ 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,12 +136,7 @@ public function remove($name) */ public function clear() { - if (false === $this->started) { - $this->start(); - } - - $this->attributes = array(); - $this->flashes = array(); + $this->storage->getAttributes()->clear(); } /** @@ -184,7 +146,6 @@ public function clear() */ public function invalidate() { - $this->clear(); $this->storage->regenerate(true); } @@ -199,160 +160,82 @@ public function migrate() $this->storage->regenerate(); } + /** + * {@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() + public function serialize() { - return $this->flashes; + return serialize($this->storage); } /** - * Sets the flash messages. + * Implements the \Serialize interface. * - * @param array $values + * @throws \InvalidArgumentException If the passed string does not unserialize to an instance of SessionStorageInterface */ - public function setFlashes($values) + public function unserialize($serialized) { - if (false === $this->started) { - $this->start(); + $storage = unserialize($serialized); + if (!$storage instanceof SessionStorageInterface) { + throw new \InvalidArgumentException('Serialized data did not return a valid instance of SessionStorageInterface'); } - $this->flashes = $values; - $this->oldFlashes = array(); + $this->storage = $storage; } /** - * Gets a flash message. - * - * @param string $name - * @param string|null $default + * Gets the flash messages driver. * - * @return string + * @return FlashBagInterface */ - public function getFlash($name, $default = null) + public function getFlashes() { - return array_key_exists($name, $this->flashes) ? $this->flashes[$name] : $default; + return $this->storage->getFlashes(); } /** - * Sets a flash message. + * Adds a flash to the stack for a given type. * - * @param string $name - * @param string $value + * @param string $message + * @param string $type */ - public function setFlash($name, $value) + public function addFlash($message, $type = FlashBagInterface::NOTICE) { - if (false === $this->started) { - $this->start(); - } - - $this->flashes[$name] = $value; - unset($this->oldFlashes[$name]); + $this->storage->getFlashes()->add($message, $type); } /** - * Checks whether a flash message exists. + * Gets flash messages for a given type. * - * @param string $name + * @param string $type Message category type. + * @param boolean $clear Clear the messages after get (default true). * - * @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). + * @return array */ - public function close() - { - $this->closed = true; - } - - public function __destruct() - { - if (true === $this->started && !$this->closed) { - $this->save(); - } - } - - public function serialize() - { - return serialize($this->storage); - } - - public function unserialize($serialized) + public function getFlash($type, $clear = true) { - $this->storage = unserialize($serialized); - $this->attributes = array(); - $this->started = false; + return $this->storage->getFlashes()->get($type, $clear); } } diff --git a/src/Symfony/Component/HttpFoundation/SessionInterface.php b/src/Symfony/Component/HttpFoundation/SessionInterface.php new file mode 100644 index 0000000000000..fec128f6943ca --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/SessionInterface.php @@ -0,0 +1,79 @@ + + * + * 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 + */ +interface SessionInterface extends AttributeInterface, \Serializable +{ + /** + * Starts the session storage. + * + * @throws \RutimeException 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. + * + * @return boolean True if session migrated, false if error. + */ + function migrate(); + + /** + * Force the session to be saved. + * + * This method is generally not required for real sessions as + * the session will be automatically saved at the end of + * code execution. + */ + function save(); + + /** + * Gets the flash messages driver. + * + * @return FlashBagInterface + */ + function getFlashes(); + + /** + * 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. + * @param boolean $clear Clear the messages after get (default true). + * + * @return array + */ + function getFlash($type, $clear = true); +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/AbstractSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/AbstractSessionStorage.php new file mode 100644 index 0000000000000..a3055706d0e2b --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/AbstractSessionStorage.php @@ -0,0 +1,285 @@ + + * + * 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 + */ +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; + + /** + * 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 from + * See session.* for values at http://www.php.net/manual/en/ini.list.php + * 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" + * + * @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) { + // Nothing to do as the session is already started. + return; + } + + // generate random session ID + if (!session_id()) { + session_id($this->generateSessionId()); + } + + // start the session + if (!session_start()) { + throw new \RuntimeException('Failed to start the session'); + } + + // after starting the session, PHP retrieves the session from whatever handlers were set + // either PHP's internal, or the ones we set using sssion_set_save_handler(). PHP takes + // the return value from the sessionRead() handler and populates $_SESSION with it automatically. + $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]); + + $this->started = 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(); + } + + /** + * Sets the session.* ini variables. + * + * Note we omit 'session.' from the beginning of the keys. + * + * @param array $options + */ + protected function setOptions(array $options) + { + $cookieDefaults = session_get_cookie_params(); + $this->options = array_merge(array( + 'lifetime' => $cookieDefaults['lifetime'], + 'path' => $cookieDefaults['path'], + 'domain' => $cookieDefaults['domain'], + 'secure' => $cookieDefaults['secure'], + '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('session.cache_limiter', $this->options)) { + $this->options['session.cache_limiter'] = 0; + } + + // See session.* for values at http://www.php.net/manual/en/ini.list.php + foreach ($this->options as $key => $value) { + 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'); + } + + /** + * Generates a session ID. + * + * @return string + */ + protected function generateSessionId() + { + return sha1(uniqid(mt_rand(), true)); + } +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/ArraySessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/ArraySessionStorage.php index aeda2d3c7e3a9..f2b3d1ae72456 100644 --- a/src/Symfony/Component/HttpFoundation/SessionStorage/ArraySessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/ArraySessionStorage.php @@ -11,57 +11,88 @@ namespace Symfony\Component\HttpFoundation\SessionStorage; +use Symfony\Component\HttpFoundation\AttributeBagInterface; +use Symfony\Component\HttpFoundation\FlashBagInterface; + /** * ArraySessionStorage mocks the session for unit tests. * - * When doing functional testing, you should use FilesystemSessionStorage instead. + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle. + * + * When doing functional testing, you should use FunctionalTestFileSessionStorage instead. * * @author Fabien Potencier * @author Bulat Shakirzyanov + * @author Drak */ - -class ArraySessionStorage implements SessionStorageInterface +class ArraySessionStorage extends AbstractSessionStorage { /** - * Storage data. - * + * @var string + */ + protected $sessionId; + + /** * @var array */ - private $data = array(); + private $attributes = array(); /** - * {@inheritdoc} + * @var array */ - public function read($key, $default = null) - { - return array_key_exists($key, $this->data) ? $this->data[$key] : $default; - } + private $flashes = array(); /** - * {@inheritdoc} + * Constructor. + * + * There is no option to set session options here because this object does not start the PHP session. + * This constructor is for easy testing, simply use `$storage = new AttributeBag()` unless you require + * specific implementations of Bag interfaces. + * + * @param AttributeBagInterface $attributes AttributeBagInterface, defaults to null for AttributeBag default + * @param FlashBagInterface $flashes FlashBagInterface, defaults to null for FlashBag default */ - public function regenerate($destroy = false) + public function __construct(AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) { - if ($destroy) { - $this->data = array(); - } - - return true; + $this->attributeBag = $attributes ? $attributes : new AttributeBag(); + $this->flashBag = $flashes ? $flashes : new FlashBag(); } /** * {@inheritdoc} */ - public function remove($key) + public function start() { - unset($this->data[$key]); + if ($this->started) { + return; + } + + $this->started = true; + $this->attributeBag->initialize($this->attributes); + $this->flashBag->initialize($this->flashes); + $this->sessionId = $this->generateSessionId(); + session_id($this->sessionId); } /** * {@inheritdoc} */ - public function start() + public function regenerate($destroy = false) { + if (!$this->started) { + $this->start(); + } + + if ($destroy) { + $this->attributeBag->clear(); + $this->flashBag->clearAll(); + } + + $this->sessionId = $this->generateSessionId(); + session_id($this->sessionId); + + return true; } /** @@ -69,13 +100,18 @@ public function start() */ public function getId() { + if (!$this->started) { + return ''; + } + + return $this->sessionId; } /** * {@inheritdoc} */ - public function write($key, $data) + public function save() { - $this->data[$key] = $data; + // nothing to do since we don't persist the session data } -} +} \ No newline at end of file diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/AttributeInterface.php b/src/Symfony/Component/HttpFoundation/SessionStorage/AttributeInterface.php new file mode 100644 index 0000000000000..176cb8bc8b0a2 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/AttributeInterface.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\SessionStorage; + +/** + * Interface for the session. + * + * @author Drak + */ +interface AttributeInterface +{ + /** + * Checks if an attribute is defined. + * + * @param string $name The attribute name + * + * @return Boolean true if the attribute is defined, false otherwise + */ + function has($name); + + /** + * Returns an attribute. + * + * @param string $name The attribute name + * @param mixed $default The default value if not found. + * + * @return mixed + */ + function get($name, $default = null); + + /** + * Sets an attribute. + * + * @param string $name + * @param mixed $value + */ + function set($name, $value); + + /** + * Returns attributes. + * + * @return array Attributes + */ + function all(); + + /** + * Sets attributes. + * + * @param array $attributes Attributes + */ + function replace(array $attributes); + + /** + * Removes an attribute. + * + * @param string $name + */ + function remove($name); + + /** + * Clears all attributes. + */ + function clear(); +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/FilesystemSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/FilesystemSessionStorage.php deleted file mode 100644 index 55f626e72e7ef..0000000000000 --- a/src/Symfony/Component/HttpFoundation/SessionStorage/FilesystemSessionStorage.php +++ /dev/null @@ -1,191 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\SessionStorage; - -/** - * FilesystemSessionStorage simulates sessions for functional tests. - * - * This storage does not start the session (session_start()) - * as it is not "available" when running tests on the command line. - * - * @author Fabien Potencier - * - * @api - */ -class FilesystemSessionStorage extends NativeSessionStorage -{ - /** - * File path. - * - * @var string - */ - private $path; - - /** - * Data. - * - * @var array - */ - private $data; - - /** - * Session started flag. - * - * @var boolean - */ - private $started; - - /** - * Constructor. - */ - public function __construct($path, array $options = array()) - { - $this->path = $path; - $this->started = false; - - parent::__construct($options); - } - - /** - * Starts the session. - * - * @api - */ - public function start() - { - if ($this->started) { - return; - } - - session_set_cookie_params( - $this->options['lifetime'], - $this->options['path'], - $this->options['domain'], - $this->options['secure'], - $this->options['httponly'] - ); - - if (!ini_get('session.use_cookies') && isset($this->options['id']) && $this->options['id'] && $this->options['id'] != session_id()) { - session_id($this->options['id']); - } - - if (!session_id()) { - session_id(hash('md5', uniqid(mt_rand(), true))); - } - - $file = $this->path.'/'.session_id().'.session'; - - $this->data = is_file($file) ? unserialize(file_get_contents($file)) : array(); - $this->started = true; - } - - /** - * Returns the session ID - * - * @return mixed The session ID - * - * @throws \RuntimeException If the session was not started yet - * - * @api - */ - public function getId() - { - if (!$this->started) { - throw new \RuntimeException('The session must be started before reading its ID'); - } - - return session_id(); - } - - /** - * 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 - */ - public function read($key, $default = null) - { - return array_key_exists($key, $this->data) ? $this->data[$key] : $default; - } - - /** - * 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 - */ - public function remove($key) - { - $retval = $this->data[$key]; - - unset($this->data[$key]); - - return $retval; - } - - /** - * 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 - */ - public function write($key, $data) - { - $this->data[$key] = $data; - - if (!is_dir($this->path)) { - mkdir($this->path, 0777, true); - } - - file_put_contents($this->path.'/'.session_id().'.session', serialize($this->data)); - } - - /** - * Regenerates id that represents this storage. - * - * @param Boolean $destroy Destroy session when regenerating? - * - * @return Boolean True if session regenerated, false if error - * - * @throws \RuntimeException If an error occurs while regenerating this storage - * - * @api - */ - public function regenerate($destroy = false) - { - if ($destroy) { - $this->data = array(); - } - - return true; - } -} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/FunctionalTestFileSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/FunctionalTestFileSessionStorage.php new file mode 100644 index 0000000000000..e5c0fdb716ed8 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/FunctionalTestFileSessionStorage.php @@ -0,0 +1,149 @@ + + * + * 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\AttributeBagInterface; +use Symfony\Component\HttpFoundation\FlashBagInterface; + +/** + * FunctionalTestFileSessionStorage is used to mock sessions for + * functional testing when done in a single PHP process. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle. + * + * @author Drak + */ +class FunctionalTestFileSessionStorage extends ArraySessionStorage +{ + /** + * @var array + */ + private $data; + + /** + * @var string + */ + private $savePath; + + /** + * Constructor. + * + * @param string $savePath Path of directory to save session files. + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag) + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct($savePath = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + if (is_null($savePath)) { + $savePath = sys_get_temp_dir(); + } + + if (!is_dir($savePath)) { + mkdir($savePath, 0777, true); + } + + $this->savePath = $savePath; + + parent::__construct($attributes, $flashes); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return; + } + + if (!ini_get('session.use_cookies') && isset($this->options['id']) && $this->options['id'] && $this->options['id'] != session_id()) { + session_id($this->options['id']); + } + + if (!session_id()) { + session_id($this->generateSessionId()); + } + + $this->sessionId = session_id(); + + $this->read(); + + $this->started = true; + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false) + { + parent::regenerate($destroy); + + // bacause we have no GC routines, we can just GC the session now manually + $this->destroy(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + if (!$this->started) { + return ''; + } + + return $this->sessionId; + } + + /** + * {@inheritdoc} + */ + public function save() + { + file_put_contents($this->getFilePath(), serialize($this->data)); + } + + public function read() + { + $filePath = $this->getFilePath(); + $this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : array(); + + $key = $this->attributeBag->getStorageKey(); + $this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : array(); + $this->attributeBag->initialize($this->data[$key]); + + $key = $this->flashBag->getStorageKey(); + $this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : array(); + $this->flashBag->initialize($this->data[$key]); + } + + public function destroy() + { + if (is_file($this->getFilePath())) { + unlink($this->getFilePath()); + } + } + + /** + * Calculate path to file. + * + * @return string File path + */ + protected function getFilePath() + { + return $this->savePath.'/'.$this->sessionId.'.session'; + } +} \ No newline at end of file diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/MemcacheSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/MemcacheSessionStorage.php new file mode 100644 index 0000000000000..99fc10d446258 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/MemcacheSessionStorage.php @@ -0,0 +1,137 @@ + + * + * 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\AttributeBagInterface; +use Symfony\Component\HttpFoundation\FlashBagInterface; + +/** + * MemcacheSessionStorage. + * + * @author Drak + */ +class MemcacheSessionStorage extends AbstractSessionStorage implements SessionSaveHandlerInterface +{ + /** + * Memcache driver. + * + * @var Memcache + */ + private $memcache; + + /** + * Configuration options. + * + * @var array + */ + private $memcacheOptions; + + /** + * Key prefix for shared environments. + * + * @var string + */ + private $prefix; + + /** + * Constructor. + * + * @param \Memcache $memcache A \Memcache instance + * @param array $memcacheOptions An associative array of Memcachge options + * @param array $options Session configuration options. + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag) + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct(\Memcache $memcache, array $memcacheOptions = array(), array $options = array(), AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + $this->memcache = $memcache; + + // defaults + if (!isset($memcacheOptions['serverpool'])) { + $memcacheOptions['serverpool'] = array('host' => '127.0.0.1', 'port' => 11211, 'timeout' => 1, 'persistent' => false, 'weight' => 1); + } + $memcacheOptions['expiretime'] = isset($memcacheOptions['expiretime']) ? (int)$memcacheOptions['expiretime'] : 86400; + $this->prefix = isset($memcachedOptions['prefix']) ? $memcachedOptions['prefix'] : 'sf2s'; + + $this->memcacheOptions = $memcacheOptions; + + parent::__construct($attributes, $flashes, $options); + } + + protected function addServer(array $server) + { + if (array_key_exists('host', $server)) { + throw new \InvalidArgumentException('host key must be set'); + } + $server['port'] = isset($server['port']) ? (int)$server['port'] : 11211; + $server['timeout'] = isset($server['timeout']) ? (int)$server['timeout'] : 1; + $server['presistent'] = isset($server['presistent']) ? (bool)$server['presistent'] : false; + $server['weight'] = isset($server['weight']) ? (bool)$server['weight'] : 1; + } + + /** + * {@inheritdoc} + */ + public function sessionOpen($savePath, $sessionName) + { + foreach ($this->memcacheOptions['serverpool'] as $server) { + $this->addServer($server); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function sessionClose() + { + return $this->memcache->close(); + } + + /** + * {@inheritdoc} + */ + public function sessionRead($sessionId) + { + $result = $this->memcache->get($this->prefix.$sessionId); + + return ($result) ? $result : ''; + } + + /** + * {@inheritdoc} + */ + public function sessionWrite($sessionId, $data) + { + return $this->memcache->set($this->prefix.$sessionId, $data, $this->memcacheOptions['expiretime']); + } + + /** + * {@inheritdoc} + */ + public function sessionDestroy($sessionId) + { + return $this->memcache->delete($this->prefix.$sessionId); + } + + /** + * {@inheritdoc} + */ + public function sessionGc($lifetime) + { + // not required here because memcache will auto expire the records anyhow. + return true; + } +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/MemcachedSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/MemcachedSessionStorage.php new file mode 100644 index 0000000000000..d54a9139d1665 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/MemcachedSessionStorage.php @@ -0,0 +1,138 @@ + + * + * 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\AttributeBagInterface; +use Symfony\Component\HttpFoundation\FlashBagInterface; + +/** + * MemcachedSessionStorage. + * + * @author Drak + */ +class MemcachedSessionStorage extends AbstractSessionStorage implements SessionSaveHandlerInterface +{ + /** + * Memcached driver. + * + * @var Memcached + */ + private $memcached; + + /** + * Configuration options. + * + * @var array + */ + private $memcachedOptions; + + /** + * Constructor. + * + * @param \Memcached $memcached A \Memcached instance + * @param array $memcachedOptions An associative array of Memcached options + * @param array $options Session configuration options. + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag) + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct(\Memcached $memcache, array $memcachedOptions = array(), array $options = array(), AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + $this->memcached = $memcached; + + // defaults + if (!isset($memcachedOptions['serverpool'])) { + $memcachedOptions['serverpool'] = array('host' => '127.0.0.1', 'port' => 11211, 'timeout' => 1, 'persistent' => false, 'weight' => 1); + } + $memcachedOptions['expiretime'] = isset($memcachedOptions['expiretime']) ? (int)$memcachedOptions['expiretime'] : 86400; + + $this->memcached->setOption(\Memcached::OPT_PREFIX_KEY, isset($memcachedOptions['prefix']) ? $memcachedOption['prefix'] : 'sf2s'); + + $this->memcacheOptions = $memcachedOptions; + + parent::__construct($attributes, $flashes, $options); + } + + /** + * {@inheritdoc} + */ + public function sessionOpen($savePath, $sessionName) + { + foreach ($this->memcachedOptions['serverpool'] as $server) { + $this->addServer($server); + } + + return true; + } + + /** + * Close session. + * + * @return boolean + */ + public function sessionClose() + { + return $this->memcached->close(); + } + + /** + * {@inheritdoc} + */ + public function sessionRead($sessionId) + { + $result = $this->memcached->get($this->prefix.$sessionId); + + return $result ? $result : ''; + } + + /** + * {@inheritdoc} + */ + public function sessionWrite($sessionId, $data) + { + return $this->memcached->set($this->prefix.$sessionId, $data, false, $this->memcachedOptions['expiretime']); + } + + /** + * {@inheritdoc} + */ + public function sessionDestroy($sessionId) + { + return $this->memcached->delete($this->prefix.$sessionId); + } + + /** + * {@inheritdoc} + */ + public function sessionGc($lifetime) + { + // not required here because memcached will auto expire the records anyhow. + return true; + } + + /** + * Adds a server to the memcached handler. + * + * @param array $server + */ + protected function addServer(array $server) + { + if (array_key_exists('host', $server)) { + throw new \InvalidArgumentException('host key must be set'); + } + $server['port'] = isset($server['port']) ? (int)$server['port'] : 11211; + $server['timeout'] = isset($server['timeout']) ? (int)$server['timeout'] : 1; + $server['presistent'] = isset($server['presistent']) ? (bool)$server['presistent'] : false; + $server['weight'] = isset($server['weight']) ? (bool)$server['weight'] : 1; + } +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/NativeFileSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/NativeFileSessionStorage.php new file mode 100644 index 0000000000000..9297e4b7182b2 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/NativeFileSessionStorage.php @@ -0,0 +1,64 @@ + + * + * 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\AttributeBagInterface; +use Symfony\Component\HttpFoundation\FlashBagInterface; + +/** + * NativeFileSessionStorage. + * + * Native session handler using PHP's built in file storage. + * + * @author Drak + */ +class NativeFileSessionStorage extends AbstractSessionStorage +{ + /** + * @var string + */ + private $savePath; + + /** + * Constructor. + * + * @param string $savePath Path of directory to save session files. + * @param array $options Session configuration options. + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag) + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct($savePath = null, array $options = array(), AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + if (is_null($savePath)) { + $savePath = sys_get_temp_dir(); + } + + if (!is_dir($savePath)) { + mkdir($savePath, 0777, true); + } + + $this->savePath = $savePath; + + parent::__construct($attributes, $flashes, $options); + } + + /** + * {@inheritdoc} + */ + protected function registerSaveHandlers() + { + ini_set('session.save_handlers', 'files'); + ini_set('session.save_path', $this->savePath); + } +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/NativeMemcacheSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/NativeMemcacheSessionStorage.php new file mode 100644 index 0000000000000..42e10a9128c01 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/NativeMemcacheSessionStorage.php @@ -0,0 +1,59 @@ + + * + * 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\AttributeBagInterface; +use Symfony\Component\HttpFoundation\FlashBagInterface; + +/** + * NativeMemcacheSessionStorage. + * + * Session based on native PHP memcache database handler. + * + * @author Drak + */ +class NativeMemcacheSessionStorage extends AbstractSessionStorage +{ + /** + * @var string + */ + private $savePath; + + /** + * Constructor. + * + * @param string $savePath Path of memcache server. + * @param array $options Session configuration options. + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag) + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct($savePath = 'tcp://127.0.0.1:11211?persistent=0', array $options = array(), AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + if (!session_module_name('memcache')) { + throw new \RuntimeException('PHP does not have "memcache" session module registered'); + } + + $this->savePath = $savePath; + parent::__construct($attributes, $flashes, $options); + } + + /** + * {@inheritdoc} + */ + protected function registerSaveHandlers() + { + ini_set('session.save_handlers', 'memcache'); + ini_set('session.save_path', $this->savePath); + } +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/NativeMemcachedSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/NativeMemcachedSessionStorage.php new file mode 100644 index 0000000000000..7b8f86529631b --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/NativeMemcachedSessionStorage.php @@ -0,0 +1,59 @@ + + * + * 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\AttributeBagInterface; +use Symfony\Component\HttpFoundation\FlashBagInterface; + +/** + * NativeMemcachedSessionStorage. + * + * Session based on native PHP memcached database handler. + * + * @author Drak + */ +class NativeMemcachedSessionStorage extends AbstractSessionStorage +{ + /** + * @var string + */ + private $savePath; + + /** + * Constructor. + * + * @param string $savePath Comma separated list of servers: e.g. memcache1.example.com:11211,memcache2.example.com:11211 + * @param array $options Session configuration options. + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for defaul FlashBag) + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct($savePath = '127.0.0.1:11211', array $options = array(), AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + if (!session_module_name('memcached')) { + throw new \RuntimeException('PHP does not have "memcached" session module registered'); + } + + $this->savePath = $savePath; + parent::__construct($attributes, $flashes, $options); + } + + /** + * {@inheritdoc} + */ + protected function registerSaveHandlers() + { + ini_set('session.save_handlers', 'memcached'); + ini_set('session.save_path', $this->savePath); + } +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/NativeSessionStorage.php deleted file mode 100644 index b759f7411a0a4..0000000000000 --- a/src/Symfony/Component/HttpFoundation/SessionStorage/NativeSessionStorage.php +++ /dev/null @@ -1,180 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\SessionStorage; - -/** - * NativeSessionStorage. - * - * @author Fabien Potencier - * - * @api - */ -class NativeSessionStorage implements SessionStorageInterface -{ - static protected $sessionIdRegenerated = false; - static protected $sessionStarted = false; - - protected $options; - - /** - * Available options: - * - * * name: The cookie name (null [omitted] by default) - * * id: The session id (null [omitted] by default) - * * lifetime: Cookie lifetime - * * path: Cookie path - * * domain: Cookie domain - * * secure: Cookie secure - * * httponly: Cookie http only - * - * The default values for most options are those returned by the session_get_cookie_params() function - * - * @param array $options An associative array of session options - */ - public function __construct(array $options = array()) - { - $cookieDefaults = session_get_cookie_params(); - - $this->options = array_merge(array( - 'lifetime' => $cookieDefaults['lifetime'], - 'path' => $cookieDefaults['path'], - 'domain' => $cookieDefaults['domain'], - 'secure' => $cookieDefaults['secure'], - 'httponly' => isset($cookieDefaults['httponly']) ? $cookieDefaults['httponly'] : false, - ), $options); - - // Skip setting new session name if user don't want it - if (isset($this->options['name'])) { - session_name($this->options['name']); - } - } - - /** - * Starts the session. - * - * @api - */ - public function start() - { - if (self::$sessionStarted) { - return; - } - - session_set_cookie_params( - $this->options['lifetime'], - $this->options['path'], - $this->options['domain'], - $this->options['secure'], - $this->options['httponly'] - ); - - // disable native cache limiter as this is managed by HeaderBag directly - session_cache_limiter(false); - - if (!ini_get('session.use_cookies') && isset($this->options['id']) && $this->options['id'] && $this->options['id'] != session_id()) { - session_id($this->options['id']); - } - - session_start(); - - self::$sessionStarted = true; - } - - /** - * {@inheritDoc} - * - * @api - */ - public function getId() - { - if (!self::$sessionStarted) { - throw new \RuntimeException('The session must be started before reading its ID'); - } - - return session_id(); - } - - /** - * 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 - * @param string $default Default value - * - * @return mixed Data associated with the key - * - * @api - */ - public function read($key, $default = null) - { - return array_key_exists($key, $_SESSION) ? $_SESSION[$key] : $default; - } - - /** - * 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 - * - * @api - */ - public function remove($key) - { - $retval = null; - - if (isset($_SESSION[$key])) { - $retval = $_SESSION[$key]; - unset($_SESSION[$key]); - } - - return $retval; - } - - /** - * 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 - * - * @api - */ - public function write($key, $data) - { - $_SESSION[$key] = $data; - } - - /** - * Regenerates id that represents this storage. - * - * @param Boolean $destroy Destroy session when regenerating? - * - * @return Boolean True if session regenerated, false if error - * - * @api - */ - public function regenerate($destroy = false) - { - if (self::$sessionIdRegenerated) { - return; - } - - session_regenerate_id($destroy); - - self::$sessionIdRegenerated = true; - } -} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/NativeSqliteSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/NativeSqliteSessionStorage.php new file mode 100644 index 0000000000000..724f5f1b4d2e5 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/NativeSqliteSessionStorage.php @@ -0,0 +1,59 @@ + + * + * 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\AttributeBagInterface; +use Symfony\Component\HttpFoundation\FlashBagInterface; + +/** + * NativeSqliteSessionStorage. + * + * Session based on native PHP sqlite database handler. + * + * @author Drak + */ +class NativeSqliteSessionStorage extends AbstractSessionStorage +{ + /** + * @var string + */ + private $dbPath; + + /** + * Constructor. + * + * @param string $dbPath Path to SQLite database file. + * @param array $options Session configuration options. + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for defaul FlashBag) + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct($dbPath, array $options = array(), AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + if (!session_module_name('sqlite')) { + throw new \RuntimeException('PHP does not have "sqlite" session module registered'); + } + + $this->dbPath = $dbPath; + parent::__construct($attribubtes, $flashes, $options); + } + + /** + * {@inheritdoc} + */ + protected function registerSaveHandlers() + { + ini_set('session.save_handlers', 'sqlite'); + ini_set('session.save_path', $this->dbPath); + } +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/NullSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/NullSessionStorage.php new file mode 100644 index 0000000000000..1fa9a34750011 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/NullSessionStorage.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\SessionStorage; + +/** + * NullSessionStorage. + * + * Can be used in unit testing or in a sitation where persisted sessions are not desired. + * + * @author Drak + * + * @api + */ +class NullSessionStorage extends AbstractSessionStorage implements SessionSaveHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function sessionOpen($savePath, $sessionName) + { + return true; + } + + /** + * Close session. + * + * @return boolean + */ + public function sessionClose() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function sessionRead($sessionId) + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function sessionWrite($sessionId, $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function sessionDestroy($sessionId) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function sessionGc($lifetime) + { + return true; + } +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/PdoSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/PdoSessionStorage.php index ee2641a98afd5..7bbf5699d92bc 100644 --- a/src/Symfony/Component/HttpFoundation/SessionStorage/PdoSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/PdoSessionStorage.php @@ -11,24 +11,27 @@ namespace Symfony\Component\HttpFoundation\SessionStorage; +use Symfony\Component\HttpFoundation\AttributeBagInterface; +use Symfony\Component\HttpFoundation\FlashBagInterface; + /** * PdoSessionStorage. * * @author Fabien Potencier * @author Michael Williams */ -class PdoSessionStorage extends NativeSessionStorage +class PdoSessionStorage extends AbstractSessionStorage implements SessionSaveHandlerInterface { /** * PDO instance. - * + * * @var \PDO */ - private $db; - + private $pdo; + /** * Database options. - * + * * @var array */ private $dbOptions; @@ -36,59 +39,35 @@ class PdoSessionStorage extends NativeSessionStorage /** * Constructor. * - * @param \PDO $db A PDO instance - * @param array $options An associative array of session options - * @param array $dbOptions An associative array of DB options + * + * @param \PDO $pdo A \PDO instance + * @param array $dbOptions An associative array of DB options + * @param array $options Session configuration options + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for defaul FlashBag) * * @throws \InvalidArgumentException When "db_table" option is not provided * - * @see NativeSessionStorage::__construct() + * @see AbstractSessionStorage::__construct() */ - public function __construct(\PDO $db, array $options = array(), array $dbOptions = array()) + public function __construct(\PDO $pdo, array $dbOptions = array(), array $options = array(), AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) { if (!array_key_exists('db_table', $dbOptions)) { throw new \InvalidArgumentException('You must provide the "db_table" option for a PdoSessionStorage.'); } - $this->db = $db; + $this->pdo = $pdo; $this->dbOptions = array_merge(array( 'db_id_col' => 'sess_id', 'db_data_col' => 'sess_data', 'db_time_col' => 'sess_time', ), $dbOptions); - parent::__construct($options); - } - - /** - * Starts the session. - */ - public function start() - { - if (self::$sessionStarted) { - return; - } - - // use this object as the session handler - session_set_save_handler( - array($this, 'sessionOpen'), - array($this, 'sessionClose'), - array($this, 'sessionRead'), - array($this, 'sessionWrite'), - array($this, 'sessionDestroy'), - array($this, 'sessionGC') - ); - - parent::start(); + parent::__construct($attributes, $flashes, $options); } /** - * Opens a session. - * - * @param string $path (ignored) - * @param string $name (ignored) - * - * @return Boolean true, if the session was opened, otherwise an exception is thrown + * {@inheritdoc} */ public function sessionOpen($path = null, $name = null) { @@ -96,22 +75,15 @@ public function sessionOpen($path = null, $name = null) } /** - * Closes a session. - * - * @return Boolean true, if the session was closed, otherwise false + * {@inheritdoc} */ public function sessionClose() { - // do nothing return true; } /** - * Destroys a session. - * - * @param string $id A session ID - * - * @return Boolean true, if the session was destroyed, otherwise an exception is thrown + * {@inheritdoc} * * @throws \RuntimeException If the session cannot be destroyed */ @@ -125,7 +97,7 @@ public function sessionDestroy($id) $sql = "DELETE FROM $dbTable WHERE $dbIdCol = :id"; try { - $stmt = $this->db->prepare($sql); + $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':id', $id, \PDO::PARAM_STR); $stmt->execute(); } catch (\PDOException $e) { @@ -136,15 +108,11 @@ public function sessionDestroy($id) } /** - * Cleans up old sessions. - * - * @param int $lifetime The lifetime of a session - * - * @return Boolean true, if old sessions have been cleaned, otherwise an exception is thrown + * {@inheritdoc} * * @throws \RuntimeException If any old sessions cannot be cleaned */ - public function sessionGC($lifetime) + public function sessionGc($lifetime) { // get table/column $dbTable = $this->dbOptions['db_table']; @@ -154,7 +122,7 @@ public function sessionGC($lifetime) $sql = "DELETE FROM $dbTable WHERE $dbTimeCol < (:time - $lifetime)"; try { - $stmt = $this->db->prepare($sql); + $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':time', time(), \PDO::PARAM_INT); $stmt->execute(); } catch (\PDOException $e) { @@ -165,11 +133,7 @@ public function sessionGC($lifetime) } /** - * Reads a session. - * - * @param string $id A session ID - * - * @return string The session data if the session was read or created, otherwise an exception is thrown + * {@inheritdoc} * * @throws \RuntimeException If the session cannot be read */ @@ -183,7 +147,7 @@ public function sessionRead($id) try { $sql = "SELECT $dbDataCol FROM $dbTable WHERE $dbIdCol = :id"; - $stmt = $this->db->prepare($sql); + $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':id', $id, \PDO::PARAM_STR, 255); $stmt->execute(); @@ -205,12 +169,7 @@ public function sessionRead($id) } /** - * Writes session data. - * - * @param string $id A session ID - * @param string $data A serialized chunk of session data - * - * @return Boolean true, if the session was written, otherwise an exception is thrown + * {@inheritdoc} * * @throws \RuntimeException If the session data cannot be written */ @@ -222,7 +181,7 @@ public function sessionWrite($id, $data) $dbIdCol = $this->dbOptions['db_id_col']; $dbTimeCol = $this->dbOptions['db_time_col']; - $sql = ('mysql' === $this->db->getAttribute(\PDO::ATTR_DRIVER_NAME)) + $sql = ('mysql' === $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME)) ? "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, :time) " ."ON DUPLICATE KEY UPDATE $dbDataCol = VALUES($dbDataCol), $dbTimeCol = CASE WHEN $dbTimeCol = :time THEN (VALUES($dbTimeCol) + 1) ELSE VALUES($dbTimeCol) END" : "UPDATE $dbTable SET $dbDataCol = :data, $dbTimeCol = :time WHERE $dbIdCol = :id"; @@ -230,7 +189,7 @@ public function sessionWrite($id, $data) try { //session data can contain non binary safe characters so we need to encode it $encoded = base64_encode($data); - $stmt = $this->db->prepare($sql); + $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':id', $id, \PDO::PARAM_STR); $stmt->bindParam(':data', $encoded, \PDO::PARAM_STR); $stmt->bindValue(':time', time(), \PDO::PARAM_INT); @@ -253,7 +212,7 @@ public function sessionWrite($id, $data) * * @param string $id * @param string $data - * + * * @return boolean True. */ private function createNewSession($id, $data = '') @@ -268,7 +227,7 @@ private function createNewSession($id, $data = '') //session data can contain non binary safe characters so we need to encode it $encoded = base64_encode($data); - $stmt = $this->db->prepare($sql); + $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':id', $id, \PDO::PARAM_STR); $stmt->bindParam(':data', $encoded, \PDO::PARAM_STR); $stmt->bindValue(':time', time(), \PDO::PARAM_INT); diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/SessionSaveHandlerInterface.php b/src/Symfony/Component/HttpFoundation/SessionStorage/SessionSaveHandlerInterface.php new file mode 100644 index 0000000000000..95ed8e5055dba --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/SessionSaveHandlerInterface.php @@ -0,0 +1,152 @@ + + * + * 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 + * to be stored. Again PHP will automatically serialize these itself using session_encode() + * + * 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 + */ +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 automatically unserialized 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); +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.php b/src/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.php index b61a2557b27c7..afde7702112a0 100644 --- a/src/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.php +++ b/src/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.php @@ -11,10 +11,14 @@ namespace Symfony\Component\HttpFoundation\SessionStorage; +use Symfony\Component\HttpFoundation\FlashBagInterface; +use Symfony\Component\HttpFoundation\AttributeBagInterface; + /** * SessionStorageInterface. * * @author Fabien Potencier + * @author Drak * * @api */ @@ -23,6 +27,8 @@ interface SessionStorageInterface /** * Starts the session. * + * @throws \RuntimeException If something goes wrong starting the session. + * * @api */ function start(); @@ -30,68 +36,51 @@ function start(); /** * 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. + * Regenerates id that represents this storage. * - * The preferred format for a key is directory style so naming conflicts can be avoided. + * 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 string $key A unique key identifying your data + * @param Boolean $destroy Destroy session when regenerating? * - * @return mixed Data associated with the key + * @return Boolean True if session regenerated, false if error * - * @throws \RuntimeException If an error occurs while reading data from this storage + * @throws \RuntimeException If an error occurs while regenerating this storage * * @api */ - function read($key); + function regenerate($destroy = false); /** - * 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 + * Force the session to be saved. * - * @return mixed Data associated with the key - * - * @throws \RuntimeException If an error occurs while removing data from this storage - * - * @api + * 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 remove($key); + function save(); /** - * Writes data to this storage. + * Gets the FlashBagInterface driver. * - * 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 + * @return FlashBagInterface */ - function write($key, $data); + function getFlashes(); /** - * Regenerates id that represents this storage. - * - * @param Boolean $destroy Destroy session when regenerating? + * Gets the AttributeBagInterface driver. * - * @return Boolean True if session regenerated, false if error - * - * @throws \RuntimeException If an error occurs while regenerating this storage - * - * @api + * @return AttributeBagInterface */ - function regenerate($destroy = false); + function getAttributes(); } diff --git a/tests/Symfony/Tests/Component/HttpFoundation/AttributeBagTest.php b/tests/Symfony/Tests/Component/HttpFoundation/AttributeBagTest.php new file mode 100644 index 0000000000000..7bda14a25146f --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/AttributeBagTest.php @@ -0,0 +1,157 @@ + + */ +class AttributeBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var array + */ + private $array; + + /** + * @var AttributeBag + */ + private $bag; + + protected function setUp() + { + $this->array = array( + 'hello' => 'world', + 'always' => 'be happy', + 'user.login' => 'drak', + 'csrf.token' => array( + 'a' => '1234', + 'b' => '4321', + ), + 'category' => array( + 'fishing' => array( + 'first' => 'cod', + 'second' => 'sole') + ), + ); + $this->bag = new AttributeBag('_sf2'); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->bag = null; + $this->array = array(); + } + + public function testInitialize() + { + $bag = new AttributeBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $this->bag->all()); + $array = array('should' => 'not stick'); + $bag->initialize($array); + + // should have remained the same + $this->assertEquals($this->array, $this->bag->all()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2', $this->bag->getStorageKey()); + $attributeBag = new AttributeBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + /** + * @dataProvider attributesProvider + */ + public function testHas($key, $value, $exists) + { + $this->assertEquals($exists, $this->bag->has($key)); + } + + /** + * @dataProvider attributesProvider + */ + public function testGet($key, $value, $expected) + { + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testGetDefaults() + { + $this->assertNull($this->bag->get('user2.login')); + $this->assertEquals('default', $this->bag->get('user2.login', 'default')); + } + + /** + * @dataProvider attributesProvider + */ + public function testSet($key, $value, $expected) + { + $this->bag->set($key, $value); + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testAll() + { + $this->assertEquals($this->array, $this->bag->all()); + + $this->bag->set('hello', 'fabien'); + $array = $this->array; + $array['hello'] = 'fabien'; + $this->assertEquals($array, $this->bag->all()); + } + + public function testReplace() + { + $array = array(); + $array['name'] = 'jack'; + $array['foo.bar'] = 'beep'; + $this->bag->replace($array); + $this->assertEquals($array, $this->bag->all()); + $this->assertNull($this->bag->get('hello')); + $this->assertNull($this->bag->get('always')); + $this->assertNull($this->bag->get('user.login')); + } + + public function testRemove() + { + $this->assertEquals('world', $this->bag->get('hello')); + $this->bag->remove('hello'); + $this->assertNull($this->bag->get('hello')); + + $this->assertEquals('be happy', $this->bag->get('always')); + $this->bag->remove('always'); + $this->assertNull($this->bag->get('always')); + + $this->assertEquals('drak', $this->bag->get('user.login')); + $this->bag->remove('user.login'); + $this->assertNull($this->bag->get('user.login')); + } + + public function testClear() + { + $this->bag->clear(); + $this->assertEquals(array(), $this->bag->all()); + } + + public function attributesProvider() + { + return array( + array('hello', 'world', true), + array('always', 'be happy', true), + array('user.login', 'drak', true), + array('csrf.token', array('a' => '1234', 'b' => '4321'), true), + array('category', array('fishing' => array('first' => 'cod', 'second' => 'sole')), true), + array('user2.login', null, false), + array('never', null, false), + array('bye', null, false), + array('bye/for/now', null, false), + ); + } +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/FlashBagTest.php b/tests/Symfony/Tests/Component/HttpFoundation/FlashBagTest.php new file mode 100644 index 0000000000000..ebfb6f19bc2c6 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/FlashBagTest.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\FlashBag; +use Symfony\Component\HttpFoundation\FlashBagInterface; + +/** + * FlashBagTest + * + * @author Drak + */ +class FlashBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\FlashBagInterface + */ + private $bag; + + /** + * @var array + */ + protected $array = array(); + + public function setUp() + { + parent::setUp(); + $this->bag = new FlashBag(); + $this->array = array(FlashBag::NOTICE => array('A previous flash message')); + $this->bag->initialize($this->array); + } + + public function tearDown() + { + $this->bag = null; + parent::tearDown(); + } + + public function testInitialize() + { + $bag = new FlashBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $this->bag->all()); + $array = array('should' => 'not stick'); + $bag->initialize($array); + + // should have remained the same + $this->assertEquals($this->array, $this->bag->all()); + } + + public function testAdd() + { + $this->bag->add('Something new', FlashBag::NOTICE); + $this->bag->add('Smile, it might work next time', FlashBag::ERROR); + $this->assertEquals(array('A previous flash message', 'Something new'), $this->bag->get(FlashBag::NOTICE)); + $this->assertEquals(array('Smile, it might work next time'), $this->bag->get(FlashBag::ERROR)); + } + + public function testGet() + { + $this->assertEquals(array('A previous flash message'), $this->bag->get(FlashBag::NOTICE)); + $this->assertEquals(array('A previous flash message'), $this->bag->get(FlashBag::NOTICE, true)); + $this->assertFalse($this->bag->has(FlashBag::NOTICE)); + $this->assertEquals(array(), $this->bag->get('non_existing_type')); + } + + public function testSet() + { + $this->bag->set(FlashBag::NOTICE, array('Foo', 'Bar')); + $this->assertEquals(array('Foo', 'Bar'), $this->bag->get(FlashBag::NOTICE)); + } + + public function testHas() + { + $this->assertFalse($this->bag->has('nothing')); + $this->assertTrue($this->bag->has(FlashBag::NOTICE)); + } + + public function testGetTypes() + { + $this->assertEquals(array(FlashBag::NOTICE), $this->bag->getTypes()); + } + + public function testAll() + { + $this->bag->set(FlashBag::NOTICE, array('Foo')); + $this->bag->set(FlashBag::ERROR, array('Bar')); + $this->assertEquals(array( + FlashBag::NOTICE => array('Foo'), + FlashBag::ERROR => array('Bar')), + $this->bag->all() + ); + $this->assertTrue($this->bag->has(FlashBag::NOTICE)); + $this->assertTrue($this->bag->has(FlashBag::ERROR)); + $this->assertEquals(array( + FlashBag::NOTICE => array('Foo'), + FlashBag::ERROR => array('Bar')), + $this->bag->all(true) + ); + $this->assertFalse($this->bag->has(FlashBag::NOTICE)); + $this->assertFalse($this->bag->has(FlashBag::ERROR)); + $this->assertEquals(array(), $this->bag->all()); + } + + public function testClear() + { + $this->assertTrue($this->bag->has(FlashBag::NOTICE)); + $this->bag->clear(FlashBag::NOTICE); + $this->assertFalse($this->bag->has(FlashBag::NOTICE)); + } + + public function testClearAll() + { + $this->assertTrue($this->bag->has(FlashBag::NOTICE)); + $this->bag->add('Smile, it might work next time', FlashBag::ERROR); + $this->assertTrue($this->bag->has(FlashBag::ERROR)); + $this->bag->clearAll(); + $this->assertFalse($this->bag->has(FlashBag::NOTICE)); + $this->assertFalse($this->bag->has(FlashBag::ERROR)); + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/HttpFoundation/NamespacedAttributeBagTest.php b/tests/Symfony/Tests/Component/HttpFoundation/NamespacedAttributeBagTest.php new file mode 100644 index 0000000000000..48623a6b22e12 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/NamespacedAttributeBagTest.php @@ -0,0 +1,162 @@ + + */ +class NamespacedAttributeBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var array + */ + private $array; + + /** + * @var NamespacedAttributeBag + */ + private $bag; + + protected function setUp() + { + $this->array = array( + 'hello' => 'world', + 'always' => 'be happy', + 'user.login' => 'drak', + 'csrf.token' => array( + 'a' => '1234', + 'b' => '4321', + ), + 'category' => array( + 'fishing' => array( + 'first' => 'cod', + 'second' => 'sole') + ), + ); + $this->bag = new NamespacedAttributeBag('_sf2', '/'); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->bag = null; + $this->array = array(); + } + + public function testInitialize() + { + $bag = new NamespacedAttributeBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $this->bag->all()); + $array = array('should' => 'not stick'); + $bag->initialize($array); + + // should have remained the same + $this->assertEquals($this->array, $this->bag->all()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2', $this->bag->getStorageKey()); + $attributeBag = new NamespacedAttributeBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + /** + * @dataProvider attributesProvider + */ + public function testHas($key, $value, $exists) + { + $this->assertEquals($exists, $this->bag->has($key)); + } + + /** + * @dataProvider attributesProvider + */ + public function testGet($key, $value, $expected) + { + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testGetDefaults() + { + $this->assertNull($this->bag->get('user2.login')); + $this->assertEquals('default', $this->bag->get('user2.login', 'default')); + } + + /** + * @dataProvider attributesProvider + */ + public function testSet($key, $value, $expected) + { + $this->bag->set($key, $value); + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testAll() + { + $this->assertEquals($this->array, $this->bag->all()); + + $this->bag->set('hello', 'fabien'); + $array = $this->array; + $array['hello'] = 'fabien'; + $this->assertEquals($array, $this->bag->all()); + } + + public function testReplace() + { + $array = array(); + $array['name'] = 'jack'; + $array['foo.bar'] = 'beep'; + $this->bag->replace($array); + $this->assertEquals($array, $this->bag->all()); + $this->assertNull($this->bag->get('hello')); + $this->assertNull($this->bag->get('always')); + $this->assertNull($this->bag->get('user.login')); + } + + public function testRemove() + { + $this->assertEquals('world', $this->bag->get('hello')); + $this->bag->remove('hello'); + $this->assertNull($this->bag->get('hello')); + + $this->assertEquals('be happy', $this->bag->get('always')); + $this->bag->remove('always'); + $this->assertNull($this->bag->get('always')); + + $this->assertEquals('drak', $this->bag->get('user.login')); + $this->bag->remove('user.login'); + $this->assertNull($this->bag->get('user.login')); + } + + public function testClear() + { + $this->bag->clear(); + $this->assertEquals(array(), $this->bag->all()); + } + + public function attributesProvider() + { + return array( + array('hello', 'world', true), + array('always', 'be happy', true), + array('user.login', 'drak', true), + array('csrf.token', array('a' => '1234', 'b' => '4321'), true), + array('csrf.token/a', '1234', true), + array('csrf.token/b', '4321', true), + array('category', array('fishing' => array('first' => 'cod', 'second' => 'sole')), true), + array('category/fishing', array('first' => 'cod', 'second' => 'sole'), true), + array('category/fishing/first', 'cod', true), + array('category/fishing/second', 'sole', true), + array('user2.login', null, false), + array('never', null, false), + array('bye', null, false), + array('bye/for/now', null, false), + ); + } +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php b/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php index cde875a4c24a3..e1f610e0db7b3 100644 --- a/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php +++ b/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php @@ -13,10 +13,10 @@ use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage; - use Symfony\Component\HttpFoundation\Session; - use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\FlashBag; +use Symfony\Component\HttpFoundation\AttributeBag; class RequestTest extends \PHPUnit_Framework_TestCase { @@ -835,7 +835,7 @@ public function testHasSession() $request = new Request; $this->assertFalse($request->hasSession()); - $request->setSession(new Session(new ArraySessionStorage())); + $request->setSession(new Session(new ArraySessionStorage(new AttributeBag(), new FlashBag()))); $this->assertTrue($request->hasSession()); } @@ -846,7 +846,7 @@ public function testHasPreviousSession() $this->assertFalse($request->hasPreviousSession()); $request->cookies->set(session_name(), 'foo'); $this->assertFalse($request->hasPreviousSession()); - $request->setSession(new Session(new ArraySessionStorage())); + $request->setSession(new Session(new ArraySessionStorage(new AttributeBag(), new FlashBag()))); $this->assertTrue($request->hasPreviousSession()); } diff --git a/tests/Symfony/Tests/Component/HttpFoundation/SessionStorage/ArraySessionStorageTest.php b/tests/Symfony/Tests/Component/HttpFoundation/SessionStorage/ArraySessionStorageTest.php new file mode 100644 index 0000000000000..6e7b921ea76bb --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/SessionStorage/ArraySessionStorageTest.php @@ -0,0 +1,90 @@ + + */ +class ArraySessionStorageTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ArraySessionStorage + */ + private $storage; + + /** + * @var FlashBag + */ + private $flashBag; + + /** + * @var AttributeBag + */ + private $attributeBag; + + private $attributes; + private $flashes; + + protected function setUp() + { + $this->attributes = array('foo' => 'bar'); + $this->flashes = array('notice' => 'hello'); + $this->flashBag = new FlashBag(); + $this->flashBag->initialize($this->flashes); + $this->attributeBag = new AttributeBag(); + $this->attributeBag->initialize($this->attributes); + $this->storage = new ArraySessionStorage($this->attributeBag, $this->flashBag); + } + + protected function tearDown() + { + $this->flashBag = null; + $this->attributesBag = null; + $this->storage = null; + } + + public function testStart() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $id = $this->storage->getId(); + $this->assertNotEquals('', $id); + $this->storage->start(); + $this->assertEquals($id, $this->storage->getId()); + } + + public function testRegenerateDestroy() + { + $this->storage->start(); + $id = $this->storage->getId(); + $this->storage->regenerate(true); + $this->assertNotEquals($id, $this->storage->getId()); + $this->assertEquals(array(), $this->storage->getAttributes()->all()); + $this->assertEquals(array(), $this->storage->getFlashes()->all()); + } + + public function testRegenerate() + { + $this->storage->start(); + $id = $this->storage->getId(); + $this->storage->regenerate(); + $this->assertNotEquals($id, $this->storage->getId()); + + $this->assertEquals($this->attributes, $this->storage->getAttributes()->all()); + $this->assertEquals($this->flashes, $this->storage->getFlashes()->all()); + } + + public function testGetId() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $this->assertNotEquals('', $this->storage->getId()); + } +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/SessionStorage/FilesystemSessionStorageTest.php b/tests/Symfony/Tests/Component/HttpFoundation/SessionStorage/FilesystemSessionStorageTest.php deleted file mode 100644 index 060cb0e913e6e..0000000000000 --- a/tests/Symfony/Tests/Component/HttpFoundation/SessionStorage/FilesystemSessionStorageTest.php +++ /dev/null @@ -1,104 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Tests\Component\HttpFoundation\SessionStorage; - -use Symfony\Component\HttpFoundation\SessionStorage\FilesystemSessionStorage; - -class FilesystemSessionStorageTest extends \PHPUnit_Framework_TestCase -{ - private $path; - - protected function setUp() - { - $this->path = sys_get_temp_dir().'/sf2/session_test'; - if (!file_exists($this->path)) { - mkdir($this->path, 0777, true); - } - } - - protected function tearDown() - { - array_map('unlink', glob($this->path.'/*.session')); - rmdir($this->path); - - $this->path = null; - } - - public function testMultipleInstances() - { - $storage1 = new FilesystemSessionStorage($this->path); - $storage1->start(); - $storage1->write('foo', 'bar'); - - $storage2 = new FilesystemSessionStorage($this->path); - $storage2->start(); - $this->assertEquals('bar', $storage2->read('foo'), 'values persist between instances'); - } - - public function testGetIdThrowsErrorBeforeStart() - { - $this->setExpectedException('RuntimeException'); - - $storage = new FilesystemSessionStorage($this->path); - $storage->getId(); - } - - public function testGetIdWorksAfterStart() - { - $storage = new FilesystemSessionStorage($this->path); - $storage->start(); - $storage->getId(); - } - - public function testGetIdSetByOptions() - { - $previous = ini_get('session.use_cookies'); - - ini_set('session.use_cookies', false); - - $storage = new FilesystemSessionStorage($this->path, array('id' => 'symfony2-sessionId')); - $storage->start(); - - $this->assertEquals('symfony2-sessionId', $storage->getId()); - - ini_set('session.use_cookies', $previous); - } - - public function testRemoveVariable() - { - $storage = new FilesystemSessionStorage($this->path); - $storage->start(); - - $storage->write('foo', 'bar'); - - $this->assertEquals('bar', $storage->read('foo')); - - $storage->remove('foo', 'bar'); - - $this->assertNull($storage->read('foo')); - } - - public function testRegenerate() - { - $storage = new FilesystemSessionStorage($this->path); - $storage->start(); - $storage->write('foo', 'bar'); - - $storage->regenerate(); - - $this->assertEquals('bar', $storage->read('foo')); - - $storage->regenerate(true); - - $this->assertNull($storage->read('foo')); - } -} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/SessionTest.php b/tests/Symfony/Tests/Component/HttpFoundation/SessionTest.php index 8318101e665d1..179c6fa553e94 100644 --- a/tests/Symfony/Tests/Component/HttpFoundation/SessionTest.php +++ b/tests/Symfony/Tests/Component/HttpFoundation/SessionTest.php @@ -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,117 +23,150 @@ * * @author Fabien Potencier * @author Robert Schönthal + * @author Drak */ class SessionTest extends \PHPUnit_Framework_TestCase { + /** + * @var \Symfony\Component\HttpFoundation\SessionStorage\SessionStorageInterface + */ protected $storage; + + /** + * @var \Symfony\Component\HttpFoundation\SessionInterface + */ protected $session; + /** + * @var \Symfony\Component\HttpFoundation\FlashBagInterface + */ + protected $flashBag; + + /** + * @var \Symfony\Component\HttpFoundation\AttributeBagInterface + */ + protected $attributeBag; + public function setUp() { - $this->storage = new ArraySessionStorage(); - $this->session = $this->getSession(); + $this->flashBag = new FlashBag(); + $this->attributesBag = new AttributeBag(); + $this->storage = new ArraySessionStorage($this->attributesBag, $this->flashBag); + $this->session = new Session($this->storage); } protected function tearDown() { $this->storage = null; + $this->flashBag = null; + $this->attributesBag = null; $this->session = null; } - public function testFlash() + public function test__Constructor() { - $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 tests the defaults on the Session object constructor + $storage = new ArraySessionStorage($this->attributesBag, $this->flashBag); + $session = new Session($storage); + $this->assertSame($this->flashBag, $storage->getFlashes()); } - public function testFlashesAreFlushedWhenNeeded() + public function testStart() { - $this->session->setFlash('foo', 'bar'); - $this->session->save(); - - $this->session = $this->getSession(); - $this->assertTrue($this->session->hasFlash('foo')); - $this->session->save(); + $this->assertEquals('', $this->storage->getId()); + $this->session->start(); + $this->assertNotEquals('', $this->storage->getId()); + } - $this->session = $this->getSession(); - $this->assertFalse($this->session->hasFlash('foo')); + public function testGetFlashes() + { + $this->assertTrue($this->session->getFlashes() instanceof FlashBagInterface); } - public function testAll() + public function testGet() { - $this->assertFalse($this->session->has('foo')); + // tests defaults $this->assertNull($this->session->get('foo')); + $this->assertEquals(1, $this->session->get('foo', 1)); + } - $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(); + /** + * @dataProvider setProvider + */ + public function testSet($key, $value) + { + $this->session->set($key, $value); + $this->assertEquals($value, $this->session->get($key)); + } - $this->session->replace($attrs); + 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->assertSame($attrs, $this->session->all()); + /** + * @dataProvider setProvider + */ + public function testAll($key, $value, $result) + { + $this->session->set($key, $value); + $this->assertEquals($result, $this->session->all()); + } + /** + * @dataProvider setProvider + */ + public function testClear($key, $value) + { + $this->session->set('hi', 'fabien'); + $this->session->set($key, $value); $this->session->clear(); - - $this->assertSame(array(), $this->session->all()); + $this->assertEquals(array(), $this->session->all()); } - public function testMigrateAndInvalidate() + public function setProvider() { - $this->session->set('foo', 'bar'); - $this->session->setFlash('foo', 'bar'); - - $this->assertSame('bar', $this->session->get('foo')); - $this->assertSame('bar', $this->session->getFlash('foo')); - - $this->session->migrate(); + 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')), + ); + } - $this->assertSame('bar', $this->session->get('foo')); - $this->assertSame('bar', $this->session->getFlash('foo')); + /** + * @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()); + } - $this->session = $this->getSession(); + public function testInvalidate() + { + $this->session->set('invalidate', 123); + $this->session->getFlashes()->add('OK'); $this->session->invalidate(); + $this->assertEquals(array(), $this->session->all()); + $this->assertEquals(array(), $this->session->getFlashes()->all()); + } - $this->assertSame(array(), $this->session->all()); - $this->assertSame(array(), $this->session->getFlashes()); + public function testMigrate() + { + $this->session->set('migrate', 321); + $this->session->getFlashes()->add('OK'); + $this->session->migrate(); + $this->assertEquals(321, $this->session->get('migrate')); + $this->assertEquals(array('OK'), $this->session->getFlashes()->get(FlashBag::NOTICE)); } public function testSerialize() { - $this->session = new Session($this->storage); - $compare = serialize($this->storage); $this->assertSame($compare, $this->session->serialize()); @@ -142,91 +179,36 @@ public function testSerialize() $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() + public function testGetId() { + $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() + public function flashAdd() { - $this->session->set('foo', 'bar'); - - $this->session->__destruct(); - - $expected = array( - 'attributes'=>array('foo'=>'bar'), - 'flashes'=>array(), - ); - $saved = $this->storage->read('_symfony2'); - $this->assertSame($expected, $saved); - } - - public function testSavedOnDestructAfterManualSave() - { - $this->session->set('foo', 'nothing'); - $this->session->save(); - $this->session->set('foo', 'bar'); - - $this->session->__destruct(); - - $expected = array( - 'attributes'=>array('foo'=>'bar'), - 'flashes'=>array(), - ); - $saved = $this->storage->read('_symfony2'); - $this->assertSame($expected, $saved); - } - - public function testStorageRegenerate() - { - $this->storage->write('foo', 'bar'); - - $this->assertTrue($this->storage->regenerate()); - - $this->assertEquals('bar', $this->storage->read('foo')); - - $this->assertTrue($this->storage->regenerate(true)); - - $this->assertNull($this->storage->read('foo')); - } - - public function testStorageRemove() - { - $this->storage->write('foo', 'bar'); - - $this->assertEquals('bar', $this->storage->read('foo')); - - $this->storage->remove('foo'); - - $this->assertNull($this->storage->read('foo')); + $this->session->addFlash('Hello world', FlashBag::NOTICE); + $this->session->addFlash('Bye bye cruel world', FlashBag::NOTICE); + $this->assertEquals(array('Hello world', 'Bye by cruel world'), $this->session->getFlash(FlashBag::NOTICE)); } - protected function getSession() + public function flashGet() { - return new Session($this->storage); + $this->session->addFlash('Hello world', FlashBag::NOTICE); + $this->session->addFlash('Bye bye cruel world', FlashBag::NOTICE); + $this->assertEquals(array('Hello world', 'Bye by cruel world'), $this->session->getFlash(FlashBag::NOTICE), true); + $this->assertEquals(array('Hello world', 'Bye by cruel world'), $this->session->getFlash(FlashBag::NOTICE)); + $this->assertEquals(array(), $this->session->getFlash(FlashBag::NOTICE)); } } diff --git a/tests/Symfony/Tests/Component/Security/Http/Firewall/ContextListenerTest.php b/tests/Symfony/Tests/Component/Security/Http/Firewall/ContextListenerTest.php index dfbcd79e7faf9..60c6fe8b8c9cb 100644 --- a/tests/Symfony/Tests/Component/Security/Http/Firewall/ContextListenerTest.php +++ b/tests/Symfony/Tests/Component/Security/Http/Firewall/ContextListenerTest.php @@ -2,6 +2,8 @@ namespace Symfony\Test\Component\Security\Http\Firewall; +use Symfony\Component\HttpFoundation\FlashBag; +use Symfony\Component\HttpFoundation\AttributeBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session; @@ -63,7 +65,7 @@ public function testOnKernelResponseWillRemoveSession() protected function runSessionOnKernelResponse($newToken, $original = null) { - $session = new Session(new ArraySessionStorage()); + $session = new Session(new ArraySessionStorage(new AttributeBag(), new FlashBag())); if ($original !== null) { $session->set('_security_session', $original);