diff --git a/CHANGELOG-2.1.md b/CHANGELOG-2.1.md index 35cbb4f96e..02ff0a5301 100644 --- a/CHANGELOG-2.1.md +++ b/CHANGELOG-2.1.md @@ -224,6 +224,34 @@ 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 all session related classes and interfaces into own namespace, as + `Symfony\Component\HttpFoudation\Session` and renamed classes accordingly. + * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. + This makes the implementation ESI compatible. + * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire behaviour of messages auto expiring + after one page page load. Messages must be retrived by `get()` or `all()`. + * [BC BREAK] Removed the `close()` method from the Session class + * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()` + `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag() instead which returns a `FlashBagInterface`. + * `Session->clear()` now only clears session attributes as before it cleared flash messages and + attributes. `Session->getFlashBag()->all()` clears flashes now. + * Added `Symfony\Component\HttpFoundation\Session\Storage\AbstractSessionStorage` base class for + session storage drivers. + * Added `Symfony\Component\HttpFoundation\Session\Storage\SessionSaveHandlerInterface` interface + which storage drivers should implement after inheriting from + `Symfony\Component\HttpFoundation\Session\Storage\AbstractSessionStorage` when writing custom session save handlers. + * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and `remove()`. Added + `getBag()`, `registerBag()`. + * Moved attribute storage to `Symfony\Component\HttpFoundation\Attribute\AttributeBagInterface`. + * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate attributes storage + behaviour from 2.0.x (default). + * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for namespace session attributes. + * Session now implements `Symfony\Component\HttpFoundation\Session\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 `MockFileSessionStorage` for functional testing instead. ### HttpKernel diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md index b94b511d79..862806138b 100644 --- a/UPGRADE-2.1.md +++ b/UPGRADE-2.1.md @@ -1,6 +1,8 @@ UPGRADE FROM 2.0 to 2.1 ======================= +### General + * assets_base_urls and base_urls merging strategy has changed Unlike most configuration blocks, successive values for @@ -11,6 +13,8 @@ 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. +### [HttpFoundation] + * moved management of the locale from the Session class to the Request class Configuring the default locale: @@ -28,17 +32,20 @@ UPGRADE FROM 2.0 to 2.1 Retrieving the locale from a Twig template: - Before: `{{ app.request.session.locale }}` or `{{ app.session.locale }}` + Before: `{{ app.request.session.locale }}` or `{{ app.session.locale }}` + After: `{{ app.request.locale }}` Retrieving the locale from a PHP template: - Before: `$view['session']->getLocale()` + Before: `$view['session']->getLocale()` + After: `$view['request']->getLocale()` Retrieving the locale from PHP code: - Before: `$session->getLocale()` + Before: `$session->getLocale()` + After: `$request->getLocale()` * Method `equals` of `Symfony\Component\Security\Core\User\UserInterface` has @@ -134,7 +141,7 @@ UPGRADE FROM 2.0 to 2.1 * The strategy for generating the HTML attributes "id" and "name" of choices in a choice field has changed - + Instead of appending the choice value, a generated integer is now appended by default. Take care if your Javascript relies on that. If you can guarantee that your choice values only contain ASCII letters, digits, @@ -144,7 +151,7 @@ UPGRADE FROM 2.0 to 2.1 * The strategy for generating the HTML attributes "value" of choices in a choice field has changed - + Instead of using the choice value, a generated integer is now stored. Again, take care if your Javascript reads this value. If your choice field is a non-expanded single-choice field, or if the choices are guaranteed not @@ -248,3 +255,63 @@ UPGRADE FROM 2.0 to 2.1 { return isset($options['widget']) && 'single_text' === $options['widget'] ? 'text' : 'choice'; } + +* Flash Messages now returns and array based on type (the old method are still available but deprecated) + + Before (PHP): + + hasFlash('notice')): ?> +
+ getFlash('notice') ?> +
+ + + After (PHP): + + getFlashBag()->has('notice')): ?> +
+ getFlashBag()->get('notice') ?> +
+ + + If you wanted to process all flash types you could also make use of the `getFlashBag()->all()` API: + + getFlashBag()->all() as $type => $flash): ?> +
+ +
+ + + Before (Twig): + + {% if app.session.hasFlash('notice') %} +
+ {{ app.session.flash('notice') }} +
+ {% endif %} + + After (Twig): + + {% if app.session.flashes.has('notice') %} +
+ {{ app.session.flashes.get('notice') }} +
+ {% endif %} + + Again you can process all flash messages in one go with + + {% for type, flashMessage in app.session.flashes.all() %} +
+ {{ flashMessage }} +
+ {% endforeach %} + +* Session storage drivers + + Session storage drivers should inherit from + `Symfony\Component\HttpFoundation\Session\Storage\AbstractSessionStorage` + and no longer should implement `read()`, `write()`, `remove()` which were removed from the + `SessionStorageInterface`. + + Any session storage driver that wants to use custom save handlers should + implement `Symfony\Component\HttpFoundation\Session\Storage\SaveHandlerInterface` diff --git a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php index 6fa324f85e..cda9dd2a0a 100644 --- a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php +++ b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php @@ -3,7 +3,8 @@ namespace Symfony\Bridge\Doctrine\HttpFoundation; use Doctrine\DBAL\Platforms\MySqlPlatform; -use Symfony\Component\HttpFoundation\SessionStorage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\AbstractSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\SessionSaveHandlerInterface; use Doctrine\DBAL\Driver\Connection; /** @@ -12,39 +13,30 @@ use Doctrine\DBAL\Driver\Connection; * @author Fabien Potencier * @author Johannes M. Schmitt */ -class DbalSessionStorage extends NativeSessionStorage +class DbalSessionStorage extends AbstractSessionStorage implements SessionSaveHandlerInterface { + /** + * @var Connection + */ private $con; - private $tableName; - - public function __construct(Connection $con, $tableName = 'sessions', array $options = array()) - { - parent::__construct($options); - - $this->con = $con; - $this->tableName = $tableName; - } /** - * Starts the session. - */ - public function start() + * @var string + */ + private $tableName; + + /** + * + * @param Connection $con An instance of Connection. + * @param string $tableName Table name. + * @param array $options Session configuration options + */ + public function __construct(Connection $con, $tableName = 'sessions', array $options = array()) { - if (self::$sessionStarted) { - return; - } + $this->con = $con; + $this->tableName = $tableName; - // 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($options); } /** @@ -55,7 +47,7 @@ class DbalSessionStorage extends NativeSessionStorage * * @return Boolean true, if the session was opened, otherwise an exception is thrown */ - public function sessionOpen($path = null, $name = null) + public function openSession($path = null, $name = null) { return true; } @@ -65,7 +57,7 @@ class DbalSessionStorage extends NativeSessionStorage * * @return Boolean true, if the session was closed, otherwise false */ - public function sessionClose() + public function closeSession() { // do nothing return true; @@ -80,7 +72,7 @@ class DbalSessionStorage extends NativeSessionStorage * * @throws \RuntimeException If the session cannot be destroyed */ - public function sessionDestroy($id) + public function destroySession($id) { try { $this->con->executeQuery("DELETE FROM {$this->tableName} WHERE sess_id = :id", array( @@ -102,7 +94,7 @@ class DbalSessionStorage extends NativeSessionStorage * * @throws \RuntimeException If any old sessions cannot be cleaned */ - public function sessionGC($lifetime) + public function gcSession($lifetime) { try { $this->con->executeQuery("DELETE FROM {$this->tableName} WHERE sess_time < :time", array( @@ -124,7 +116,7 @@ class DbalSessionStorage extends NativeSessionStorage * * @throws \RuntimeException If the session cannot be read */ - public function sessionRead($id) + public function readSession($id) { try { $data = $this->con->executeQuery("SELECT sess_data FROM {$this->tableName} WHERE sess_id = :id", array( @@ -140,7 +132,7 @@ class DbalSessionStorage extends NativeSessionStorage return ''; } catch (\PDOException $e) { - throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); + throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e); } } @@ -154,7 +146,7 @@ class DbalSessionStorage extends NativeSessionStorage * * @throws \RuntimeException If the session data cannot be written */ - public function sessionWrite($id, $data) + public function writeSession($id, $data) { $platform = $this->con->getDatabasePlatform(); @@ -181,7 +173,7 @@ class DbalSessionStorage extends NativeSessionStorage $this->createNewSession($id, $data); } } catch (\PDOException $e) { - throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); + throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e); } return true; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 5195ff1c2f..6738eb3e4a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -167,7 +167,7 @@ class Configuration implements ConfigurationInterface ->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/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 2e80aea918..d635a7f598 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -301,7 +301,7 @@ class FrameworkExtension extends Extension $this->addClassesToCompile(array( 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\SessionListener', - 'Symfony\\Component\\HttpFoundation\\SessionStorage\\SessionStorageInterface', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface', $container->getDefinition('session')->getClass(), )); diff --git a/src/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.php b/src/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.php index 7d6ac49a06..d335758b70 100644 --- a/src/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.php +++ b/src/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.php @@ -69,7 +69,6 @@ class TestSessionListener implements EventSubscriberInterface if ($session = $event->getRequest()->getSession()) { $session->save(); - $session->close(); $params = session_get_cookie_params(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml index c3c2eba0c5..31d374aa6b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml @@ -5,22 +5,31 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - Symfony\Component\HttpFoundation\Session - Symfony\Component\HttpFoundation\SessionStorage\NativeSessionStorage - Symfony\Component\HttpFoundation\SessionStorage\FilesystemSessionStorage + Symfony\Component\HttpFoundation\Session\Session + Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag + Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag + Symfony\Component\HttpFoundation\Session\Storage\NativeFileSessionStorage + Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage Symfony\Bundle\FrameworkBundle\EventListener\SessionListener + + - + + + + + + %kernel.cache_dir%/sessions %session.storage.options% - + %kernel.cache_dir%/sessions %session.storage.options% @@ -29,5 +38,9 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.php b/src/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.php index 08d32c86cf..f77ca12436 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.php @@ -79,7 +79,7 @@ class GlobalVariables /** * Returns the current session. * - * @return Symfony\Component\HttpFoundation\Session|void The session + * @return Symfony\Component\HttpFoundation\Session\Session|void The session */ public function getSession() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/SessionHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/SessionHelper.php index 041d9ddff9..c6628172f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/SessionHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/SessionHelper.php @@ -48,17 +48,17 @@ class SessionHelper extends Helper public function getFlash($name, $default = null) { - return $this->session->getFlash($name, $default); + return $this->session->getFlashBag()->get($name); } public function getFlashes() { - return $this->session->getFlashes(); + return $this->session->getFlashBag()->all(); } public function hasFlash($name) { - return $this->session->hasFlash($name); + return $this->session->getFlashBag()->has($name); } /** 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 01361f2280..3ab6488493 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 @@ $container->loadFromExtension('framework', array( ), '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 2d6a06047e..e46a476a96 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 3c7db0ee49..126baf2d4e 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 3da5483944..6aa952e3a6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -78,7 +78,7 @@ abstract class FrameworkExtensionTest extends TestCase $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 7c656ba7e0..fe551ba360 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/EventListener/TestSessionListenerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/EventListener/TestSessionListenerTest.php @@ -94,7 +94,7 @@ class TestSessionListenerTest extends \PHPUnit_Framework_TestCase private function getSession() { - return $this->getMockBuilder('Symfony\Component\HttpFoundation\Session') + return $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Session') ->disableOriginalConstructor() ->getMock(); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SessionController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SessionController.php index e77f1f1266..b1c4334cde 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SessionController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SessionController.php @@ -51,7 +51,7 @@ class SessionController extends ContainerAware { $request = $this->container->get('request'); $session = $request->getSession(); - $session->setFlash('notice', $message); + $session->getFlashBag()->set('notice', $message); return new RedirectResponse($this->container->get('router')->generate('session_showflash')); } @@ -61,8 +61,8 @@ class SessionController extends ContainerAware $request = $this->container->get('request'); $session = $request->getSession(); - if ($session->hasFlash('notice')) { - $output = $session->getFlash('notice'); + if ($session->getFlashBag()->has('notice')) { + $output = $session->getFlashBag()->get('notice'); } else { $output = 'No flash was set.'; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml index 18ed6f4d87..0cc6b74d30 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml +++ b/src/Symfony/Bundle/FrameworkBundle/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.mock_file services: logger: { class: Symfony\Component\HttpKernel\Log\NullLogger } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/SessionHelperTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/SessionHelperTest.php index ee400cd7dc..ce8c91b173 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/SessionHelperTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/SessionHelperTest.php @@ -12,8 +12,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Session; -use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Bundle\FrameworkBundle\Templating\Helper\SessionHelper; class SessionHelperTest extends \PHPUnit_Framework_TestCase @@ -24,9 +24,9 @@ class SessionHelperTest extends \PHPUnit_Framework_TestCase { $this->request = new Request(); - $session = new Session(new ArraySessionStorage()); + $session = new Session(new MockArraySessionStorage()); $session->set('foobar', 'bar'); - $session->setFlash('foo', 'bar'); + $session->getFlashBag()->set('notice', 'bar'); $this->request->setSession($session); } @@ -40,14 +40,15 @@ class SessionHelperTest extends \PHPUnit_Framework_TestCase { $helper = new SessionHelper($this->request); - $this->assertTrue($helper->hasFlash('foo')); + $this->assertTrue($helper->hasFlash('notice')); - $this->assertEquals('bar', $helper->getFlash('foo')); - $this->assertEquals('foo', $helper->getFlash('bar', 'foo')); + $this->assertEquals('bar', $helper->getFlash('notice')); + } - $this->assertNull($helper->getFlash('foobar')); - - $this->assertEquals(array('foo' => 'bar'), $helper->getFlashes()); + public function testGetFlashes() + { + $helper = new SessionHelper($this->request); + $this->assertEquals(array('notice' => '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 cefc38b0ef..476b398e61 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/PhpEngineTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/PhpEngineTest.php @@ -14,8 +14,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Templating; use Symfony\Bundle\FrameworkBundle\Templating\PhpEngine; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Session; -use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\Templating\TemplateNameParser; use Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; @@ -64,7 +64,7 @@ class PhpEngineTest extends TestCase { $container = new Container(); $request = new Request(); - $session = new Session(new ArraySessionStorage()); + $session = new Session(new MockArraySessionStorage()); $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 18ed6f4d87..0cc6b74d30 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.mock_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 77b79dd545..f15ef3f945 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/TwigEngineTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/TwigEngineTest.php @@ -14,8 +14,8 @@ namespace Symfony\Bundle\TwigBundle\Tests; use Symfony\Bundle\TwigBundle\TwigEngine; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Session; -use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\Templating\TemplateNameParser; use Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables; @@ -71,7 +71,7 @@ class TwigEngineTest extends TestCase { $container = new Container(); $request = new Request(); - $session = new Session(new ArraySessionStorage()); + $session = new Session(new MockArraySessionStorage()); $request->setSession($session); $container->set('request', $request); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index d6235025d0..491ca0b6ea 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerAware; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag; /** * ProfilerController. @@ -146,9 +147,9 @@ class ProfilerController extends ContainerAware { $request = $this->container->get('request'); - if (null !== $session = $request->getSession()) { - // keep current flashes for one more request - $session->setFlashes($session->getFlashes()); + if (null !== $session = $request->getSession() && $session->getFlashBag() instanceof AutoExpireFlashBag) { + // keep current flashes for one more request if using AutoExpireFlashBag + $session->getFlashBag()->setAll($session->getFlashBag()->peekAll()); } if (null === $token) { diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index a1b6d31a7c..b9418e6b2a 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Bundle\TwigBundle\TwigEngine; +use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag; /** * WebDebugToolbarListener injects the Web Debug Toolbar. @@ -70,9 +71,10 @@ class WebDebugToolbarListener } 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()); + $session = $request->getSession(); + if ($session->getFlashBag() instanceof AutoExpireFlashBag) { + // keep current flashes for one more request if using AutoExpireFlashBag + $session->getFlashBag()->setAll($session->getFlashBag()->peekAll()); } $response->setContent($this->templating->render('WebProfilerBundle:Profiler:toolbar_redirect.html.twig', array('location' => $response->headers->get('Location')))); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php index b6986176e8..ce75b593dc 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php @@ -177,7 +177,7 @@ class WebDebugToolbarListenerTest extends \PHPUnit_Framework_TestCase protected function getRequestMock($isXmlHttpRequest = false, $requestFormat = 'html') { - $session = $this->getMock('Symfony\Component\HttpFoundation\Session', array(), array(), '', false); + $session = $this->getMock('Symfony\Component\HttpFoundation\Session\Session', array(), array(), '', false); $request = $this->getMock( 'Symfony\Component\HttpFoundation\Request', array('getSession', 'isXmlHttpRequest', 'getRequestFormat'), diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php index d52fcac4b3..5174412a97 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; -use Symfony\Component\HttpFoundation\Session; +use Symfony\Component\HttpFoundation\Session\Session; /** * This provider uses a Symfony2 Session object to retrieve the user's diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index fc33610906..beab652a42 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpFoundation; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + /** * Request represents an HTTP request. * @@ -122,7 +124,7 @@ class Request protected $format; /** - * @var \Symfony\Component\HttpFoundation\Session + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface */ protected $session; @@ -466,7 +468,7 @@ class Request /** * Gets the Session. * - * @return Session|null The session + * @return SessionInterface|null The session * * @api */ @@ -504,11 +506,11 @@ class Request /** * 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; } diff --git a/src/Symfony/Component/HttpFoundation/Session.php b/src/Symfony/Component/HttpFoundation/Session.php deleted file mode 100644 index 721a6c7240..0000000000 --- a/src/Symfony/Component/HttpFoundation/Session.php +++ /dev/null @@ -1,358 +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; - -use Symfony\Component\HttpFoundation\SessionStorage\SessionStorageInterface; - -/** - * Session. - * - * @author Fabien Potencier - * - * @api - */ -class Session implements \Serializable -{ - protected $storage; - protected $started; - protected $attributes; - protected $flashes; - protected $oldFlashes; - protected $closed; - - /** - * Constructor. - * - * @param SessionStorageInterface $storage A SessionStorageInterface instance - */ - public function __construct(SessionStorageInterface $storage) - { - $this->storage = $storage; - $this->flashes = array(); - $this->oldFlashes = array(); - $this->attributes = array(); - $this->started = false; - $this->closed = false; - } - - /** - * Starts the session storage. - * - * @api - */ - public function start() - { - if (true === $this->started) { - return; - } - - $this->storage->start(); - - $attributes = $this->storage->read('_symfony2'); - - if (isset($attributes['attributes'])) { - $this->attributes = $attributes['attributes']; - $this->flashes = $attributes['flashes']; - - // flag current flash messages to be removed at shutdown - $this->oldFlashes = $this->flashes; - } - - $this->started = true; - } - - /** - * Checks if an attribute is defined. - * - * @param string $name The attribute name - * - * @return Boolean true if the attribute is defined, false otherwise - * - * @api - */ - public function has($name) - { - return array_key_exists($name, $this->attributes); - } - - /** - * Returns an attribute. - * - * @param string $name The attribute name - * @param mixed $default The default value - * - * @return mixed - * - * @api - */ - public function get($name, $default = null) - { - return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; - } - - /** - * Sets an attribute. - * - * @param string $name - * @param mixed $value - * - * @api - */ - public function set($name, $value) - { - if (false === $this->started) { - $this->start(); - } - - $this->attributes[$name] = $value; - } - - /** - * Returns attributes. - * - * @return array Attributes - * - * @api - */ - public function all() - { - return $this->attributes; - } - - /** - * Sets attributes. - * - * @param array $attributes Attributes - * - * @api - */ - public function replace(array $attributes) - { - if (false === $this->started) { - $this->start(); - } - - $this->attributes = $attributes; - } - - /** - * Removes an attribute. - * - * @param string $name - * - * @api - */ - public function remove($name) - { - if (false === $this->started) { - $this->start(); - } - - if (array_key_exists($name, $this->attributes)) { - unset($this->attributes[$name]); - } - } - - /** - * Clears all attributes. - * - * @api - */ - public function clear() - { - if (false === $this->started) { - $this->start(); - } - - $this->attributes = array(); - $this->flashes = array(); - } - - /** - * Invalidates the current session. - * - * @api - */ - public function invalidate() - { - $this->clear(); - $this->storage->regenerate(true); - } - - /** - * Migrates the current session to a new session id while maintaining all - * session attributes. - * - * @api - */ - public function migrate() - { - $this->storage->regenerate(); - } - - /** - * Returns 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. - * - * @return array - */ - public function getFlashes() - { - return $this->flashes; - } - - /** - * Sets the flash messages. - * - * @param array $values - */ - public function setFlashes($values) - { - if (false === $this->started) { - $this->start(); - } - - $this->flashes = $values; - $this->oldFlashes = array(); - } - - /** - * Gets a flash message. - * - * @param string $name - * @param string|null $default - * - * @return string - */ - public function getFlash($name, $default = null) - { - return array_key_exists($name, $this->flashes) ? $this->flashes[$name] : $default; - } - - /** - * Sets a flash message. - * - * @param string $name - * @param string $value - */ - public function setFlash($name, $value) - { - if (false === $this->started) { - $this->start(); - } - - $this->flashes[$name] = $value; - unset($this->oldFlashes[$name]); - } - - /** - * Checks whether a flash message exists. - * - * @param string $name - * - * @return Boolean - */ - public function hasFlash($name) - { - if (false === $this->started) { - $this->start(); - } - - return array_key_exists($name, $this->flashes); - } - - /** - * Removes a flash message. - * - * @param string $name - */ - public function removeFlash($name) - { - if (false === $this->started) { - $this->start(); - } - - unset($this->flashes[$name]); - } - - /** - * Removes the flash messages. - */ - public function clearFlashes() - { - if (false === $this->started) { - $this->start(); - } - - $this->flashes = array(); - $this->oldFlashes = array(); - } - - public function save() - { - if (false === $this->started) { - $this->start(); - } - - $this->flashes = array_diff_key($this->flashes, $this->oldFlashes); - - $this->storage->write('_symfony2', array( - 'attributes' => $this->attributes, - 'flashes' => $this->flashes, - )); - } - - /** - * This method should be called when you don't want the session to be saved - * when the Session object is garbaged collected (useful for instance when - * you want to simulate the interaction of several users/sessions in a single - * PHP process). - */ - public function close() - { - $this->closed = true; - } - - public function __destruct() - { - if (true === $this->started && !$this->closed) { - $this->save(); - } - } - - public function serialize() - { - return serialize($this->storage); - } - - public function unserialize($serialized) - { - $this->storage = unserialize($serialized); - $this->attributes = array(); - $this->started = false; - } -} diff --git a/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php new file mode 100644 index 0000000000..2915b03297 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.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\Session\Attribute; + +/** + * This class relates to session attribute storage + */ +class AttributeBag implements AttributeBagInterface +{ + private $name = 'attributes'; + + /** + * @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 getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$attributes) + { + $this->attributes = &$attributes; + } + + /** + * {@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() + { + $return = $this->attributes; + $this->attributes = array(); + + return $return; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php new file mode 100644 index 0000000000..af16d642ef --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Attributes store. + * + * @author Drak + */ +interface AttributeBagInterface extends SessionBagInterface +{ + /** + * 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); +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Attribute/NamespacedAttributeBag.php b/src/Symfony/Component/HttpFoundation/Session/Attribute/NamespacedAttributeBag.php new file mode 100644 index 0000000000..7a32405096 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Attribute/NamespacedAttributeBag.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * 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() + { + $return = $this->attributes; + $this->attributes = array(); + + return $return; + } + + /** + * 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/Session/Flash/AutoExpireFlashBag.php b/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php new file mode 100644 index 0000000000..1c7b9e9d07 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * AutoExpireFlashBag flash message container. + * + * @author Drak + */ +class AutoExpireFlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + + /** + * Flash messages. + * + * @var array + */ + private $flashes = array(); + + /** + * 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; + $this->flashes = array('display' => array(), 'new' => array()); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + + // The logic: messages from the last request will be stored in new, so we move them to previous + // This request we will show what is in 'display'. What is placed into 'new' this time round will + // be moved to display next time round. + $this->flashes['display'] = array_key_exists('new', $this->flashes) ? $this->flashes['new'] : array(); + $this->flashes['new'] = array(); + } + + /** + * {@inheritdoc} + */ + public function peek($type, $default = null) + { + return $this->has($type) ? $this->flashes['display'][$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return array_key_exists('display', $this->flashes) ? (array)$this->flashes['display'] : array(); + } + + /** + * {@inheritdoc} + */ + public function get($type, $default = null) + { + if (!$this->has($type)) { + return $default; + } + + $return = null; + if (isset($this->flashes['new'][$type])) { + unset($this->flashes['new'][$type]); + } + + if (isset($this->flashes['display'][$type])) { + $return = $this->flashes['display'][$type]; + unset($this->flashes['display'][$type]); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->flashes['display']; + $this->flashes = array('new' => array(), 'display' => array()); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes['new'] = $messages; + } + + /** + * {@inheritdoc} + */ + public function set($type, $message) + { + $this->flashes['new'][$type] = $message; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes['display']); + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes['display']); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $return = $this->all(); + $this->flashes = array('display' => array(), 'new' => array()); + + return $return; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php b/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php new file mode 100644 index 0000000000..cbb77af03d --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * FlashBag flash message container. + * + * @author Drak + */ +class FlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + + /** + * Flash messages. + * + * @var array + */ + private $flashes = array(); + + /** + * 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 getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + } + + /** + * {@inheritdoc} + */ + public function peek($type, $default = null) + { + return $this->has($type) ? $this->flashes[$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return $this->flashes; + } + + /** + * {@inheritdoc} + */ + public function get($type, $default = null) + { + if (!$this->has($type)) { + return $default; + } + + $return = $this->flashes[$type]; + + unset($this->flashes[$type]); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->peekAll(); + $this->flashes = array(); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function set($type, $message) + { + $this->flashes[$type] = $message; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes = $messages; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes); + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php b/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php new file mode 100644 index 0000000000..0c45d74bb7 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * FlashBagInterface. + * + * @author Drak + */ +interface FlashBagInterface extends SessionBagInterface +{ + /** + * Registers a message for a given type. + * + * @param string $type + * @param string $message + */ + function set($type, $message); + + /** + * Gets flash message for a given type. + * + * @param string $type Message category type. + * @param string $default Default value if $type doee not exist. + * + * @return string + */ + function peek($type, $default = null); + + /** + * Gets all flash messages. + * + * @return array + */ + function peekAll(); + + /** + * Gets and clears flash from the stack. + * + * @param string $type + * @param string $default Default value if $type doee not exist. + * + * @return string + */ + function get($type, $default = null); + + /** + * Gets and clears flashes from the stack. + * + * @return array + */ + function all(); + + /** + * Sets all flash messages. + */ + function setAll(array $messages); + + /** + * 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 keys(); +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Session.php b/src/Symfony/Component/HttpFoundation/Session/Session.php new file mode 100644 index 0000000000..468040e3aa --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Session.php @@ -0,0 +1,318 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Session. + * + * @author Fabien Potencier + * @author Drak + * + * @api + */ +class Session implements SessionInterface +{ + /** + * Storage driver. + * + * @var SessionStorageInterface + */ + protected $storage; + + /** + * Constructor. + * + * @param SessionStorageInterface $storage A SessionStorageInterface instance. + * @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(SessionStorageInterface $storage, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + $this->storage = $storage; + $this->registerBag($attributes ?: new AttributeBag()); + $this->registerBag($flashes ?: new FlashBag()); + } + + /** + * Starts the session storage. + * + * @return boolean True if session started. + * + * @api + */ + public function start() + { + return $this->storage->start(); + } + + /** + * Checks if an attribute is defined. + * + * @param string $name The attribute name + * + * @return Boolean true if the attribute is defined, false otherwise + * + * @api + */ + public function has($name) + { + return $this->storage->getBag('attributes')->has($name); + } + + /** + * Returns an attribute. + * + * @param string $name The attribute name + * @param mixed $default The default value + * + * @return mixed + * + * @api + */ + public function get($name, $default = null) + { + return $this->storage->getBag('attributes')->get($name, $default); + } + + /** + * Sets an attribute. + * + * @param string $name + * @param mixed $value + * + * @api + */ + public function set($name, $value) + { + $this->storage->getBag('attributes')->set($name, $value); + } + + /** + * Returns attributes. + * + * @return array Attributes + * + * @api + */ + public function all() + { + return $this->storage->getBag('attributes')->all(); + } + + /** + * Sets attributes. + * + * @param array $attributes Attributes + * + * @api + */ + public function replace(array $attributes) + { + $this->storage->getBag('attributes')->replace($attributes); + } + + /** + * Removes an attribute. + * + * @param string $name + * + * @api + */ + public function remove($name) + { + return $this->storage->getBag('attributes')->remove($name); + } + + /** + * Clears all attributes. + * + * @api + */ + public function clear() + { + $this->storage->getBag('attributes')->clear(); + } + + /** + * Invalidates the current session. + * + * Clears all session attributes and flashes and regenerates the + * session and deletes the old session from persistence. + * + * @return boolean True if session invalidated, false if error. + * + * @api + */ + public function invalidate() + { + $this->storage->clear(); + + return $this->storage->regenerate(true); + } + + /** + * Migrates the current session to a new session id while maintaining all + * session attributes. + * + * @param boolean $destroy Whether to delete the old session or leave it to garbage collection. + * + * @return boolean True if session migrated, false if error + * + * @api + */ + public function migrate($destroy = false) + { + return $this->storage->regenerate($destroy); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $this->storage->save(); + } + + /** + * Returns the session ID + * + * @return mixed The session ID + * + * @api + */ + public function getId() + { + return $this->storage->getId(); + } + + /** + * Implements the \Serialize interface. + * + * @return SessionStorageInterface + */ + public function serialize() + { + return serialize($this->storage); + } + + /** + * Implements the \Serialize interface. + * + * @throws \InvalidArgumentException If the passed string does not unserialize to an instance of SessionStorageInterface + */ + public function unserialize($serialized) + { + $storage = unserialize($serialized); + if (!$storage instanceof SessionStorageInterface) { + throw new \InvalidArgumentException('Serialized data did not return a valid instance of SessionStorageInterface'); + } + + $this->storage = $storage; + } + + /** + * Registers a SessionBagInterface with the sessio. + * + * @param SessionBagInterface $bag + */ + public function registerBag(SessionBagInterface $bag) + { + $this->storage->registerBag($bag); + } + + /** + * Get's a bag instance. + * + * @param string $name + * + * @return SessionBagInterface + */ + public function getBag($name) + { + return $this->storage->getBag($name); + } + + /** + * Gets the flashbag interface. + * + * @return FlashBagInterface + */ + public function getFlashBag() + { + return $this->getBag('flashes'); + } + + // the following methods are kept for compatibility with Symfony 2.0 (they will be removed for Symfony 2.3) + + /** + * @deprecated since 2.1, will be removed from 2.3 + */ + public function getFlashes() + { + return $this->getBag('flashes')->all(); + } + + /** + * @deprecated since 2.1, will be removed from 2.3 + */ + public function setFlashes($values) + { + $this->getBag('flashes')->setAll($values); + } + + /** + * @deprecated since 2.1, will be removed from 2.3 + */ + public function getFlash($name, $default = null) + { + return $this->getBag('flashes')->get($name, $default); + } + + /** + * @deprecated since 2.1, will be removed from 2.3 + */ + public function setFlash($name, $value) + { + $this->getBag('flashes')->set($name, $value); + } + + /** + * @deprecated since 2.1, will be removed from 2.3 + */ + public function hasFlash($name) + { + return $this->getBag('flashes')->has($name); + } + + /** + * @deprecated since 2.1, will be removed from 2.3 + */ + public function removeFlash($name) + { + $this->getBag('flashes')->get($name); + } + + /** + * @deprecated since 2.1, will be removed from 2.3 + */ + public function clearFlashes() + { + return $this->getBag('flashes')->clear(); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php b/src/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php new file mode 100644 index 0000000000..50c2d4bd0c --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Session Bag store. + * + * @author Drak + */ +interface SessionBagInterface +{ + /** + * Gets this bag's name + * + * @return string + */ + function getName(); + + /** + * Initializes the Bag + * + * @param array $array + */ + function initialize(array &$array); + + /** + * Gets the storage key for this bag. + * + * @return string + */ + function getStorageKey(); + + /** + * Clears out data from bag. + * + * @return mixed Whatever data was contained. + */ + function clear(); +} diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php b/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php new file mode 100644 index 0000000000..c31da6d165 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Interface for the session. + * + * @author Drak + */ +interface SessionInterface extends \Serializable +{ + /** + * Starts the session storage. + * + * @throws \RuntimeException If session fails to start. + */ + function start(); + + /** + * Invalidates the current session. + * + * @return boolean True if session invalidated, false if error. + */ + function invalidate(); + + /** + * Migrates the current session to a new session id while maintaining all + * session attributes. + * + * @param boolean $destroy Whether to delete the old session or leave it to garbage collection. + * + * @return boolean True if session migrated, false if error. + * + * @api + */ + function migrate($destroy = false); + + /** + * Force the session to be saved and closed. + * + * This method is generally not required for real sessions as + * the session will be automatically saved at the end of + * code execution. + */ + function save(); + + /** + * 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/Session/Storage/AbstractSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/AbstractSessionStorage.php new file mode 100644 index 0000000000..c703d09a00 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/AbstractSessionStorage.php @@ -0,0 +1,339 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * This provides a base class for session attribute storage. + * + * @author Drak + */ +abstract class AbstractSessionStorage implements SessionStorageInterface +{ + /** + * Array of SessionBagInterface + * + * @var array + */ + protected $bags; + + /** + * @var array + */ + protected $options = array(); + + /** + * @var boolean + */ + protected $started = false; + + /** + * @var boolean + */ + protected $closed = false; + + /** + * Constructor. + * + * Depending on how you want the storage driver to behave you probably + * want top override this constructor entirely. + * + * List of options for $options array with their defaults. + * @see http://www.php.net/manual/en/session.configuration.php for options + * but we omit 'session.' from the beginning of the keys. + * + * auto_start, "0" + * cookie_domain, "" + * cookie_httponly, "" + * cookie_lifetime, "0" + * cookie_path, "/" + * cookie_secure, "" + * entropy_file, "" + * entropy_length, "0" + * gc_divisor, "100" + * gc_maxlifetime, "1440" + * gc_probability, "1" + * hash_bits_per_character, "4" + * hash_function, "0" + * name, "PHPSESSID" + * referer_check, "" + * save_path, "" + * serialize_handler, "php" + * use_cookies, "1" + * use_only_cookies, "1" + * use_trans_sid, "0" + * upload_progress.enabled, "1" + * upload_progress.cleanup, "1" + * upload_progress.prefix, "upload_progress_" + * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS" + * upload_progress.freq, "1%" + * upload_progress.min-freq, "1" + * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset=" + * + * @param array $options Session configuration options. + */ + public function __construct(array $options = array()) + { + $this->setOptions($options); + $this->registerSaveHandlers(); + $this->registerShutdownFunction(); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started && !$this->closed) { + return true; + } + + if ($this->options['use_cookies'] && headers_sent()) { + throw new \RuntimeException('Failed to start the session because header have already been sent.'); + } + + // start the session + if (!session_start()) { + throw new \RuntimeException('Failed to start the session'); + } + + $this->loadSession(); + + $this->started = true; + $this->closed = false; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + if (!$this->started) { + return ''; // returning empty is consistent with session_id() behaviour + } + + return session_id(); + } + + /** + * Regenerates the session. + * + * This method will regenerate the session ID and optionally + * destroy the old ID. Session regeneration should be done + * periodically and for example, should be done when converting + * an anonymous session to a logged in user session. + * + * @param boolean $destroy + * + * @return boolean Returns true on success or false on failure. + */ + public function regenerate($destroy = false) + { + return session_regenerate_id($destroy); + } + + /** + * {@inheritdoc} + */ + public function save() + { + session_write_close(); + $this->closed = true; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $_SESSION = array(); + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * Register a SessionBagInterface for use. + * + * @param SessionBagInterface $bag + */ + public function registerBag(SessionBagInterface $bag) + { + $this->bags[$bag->getName()] = $bag; + } + + /** + * Gets a bag by name. + * + * @param string $name + * + * @return SessionBagInterface + * + * @throws \InvalidArgumentException + */ + public function getBag($name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); + } + + if ($this->options['auto_start'] && !$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + /** + * Sets session.* ini variables. + * + * For convenience we omit 'session.' from the beginning of the keys. + * Explicitly ignores other ini keys. + * + * session_get_cookie_params() overrides values. + * + * @param array $options + * + * @see http://www.php.net/manual/en/session.configuration.php + */ + protected function setOptions(array $options) + { + $cookieDefaults = session_get_cookie_params(); + $this->options = array_merge(array( + 'cookie_lifetime' => $cookieDefaults['lifetime'], + 'cookie_path' => $cookieDefaults['path'], + 'cookie_domain' => $cookieDefaults['domain'], + 'cookie_secure' => $cookieDefaults['secure'], + 'cookie_httponly' => isset($cookieDefaults['httponly']) ? $cookieDefaults['httponly'] : false, + ), $options); + + // Unless session.cache_limiter has been set explicitly, disable it + // because this is managed by HeaderBag directly (if used). + if (!isset($this->options['cache_limiter'])) { + $this->options['cache_limiter'] = 0; + } + + if (!isset($this->options['auto_start'])) { + $this->options['auto_start'] = 0; + } + + if (!isset($this->options['use_cookies'])) { + $this->options['use_cookies'] = 1; + } + + foreach ($this->options as $key => $value) { + if (in_array($key, array( + 'auto_start', 'cookie_domain', 'cookie_httponly', + 'cookie_lifetime', 'cookie_path', 'cookie_secure', + 'entropy_file', 'entropy_length', 'gc_divisor', + 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character', + 'hash_function', 'name', 'referer_check', + 'save_path', 'serialize_handler', 'use_cookies', + 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', + 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', + 'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags'))) { + ini_set('session.'.$key, $value); + } + } + } + + /** + * Registers this storage device for PHP session handling. + * + * PHP requires session save handlers to be set, either it's own, or custom ones. + * There are some defaults set automatically when PHP starts, but these can be overriden + * using this command if you need anything other than PHP's default handling. + * + * When the session starts, PHP will call the sessionRead() handler which should return an array + * of any session attributes. PHP will then populate these into $_SESSION. + * + * When PHP shuts down, the sessionWrite() handler is called and will pass the $_SESSION contents + * to be stored. + * + * When a session is specifically destroyed, PHP will call the sessionDestroy() handler with the + * session ID. This happens when the session is regenerated for example and th handler + * MUST delete the session by ID from the persistent storage immediately. + * + * PHP will call sessionGc() from time to time to expire any session records according to the + * set max lifetime of a session. This routine should delete all records from persistent + * storage which were last accessed longer than the $lifetime. + * + * PHP sessionOpen() and sessionClose() are pretty much redundant and can just return true. + * + * NOTE: + * + * To use PHP native save handlers, override this method using ini_set with + * session.save_handlers and session.save_path e.g. + * + * ini_set('session.save_handlers', 'files'); + * ini_set('session.save_path', /tmp'); + * + * @see http://php.net/manual/en/function.session-set-save-handler.php + * @see SessionSaveHandlerInterface + */ + protected function registerSaveHandlers() + { + // note this can be reset to PHP's control using ini_set('session.save_handler', 'files'); + // so long as ini_set() is called before the session is started. + if ($this instanceof SessionSaveHandlerInterface) { + session_set_save_handler( + array($this, 'openSession'), + array($this, 'closeSession'), + array($this, 'readSession'), + array($this, 'writeSession'), + array($this, 'destroySession'), + array($this, 'gcSession') + ); + } + } + + /** + * Registers PHP shutdown function. + * + * This method is required to avoid strange issues when using PHP objects as + * session save handlers. + */ + protected function registerShutdownFunction() + { + register_shutdown_function('session_write_close'); + } + + /** + * Load the session with attributes. + * + * After starting the session, PHP retrieves the session from whatever handlers + * are set to (either PHP's internal, custom set with session_set_save_handler()). + * PHP takes the return value from the sessionRead() handler, unserializes it + * and populates $_SESSION with the result automatically. + */ + protected function loadSession(array &$session = null) + { + if (null === $session) { + $session = &$_SESSION; + } + + foreach ($this->bags as $bag) { + $key = $bag->getStorageKey(); + $session[$key] = isset($session[$key]) ? $session[$key] : array(); + $bag->initialize($session[$key]); + } + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MemcacheSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MemcacheSessionStorage.php new file mode 100644 index 0000000000..f5d5910d86 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MemcacheSessionStorage.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * 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. + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct(\Memcache $memcache, array $memcacheOptions = array(), array $options = array()) + { + $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($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 openSession($savePath, $sessionName) + { + foreach ($this->memcacheOptions['serverpool'] as $server) { + $this->addServer($server); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function closeSession() + { + return $this->memcache->close(); + } + + /** + * {@inheritdoc} + */ + public function readSession($sessionId) + { + return $this->memcache->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritdoc} + */ + public function writeSession($sessionId, $data) + { + return $this->memcache->set($this->prefix.$sessionId, $data, $this->memcacheOptions['expiretime']); + } + + /** + * {@inheritdoc} + */ + public function destroySession($sessionId) + { + return $this->memcache->delete($this->prefix.$sessionId); + } + + /** + * {@inheritdoc} + */ + public function gcSession($lifetime) + { + // not required here because memcache will auto expire the records anyhow. + return true; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MemcachedSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MemcachedSessionStorage.php new file mode 100644 index 0000000000..f7041811a3 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MemcachedSessionStorage.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\Session\Storage; + +/** + * 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. + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct(\Memcached $memcache, array $memcachedOptions = array(), array $options = array()) + { + $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($options); + } + + /** + * {@inheritdoc} + */ + public function openSession($savePath, $sessionName) + { + foreach ($this->memcachedOptions['serverpool'] as $server) { + $this->addServer($server); + } + + return true; + } + + /** + * Close session. + * + * @return boolean + */ + public function closeSession() + { + return $this->memcached->close(); + } + + /** + * {@inheritdoc} + */ + public function readSession($sessionId) + { + return $this->memcached->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritdoc} + */ + public function writeSession($sessionId, $data) + { + return $this->memcached->set($this->prefix.$sessionId, $data, false, $this->memcachedOptions['expiretime']); + } + + /** + * {@inheritdoc} + */ + public function destroySession($sessionId) + { + return $this->memcached->delete($this->prefix.$sessionId); + } + + /** + * {@inheritdoc} + */ + public function gcSession($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/Session/Storage/MockArraySessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php new file mode 100644 index 0000000000..2fd1a94c26 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * MockArraySessionStorage mocks the session for unit tests. + * + * 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 MockFileSessionStorage instead. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Drak + */ +class MockArraySessionStorage extends AbstractSessionStorage +{ + /** + * @var string + */ + protected $sessionId; + + /** + * @var array + */ + private $sessionData = array(); + + public function setSessionData(array $array) + { + $this->sessionData = $array; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started && !$this->closed) { + return true; + } + + $this->started = true; + $this->loadSession($this->sessionData); + + $this->sessionId = $this->generateSessionId(); + session_id($this->sessionId); + + return true; + } + + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false) + { + if ($this->options['auto_start'] && !$this->started) { + $this->start(); + } + + $this->sessionId = $this->generateSessionId(); + session_id($this->sessionId); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + if (!$this->started) { + return ''; + } + + return $this->sessionId; + } + + /** + * {@inheritdoc} + */ + public function save() + { + // nothing to do since we don't persist the session data + $this->closed = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $this->sessionData = array(); + + // reconnect the bags to the session + $this->loadSession($this->sessionData); + } + + /** + * Generates a session ID. + * + * @return string + */ + protected function generateSessionId() + { + return sha1(uniqid(mt_rand(), true)); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php new file mode 100644 index 0000000000..094c4d6005 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * MockFileSessionStorage 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 MockFileSessionStorage extends MockArraySessionStorage +{ + /** + * @var string + */ + private $savePath; + + /** + * Constructor. + * + * @param string $savePath Path of directory to save session files. + * @param array $options Session options. + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct($savePath = null, array $options = array()) + { + if (null === $savePath) { + $savePath = sys_get_temp_dir(); + } + + if (!is_dir($savePath)) { + mkdir($savePath, 0777, true); + } + + $this->savePath = $savePath; + + parent::__construct($options); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (!session_id()) { + session_id($this->generateSessionId()); + } + + $this->sessionId = session_id(); + + $this->read(); + + $this->started = true; + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false) + { + if ($destroy) { + $this->destroy(); + } + + session_id($this->generateSessionId()); + $this->sessionId = session_id(); + + $this->save(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + if (!$this->started) { + return ''; + } + + return $this->sessionId; + } + + /** + * {@inheritdoc} + */ + public function save() + { + file_put_contents($this->getFilePath(), serialize($this->sessionData)); + } + + private function destroy() + { + if (is_file($this->getFilePath())) { + unlink($this->getFilePath()); + } + } + + /** + * Calculate path to file. + * + * @return string File path + */ + public function getFilePath() + { + return $this->savePath.'/'.$this->sessionId.'.sess'; + } + + private function read() + { + $filePath = $this->getFilePath(); + $this->sessionData = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : array(); + + $this->loadSession($this->sessionData); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeFileSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeFileSessionStorage.php new file mode 100644 index 0000000000..09350e8d21 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeFileSessionStorage.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\Session\Storage; + +/** + * 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. + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct($savePath = null, array $options = array()) + { + if (null === $savePath) { + $savePath = sys_get_temp_dir(); + } + + if (!is_dir($savePath)) { + mkdir($savePath, 0777, true); + } + + $this->savePath = $savePath; + + parent::__construct($options); + } + + /** + * {@inheritdoc} + */ + protected function registerSaveHandlers() + { + ini_set('session.save_handler', 'files'); + ini_set('session.save_path', $this->savePath); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeMemcacheSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeMemcacheSessionStorage.php new file mode 100644 index 0000000000..5572c47812 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeMemcacheSessionStorage.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * 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. + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct($savePath = 'tcp://127.0.0.1:11211?persistent=0', array $options = array()) + { + if (!extension_loaded('memcache')) { + throw new \RuntimeException('PHP does not have "memcache" session module registered'); + } + + $this->savePath = $savePath; + parent::__construct($options); + } + + /** + * {@inheritdoc} + */ + protected function registerSaveHandlers() + { + ini_set('session.save_handler', 'memcache'); + ini_set('session.save_path', $this->savePath); + } + + /** + * {@inheritdoc} + * + * Sets any values memcached ini values. + * + * @see http://www.php.net/manual/en/memcache.ini.php + */ + protected function setOptions(array $options) + { + foreach ($options as $key => $value) { + if (in_array($key, array( + 'memcache.allow_failover', 'memcache.max_failover_attempts', + 'memcache.chunk_size', 'memcache.default_port', 'memcache.hash_strategy', + 'memcache.hash_function', 'memcache.protocol', 'memcache.redundancy', + 'memcache.session_redundancy', 'memcache.compress_threshold', + 'memcache.lock_timeout'))) { + ini_set($key, $value); + } + } + + parent::setOptions($options); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeMemcachedSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeMemcachedSessionStorage.php new file mode 100644 index 0000000000..50d8d060fd --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeMemcachedSessionStorage.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * 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. + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct($savePath = '127.0.0.1:11211', array $options = array()) + { + if (!extension_loaded('memcached')) { + throw new \RuntimeException('PHP does not have "memcached" session module registered'); + } + + $this->savePath = $savePath; + parent::__construct($options); + } + + /** + * {@inheritdoc} + */ + protected function registerSaveHandlers() + { + ini_set('session.save_handler', 'memcached'); + ini_set('session.save_path', $this->savePath); + } + + /** + * {@inheritdoc} + * + * Sets any values memcached ini values. + * + * @see https://github.com/php-memcached-dev/php-memcached/blob/master/memcached.ini + */ + protected function setOptions(array $options) + { + foreach ($options as $key => $value) { + if (in_array($key, array( + 'memcached.sess_locking', 'memcached.sess_lock_wait', + 'memcached.sess_prefix', 'memcached.compression_type', + 'memcached.compression_factor', 'memcached.compression_threshold', + 'memcached.serializer'))) { + ini_set($key, $value); + } + } + + parent::setOptions($options); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSqliteSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSqliteSessionStorage.php new file mode 100644 index 0000000000..1dac36ac2f --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSqliteSessionStorage.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * 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. + * + * @see AbstractSessionStorage::__construct() + */ + public function __construct($dbPath, array $options = array()) + { + if (!extension_loaded('sqlite')) { + throw new \RuntimeException('PHP does not have "sqlite" session module registered'); + } + + $this->dbPath = $dbPath; + parent::__construct($options); + } + + /** + * {@inheritdoc} + */ + protected function registerSaveHandlers() + { + ini_set('session.save_handler', 'sqlite'); + ini_set('session.save_path', $this->dbPath); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NullSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NullSessionStorage.php new file mode 100644 index 0000000000..b81fa8ae3a --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/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\Session\Storage; + +/** + * 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 openSession($savePath, $sessionName) + { + return true; + } + + /** + * Close session. + * + * @return boolean + */ + public function closeSession() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function readSession($sessionId) + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function writeSession($sessionId, $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function destroySession($sessionId) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function gcSession($lifetime) + { + return true; + } +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/PdoSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/PdoSessionStorage.php similarity index 68% rename from src/Symfony/Component/HttpFoundation/SessionStorage/PdoSessionStorage.php rename to src/Symfony/Component/HttpFoundation/Session/Storage/PdoSessionStorage.php index 24898b7c20..2015396b2f 100644 --- a/src/Symfony/Component/HttpFoundation/SessionStorage/PdoSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/PdoSessionStorage.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\HttpFoundation\SessionStorage; +namespace Symfony\Component\HttpFoundation\Session\Storage; /** * PdoSessionStorage. @@ -17,18 +17,19 @@ namespace Symfony\Component\HttpFoundation\SessionStorage; * @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,21 +37,22 @@ 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 * * @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()) { 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', @@ -61,61 +63,27 @@ class PdoSessionStorage extends NativeSessionStorage } /** - * Starts the session. + * {@inheritdoc} */ - 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(); - } - - /** - * Opens a session. - * - * @param string $path (ignored) - * @param string $name (ignored) - * - * @return Boolean true, if the session was opened, otherwise an exception is thrown - */ - public function sessionOpen($path = null, $name = null) + public function openSession($path = null, $name = null) { return true; } /** - * Closes a session. - * - * @return Boolean true, if the session was closed, otherwise false + * {@inheritdoc} */ - public function sessionClose() + public function closeSession() { - // 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 */ - public function sessionDestroy($id) + public function destroySession($id) { // get table/column $dbTable = $this->dbOptions['db_table']; @@ -125,7 +93,7 @@ class PdoSessionStorage extends NativeSessionStorage $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 +104,11 @@ class PdoSessionStorage extends NativeSessionStorage } /** - * 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 gcSession($lifetime) { // get table/column $dbTable = $this->dbOptions['db_table']; @@ -154,7 +118,7 @@ class PdoSessionStorage extends NativeSessionStorage $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,15 +129,11 @@ class PdoSessionStorage extends NativeSessionStorage } /** - * 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 */ - public function sessionRead($id) + public function readSession($id) { // get table/columns $dbTable = $this->dbOptions['db_table']; @@ -183,7 +143,7 @@ class PdoSessionStorage extends NativeSessionStorage 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(); @@ -200,21 +160,16 @@ class PdoSessionStorage extends NativeSessionStorage return ''; } catch (\PDOException $e) { - throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); + throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e); } } /** - * 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 */ - public function sessionWrite($id, $data) + public function writeSession($id, $data) { // get table/column $dbTable = $this->dbOptions['db_table']; @@ -222,7 +177,7 @@ class PdoSessionStorage extends NativeSessionStorage $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 +185,7 @@ class PdoSessionStorage extends NativeSessionStorage 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); @@ -242,7 +197,7 @@ class PdoSessionStorage extends NativeSessionStorage $this->createNewSession($id, $data); } } catch (\PDOException $e) { - throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); + throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e); } return true; @@ -268,7 +223,7 @@ class PdoSessionStorage extends NativeSessionStorage //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/Session/Storage/SessionSaveHandlerInterface.php b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionSaveHandlerInterface.php new file mode 100644 index 0000000000..ec4530a150 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionSaveHandlerInterface.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * 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 readSession() handler + * which should return a string extactly as stored (which will have + * been encoded by PHP using a special session serializer session_decode() + * which is different to the serialize() function. PHP will then populate + * these into $_SESSION. + * + * When PHP shuts down, the sessionWrite() handler is called and will pass + * the $_SESSION contents already serialized (using session_encode()) to + * be stored. + * + * When a session is specifically destroyed, PHP will call the + * sessionSession() 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 sessionSession() 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 openSession() and closeSession() 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 openSession($savePath, $sessionName); + + /** + * Close session. + * + * This method is for internal use by PHP and must not be called manually. + * + * @return boolean + */ + function closeSession(); + + /** + * Read session. + * + * This method is for internal use by PHP and must not be called manually. + * + * This method is called by PHP itself when the session is started. + * This method should retrieve the session data from storage by the + * ID provided by PHP. Return the string directly as is from storage. + * If the record was not found you must return an empty string. + * + * The returned data will be unserialized automatically by PHP using a + * special unserializer method session_decode() and the result will be used + * to populate the $_SESSION superglobal. This is done automatically and + * is not configurable. + * + * @param string $sessionId Session ID. + * + * @throws \RuntimeException On fatal error but not "record not found". + * + * @return string String as stored in persistent storage or empty string in all other cases. + */ + function readSession($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 writeSession($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 destroySession($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 gcSession($lifetime); +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php new file mode 100644 index 0000000000..7eb720b7c6 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * StorageInterface. + * + * @author Fabien Potencier + * @author Drak + * + * @api + */ +interface SessionStorageInterface +{ + /** + * Starts the session. + * + * @throws \RuntimeException If something goes wrong starting the session. + * + * @return boolean True if started. + * + * @api + */ + function start(); + + /** + * Returns the session ID + * + * @return mixed The session ID or false if the session has not started. + * + * @api + */ + function getId(); + + /** + * Regenerates id that represents this storage. + * + * This method must invoke session_regenerate_id($destroy) unless + * this interface is used for a storage object designed for unit + * or functional testing where a real PHP session would interfere + * with testing. + * + * @param Boolean $destroy Destroy session when regenerating? + * + * @return Boolean True if session regenerated, false if error + * + * @throws \RuntimeException If an error occurs while regenerating this storage + * + * @api + */ + function regenerate($destroy = false); + + /** + * Force the session to be saved and closed. + * + * This method must invoke session_write_close() unless this interface is + * used for a storage object design for unit or functional testing where + * a real PHP session would interfere with testing, in which case it + * it should actually persist the session data if required. + */ + function save(); + + /** + * Clear all session data in memory. + */ + function clear(); + + /** + * Gets a SessionBagInterface by name. + * + * @return SessionBagInterface + */ + function getBag($name); + + /** + * Registers a SessionBagInterface for use. + * + * @param SessionBagInterface $bag + */ + function registerBag(SessionBagInterface $bag); +} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/ArraySessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/ArraySessionStorage.php deleted file mode 100644 index 5a6558a2b3..0000000000 --- a/src/Symfony/Component/HttpFoundation/SessionStorage/ArraySessionStorage.php +++ /dev/null @@ -1,81 +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; - -/** - * ArraySessionStorage mocks the session for unit tests. - * - * When doing functional testing, you should use FilesystemSessionStorage instead. - * - * @author Fabien Potencier - * @author Bulat Shakirzyanov - */ - -class ArraySessionStorage implements SessionStorageInterface -{ - /** - * Storage data. - * - * @var array - */ - private $data = array(); - - /** - * {@inheritdoc} - */ - public function read($key, $default = null) - { - return array_key_exists($key, $this->data) ? $this->data[$key] : $default; - } - - /** - * {@inheritdoc} - */ - public function regenerate($destroy = false) - { - if ($destroy) { - $this->data = array(); - } - - return true; - } - - /** - * {@inheritdoc} - */ - public function remove($key) - { - unset($this->data[$key]); - } - - /** - * {@inheritdoc} - */ - public function start() - { - } - - /** - * {@inheritdoc} - */ - public function getId() - { - } - - /** - * {@inheritdoc} - */ - public function write($key, $data) - { - $this->data[$key] = $data; - } -} diff --git a/src/Symfony/Component/HttpFoundation/SessionStorage/FilesystemSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/FilesystemSessionStorage.php deleted file mode 100644 index ceb5913fe9..0000000000 --- a/src/Symfony/Component/HttpFoundation/SessionStorage/FilesystemSessionStorage.php +++ /dev/null @@ -1,192 +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 - * @param string $default The default value - * - * @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/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/SessionStorage/NativeSessionStorage.php deleted file mode 100644 index b759f7411a..0000000000 --- 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/SessionStorageInterface.php b/src/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.php deleted file mode 100644 index b61a2557b2..0000000000 --- a/src/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.php +++ /dev/null @@ -1,97 +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; - -/** - * SessionStorageInterface. - * - * @author Fabien Potencier - * - * @api - */ -interface SessionStorageInterface -{ - /** - * Starts the session. - * - * @api - */ - function start(); - - /** - * Returns the session ID - * - * @return mixed The session ID - * - * @throws \RuntimeException If the session was not started yet - * - * @api - */ - function getId(); - - /** - * Reads data from this storage. - * - * The preferred format for a key is directory style so naming conflicts can be avoided. - * - * @param string $key A unique key identifying your data - * - * @return mixed Data associated with the key - * - * @throws \RuntimeException If an error occurs while reading data from this storage - * - * @api - */ - function read($key); - - /** - * Removes data from this storage. - * - * The preferred format for a key is directory style so naming conflicts can be avoided. - * - * @param string $key A unique key identifying your data - * - * @return mixed Data associated with the key - * - * @throws \RuntimeException If an error occurs while removing data from this storage - * - * @api - */ - function remove($key); - - /** - * Writes data to this storage. - * - * The preferred format for a key is directory style so naming conflicts can be avoided. - * - * @param string $key A unique key identifying your data - * @param mixed $data Data associated with your key - * - * @throws \RuntimeException If an error occurs while writing to this storage - * - * @api - */ - function write($key, $data); - - /** - * Regenerates id that represents this storage. - * - * @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 - */ - function regenerate($destroy = false); -} diff --git a/tests/Symfony/Tests/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProviderTest.php b/tests/Symfony/Tests/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProviderTest.php index 958a328295..3358ffcb21 100644 --- a/tests/Symfony/Tests/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProviderTest.php +++ b/tests/Symfony/Tests/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProviderTest.php @@ -21,7 +21,7 @@ class SessionCsrfProviderTest extends \PHPUnit_Framework_TestCase protected function setUp() { $this->session = $this->getMock( - 'Symfony\Component\HttpFoundation\Session', + 'Symfony\Component\HttpFoundation\Session\Session', array(), array(), '', diff --git a/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php b/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php index 4b208bb3ec..c147070fdf 100644 --- a/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php +++ b/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php @@ -12,10 +12,8 @@ namespace Symfony\Tests\Component\HttpFoundation; -use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage; - -use Symfony\Component\HttpFoundation\Session; - +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Request; class RequestTest extends \PHPUnit_Framework_TestCase @@ -848,7 +846,7 @@ class RequestTest extends \PHPUnit_Framework_TestCase $request = new Request; $this->assertFalse($request->hasSession()); - $request->setSession(new Session(new ArraySessionStorage())); + $request->setSession(new Session(new MockArraySessionStorage())); $this->assertTrue($request->hasSession()); } @@ -859,7 +857,7 @@ class RequestTest extends \PHPUnit_Framework_TestCase $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 MockArraySessionStorage())); $this->assertTrue($request->hasPreviousSession()); } diff --git a/tests/Symfony/Tests/Component/HttpFoundation/Session/Attribute/AttributeBagTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/Attribute/AttributeBagTest.php new file mode 100644 index 0000000000..a5c921254c --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/Attribute/AttributeBagTest.php @@ -0,0 +1,155 @@ + + */ +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, $bag->all()); + $array = array('should' => 'change'); + $bag->initialize($array); + $this->assertEquals($array, $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/Session/Attribute/NamespacedAttributeBagTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/Attribute/NamespacedAttributeBagTest.php new file mode 100644 index 0000000000..94e5d80b07 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/Attribute/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/Session/Flash/AutoExpireFlashBagTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/Flash/AutoExpireFlashBagTest.php new file mode 100644 index 0000000000..3b12c0eaea --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/Flash/AutoExpireFlashBagTest.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\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag as FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; + +/** + * AutoExpireFlashBagTest + * + * @author Drak + */ +class AutoExpireFlashBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface + */ + private $bag; + + /** + * @var array + */ + protected $array = array(); + + public function setUp() + { + parent::setUp(); + $this->bag = new FlashBag(); + $this->array = array('new' => array('notice' => 'A previous flash message')); + $this->bag->initialize($this->array); + } + + public function tearDown() + { + $this->bag = null; + parent::tearDown(); + } + + public function testInitialize() + { + $bag = new FlashBag(); + $array = array('new' => array('notice' => 'A previous flash message')); + $bag->initialize($array); + $this->assertEquals('A previous flash message', $bag->peek('notice')); + $array = array('new' => array( + 'notice' => 'Something else', + 'error' => 'a', + )); + $bag->initialize($array); + $this->assertEquals('Something else', $bag->peek('notice')); + $this->assertEquals('a', $bag->peek('error')); + } + + public function testPeek() + { + $this->assertNull($this->bag->peek('non_existing')); + $this->assertEquals('default', $this->bag->peek('non_existing', 'default')); + $this->assertEquals('A previous flash message', $this->bag->peek('notice')); + $this->assertEquals('A previous flash message', $this->bag->peek('notice')); + } + + public function testSet() + { + $this->bag->set('notice', 'Foo'); + $this->assertNotEquals('Foo', $this->bag->peek('notice')); + } + + public function testHas() + { + $this->assertFalse($this->bag->has('nothing')); + $this->assertTrue($this->bag->has('notice')); + } + + public function testKeys() + { + $this->assertEquals(array('notice'), $this->bag->keys()); + } + + public function testPeekAll() + { + $array = array( + 'new' => array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), + ); + + $this->bag->initialize($array); + $this->assertEquals(array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), $this->bag->peekAll() + ); + + $this->assertEquals(array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), $this->bag->peekAll() + ); + } + + public function testGet() + { + $this->assertNull($this->bag->get('non_existing')); + $this->assertEquals('default', $this->bag->get('non_existing', 'default')); + $this->assertEquals('A previous flash message', $this->bag->get('notice')); + $this->assertNull($this->bag->get('notice')); + } + + public function testAll() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('error', 'Bar'); + $this->assertEquals(array( + 'notice' => 'A previous flash message', + ), $this->bag->all() + ); + + $this->assertEquals(array(), $this->bag->all()); + } +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/Session/Flash/FlashBagTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/Flash/FlashBagTest.php new file mode 100644 index 0000000000..3616ec9156 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/Flash/FlashBagTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpFoundation\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; + +/** + * FlashBagTest + * + * @author Drak + */ +class FlashBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\SessionFlash\FlashBagInterface + */ + private $bag; + + /** + * @var array + */ + protected $array = array(); + + public function setUp() + { + parent::setUp(); + $this->bag = new FlashBag(); + $this->array = array('notice' => '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, $bag->peekAll()); + $array = array('should' => array('change')); + $bag->initialize($array); + $this->assertEquals($array, $bag->peekAll()); + } + + public function testPeek() + { + $this->assertNull($this->bag->peek('non_existing')); + $this->assertEquals('default', $this->bag->peek('not_existing', 'default')); + $this->assertEquals('A previous flash message', $this->bag->peek('notice')); + $this->assertEquals('A previous flash message', $this->bag->peek('notice')); + } + + public function testGet() + { + $this->assertNull($this->bag->get('non_existing')); + $this->assertEquals('default', $this->bag->get('not_existing', 'default')); + $this->assertEquals('A previous flash message', $this->bag->get('notice')); + $this->assertNull($this->bag->get('notice')); + } + + public function testAll() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('error', 'Bar'); + $this->assertEquals(array( + 'notice' => 'Foo', + 'error' => 'Bar'), $this->bag->all() + ); + + $this->assertEquals(array(), $this->bag->all()); + } + + public function testSet() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('notice', 'Bar'); + $this->assertEquals('Bar', $this->bag->peek('notice')); + } + + public function testHas() + { + $this->assertFalse($this->bag->has('nothing')); + $this->assertTrue($this->bag->has('notice')); + } + + public function testKeys() + { + $this->assertEquals(array('notice'), $this->bag->keys()); + } + + public function testPeekAll() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('error', 'Bar'); + $this->assertEquals(array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), $this->bag->peekAll() + ); + $this->assertTrue($this->bag->has('notice')); + $this->assertTrue($this->bag->has('error')); + $this->assertEquals(array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), $this->bag->peekAll() + ); + } +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/Session/SessionTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/SessionTest.php new file mode 100644 index 0000000000..3f57670581 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/SessionTest.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; + +/** + * SessionTest + * + * @author Fabien Potencier + * @author Robert Schönthal + * @author Drak + */ +class SessionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface + */ + protected $storage; + + /** + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface + */ + protected $session; + + public function setUp() + { + $this->storage = new MockArraySessionStorage(); + $this->session = new Session($this->storage, new AttributeBag(), new FlashBag()); + } + + protected function tearDown() + { + $this->storage = null; + $this->session = null; + } + + public function testStart() + { + $this->assertEquals('', $this->session->getId()); + $this->assertTrue($this->session->start()); + $this->assertNotEquals('', $this->session->getId()); + } + + public function testGet() + { + // tests defaults + $this->assertNull($this->session->get('foo')); + $this->assertEquals(1, $this->session->get('foo', 1)); + } + + /** + * @dataProvider setProvider + */ + public function testSet($key, $value) + { + $this->session->set($key, $value); + $this->assertEquals($value, $this->session->get($key)); + } + + 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()); + } + + /** + * @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->assertEquals(array(), $this->session->all()); + } + + public function setProvider() + { + return array( + array('foo', 'bar', array('foo' => 'bar')), + array('foo.bar', 'too much beer', array('foo.bar' => 'too much beer')), + array('great', 'symfony2 is great', array('great' => 'symfony2 is great')), + ); + } + + /** + * @dataProvider setProvider + */ + public function testRemove($key, $value) + { + $this->session->set('hi.world', 'have a nice day'); + $this->session->set($key, $value); + $this->session->remove($key); + $this->assertEquals(array('hi.world' => 'have a nice day'), $this->session->all()); + } + + public function testInvalidate() + { + $this->session->set('invalidate', 123); + $this->session->invalidate(); + $this->assertEquals(array(), $this->session->all()); + } + + public function testMigrate() + { + $this->session->set('migrate', 321); + $this->session->migrate(); + $this->assertEquals(321, $this->session->get('migrate')); + } + + public function testMigrateDestroy() + { + $this->session->set('migrate', 333); + $this->session->migrate(true); + $this->assertEquals(333, $this->session->get('migrate')); + } + + public function testSerialize() + { + $compare = serialize($this->storage); + + $this->assertSame($compare, $this->session->serialize()); + + $this->session->unserialize($compare); + + $_storage = new \ReflectionProperty(get_class($this->session), 'storage'); + $_storage->setAccessible(true); + + $this->assertEquals($_storage->getValue($this->session), $this->storage, 'storage match'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testUnserializeException() + { + $serialized = serialize(new \ArrayObject()); + $this->session->unserialize($serialized); + } + + public function testGetId() + { + $this->assertEquals('', $this->session->getId()); + $this->session->start(); + $this->assertNotEquals('', $this->session->getId()); + } +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/AbstractSessionStorageTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/AbstractSessionStorageTest.php new file mode 100644 index 0000000000..05bc5fefb3 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/AbstractSessionStorageTest.php @@ -0,0 +1,127 @@ + + * + * These tests require separate processes. + * + * @runTestsInSeparateProcesses + */ +class AbstractSessionStorageTest extends \PHPUnit_Framework_TestCase +{ + /** + * @return AbstractSessionStorage + */ + protected function getStorage() + { + $storage = new CustomHandlerSessionStorage(); + $storage->registerBag(new AttributeBag); + + return $storage; + } + + public function testBag() + { + $storage = $this->getStorage(); + $bag = new FlashBag(); + $storage->registerBag($bag); + $this->assertSame($bag, $storage->getBag($bag->getName())); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRegisterBagException() + { + $storage = $this->getStorage(); + $storage->getBag('non_existing'); + } + + public function testGetId() + { + $storage = $this->getStorage(); + $this->assertEquals('', $storage->getId()); + $storage->start(); + $this->assertNotEquals('', $storage->getId()); + } + + public function testRegenerate() + { + $storage = $this->getStorage(); + $storage->start(); + $id = $storage->getId(); + $storage->getBag('attributes')->set('lucky', 7); + $storage->regenerate(); + $this->assertNotEquals($id, $storage->getId()); + $this->assertEquals(7, $storage->getBag('attributes')->get('lucky')); + + } + + public function testRegenerateDestroy() + { + $storage = $this->getStorage(); + $storage->start(); + $id = $storage->getId(); + $storage->getBag('attributes')->set('legs', 11); + $storage->regenerate(true); + $this->assertNotEquals($id, $storage->getId()); + $this->assertEquals(11, $storage->getBag('attributes')->get('legs')); + } + + public function testCustomSaveHandlers() + { + $storage = new CustomHandlerSessionStorage(); + $this->assertEquals('user', ini_get('session.save_handler')); + } + + public function testNativeSaveHandlers() + { + $storage = new ConcreteSessionStorage(); + $this->assertNotEquals('user', ini_get('session.save_handler')); + } +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/MockArraySessionStorageTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/MockArraySessionStorageTest.php new file mode 100644 index 0000000000..4611457940 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/MockArraySessionStorageTest.php @@ -0,0 +1,90 @@ + + */ +class MockArraySessionStorageTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var MockArraySessionStorage + */ + private $storage; + + /** + * @var array + */ + private $attributes; + + /** + * @var array + */ + private $flashes; + + private $data; + + protected function setUp() + { + $this->attributes = new AttributeBag(); + $this->flashes = new FlashBag(); + + $this->data = array( + $this->attributes->getStorageKey() => array('foo' => 'bar'), + $this->flashes->getStorageKey() => array('notice' => 'hello'), + ); + + $this->storage = new MockArraySessionStorage(); + $this->storage->registerBag($this->flashes); + $this->storage->registerBag($this->attributes); + $this->storage->setSessionData($this->data); + } + + protected function tearDown() + { + $this->data = null; + $this->flashes = null; + $this->attributes = 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 testRegenerate() + { + $this->storage->start(); + $id = $this->storage->getId(); + $this->storage->regenerate(); + $this->assertNotEquals($id, $this->storage->getId()); + $this->assertEquals(array('foo' => 'bar'), $this->storage->getBag('attributes')->all()); + $this->assertEquals(array('notice' => 'hello'), $this->storage->getBag('flashes')->peekAll()); + + $id = $this->storage->getId(); + $this->storage->regenerate(true); + $this->assertNotEquals($id, $this->storage->getId()); + $this->assertEquals(array('foo' => 'bar'), $this->storage->getBag('attributes')->all()); + $this->assertEquals(array('notice' => 'hello'), $this->storage->getBag('flashes')->peekAll()); + } + + public function testGetId() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $this->assertNotEquals('', $this->storage->getId()); + } +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/MockFileSessionStorageTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/MockFileSessionStorageTest.php new file mode 100644 index 0000000000..770bcda8f0 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/MockFileSessionStorageTest.php @@ -0,0 +1,105 @@ + + */ +class MockFileSessionStorageTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var string + */ + private $sessionDir; + + /** + * @var FileMockSessionStorage + */ + protected $storage; + + protected function setUp() + { + $this->sessionDir = sys_get_temp_dir().'/sf2test'; + $this->storage = $this->getStorage(); + } + + protected function tearDown() + { + $this->sessionDir = null; + $this->storage = null; + array_map('unlink', glob($this->sessionDir.'/*.session')); + if (is_dir($this->sessionDir)) { + rmdir($this->sessionDir); + } + } + + public function testStart() + { + $this->assertEquals('', $this->storage->getId()); + $this->assertTrue($this->storage->start()); + $id = $this->storage->getId(); + $this->assertNotEquals('', $this->storage->getId()); + $this->assertTrue($this->storage->start()); + $this->assertEquals($id, $this->storage->getId()); + } + + public function testRegenerate() + { + $this->storage->start(); + $this->storage->getBag('attributes')->set('regenerate', 1234); + $this->storage->regenerate(); + $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate')); + $this->storage->regenerate(true); + $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate')); + } + + public function testGetId() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $this->assertNotEquals('', $this->storage->getId()); + } + + public function testSave() + { + $this->storage->start(); + $this->assertNotEquals('108', $this->storage->getBag('attributes')->get('new')); + $this->assertFalse($this->storage->getBag('flashes')->has('newkey')); + $this->storage->getBag('attributes')->set('new', '108'); + $this->storage->getBag('flashes')->set('newkey', 'test'); + $this->storage->save(); + + $storage = $this->getStorage(); + $storage->start(); + $this->assertEquals('108', $storage->getBag('attributes')->get('new')); + $this->assertTrue($storage->getBag('flashes')->has('newkey')); + $this->assertEquals('test', $storage->getBag('flashes')->peek('newkey')); + } + + public function testMultipleInstances() + { + $storage1 = $this->getStorage(); + $storage1->start(); + $storage1->getBag('attributes')->set('foo', 'bar'); + $storage1->save(); + + $storage2 = $this->getStorage(); + $storage2->start(); + $this->assertEquals('bar', $storage2->getBag('attributes')->get('foo'), 'values persist between instances'); + } + + private function getStorage(array $options = array()) + { + $storage = new MockFileSessionStorage($this->sessionDir, $options); + $storage->registerBag(new FlashBag); + $storage->registerBag(new AttributeBag); + + return $storage; + } +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NativeFileSessionStorageTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NativeFileSessionStorageTest.php new file mode 100644 index 0000000000..df3ef95cbf --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NativeFileSessionStorageTest.php @@ -0,0 +1,23 @@ + + * + * @runTestsInSeparateProcesses + */ +class NativeFileSessionStorageTest extends \PHPUnit_Framework_TestCase +{ + public function testSaveHandlers() + { + $storage = new NativeFileSessionStorage(sys_get_temp_dir(), array('name' => 'TESTING')); + $this->assertEquals('files', ini_get('session.save_handler')); + $this->assertEquals(sys_get_temp_dir(), ini_get('session.save_path')); + $this->assertEquals('TESTING', ini_get('session.name')); + } +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NativeMemcacheSessionStorageTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NativeMemcacheSessionStorageTest.php new file mode 100644 index 0000000000..4274ed5bd1 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NativeMemcacheSessionStorageTest.php @@ -0,0 +1,27 @@ + + * + * @runTestsInSeparateProcesses + */ +class NativeMemcacheSessionStorageTest extends \PHPUnit_Framework_TestCase +{ + public function testSaveHandlers() + { + if (!extension_loaded('memcache')) { + $this->markTestSkipped('Skipped tests SQLite extension is not present'); + } + + $storage = new NativeMemcacheSessionStorage('tcp://127.0.0.1:11211?persistent=0', array('name' => 'TESTING')); + $this->assertEquals('memcache', ini_get('session.save_handler')); + $this->assertEquals('tcp://127.0.0.1:11211?persistent=0', ini_get('session.save_path')); + $this->assertEquals('TESTING', ini_get('session.name')); + } +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NativeMemcachedSessionStorageTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NativeMemcachedSessionStorageTest.php new file mode 100644 index 0000000000..c0a12aa2b4 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NativeMemcachedSessionStorageTest.php @@ -0,0 +1,32 @@ + + * + * @runTestsInSeparateProcesses + */ +class NativeMemcachedSessionStorageTest extends \PHPUnit_Framework_TestCase +{ + public function testSaveHandlers() + { + if (!extension_loaded('memcached')) { + $this->markTestSkipped('Skipped tests SQLite extension is not present'); + } + + // test takes too long if memcached server is not running + ini_set('memcached.sess_locking', '0'); + + $storage = new NativeMemcachedSessionStorage('127.0.0.1:11211', array('name' => 'TESTING')); + + $this->assertEquals('memcached', ini_get('session.save_handler')); + $this->assertEquals('127.0.0.1:11211', ini_get('session.save_path')); + $this->assertEquals('TESTING', ini_get('session.name')); + } +} + diff --git a/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NativeSqliteSessionStorageTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NativeSqliteSessionStorageTest.php new file mode 100644 index 0000000000..b1700326b1 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NativeSqliteSessionStorageTest.php @@ -0,0 +1,28 @@ + + * + * @runTestsInSeparateProcesses + */ +class NativeSqliteSessionStorageTest extends \PHPUnit_Framework_TestCase +{ + public function testSaveHandlers() + { + if (!extension_loaded('sqlite')) { + $this->markTestSkipped('Skipped tests SQLite extension is not present'); + } + + $storage = new NativeSqliteSessionStorage(sys_get_temp_dir().'/sqlite.db', array('name' => 'TESTING')); + $this->assertEquals('sqlite', ini_get('session.save_handler')); + $this->assertEquals(sys_get_temp_dir().'/sqlite.db', ini_get('session.save_path')); + $this->assertEquals('TESTING', ini_get('session.name')); + } +} + diff --git a/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NullSessionStorageTest.php b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NullSessionStorageTest.php new file mode 100644 index 0000000000..66599f68b3 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/Session/Storage/NullSessionStorageTest.php @@ -0,0 +1,42 @@ + + * + * @runTestsInSeparateProcesses + */ +class NullSessionStorageTest extends \PHPUnit_Framework_TestCase +{ + public function testSaveHandlers() + { + $storage = new NullSessionStorage(); + $this->assertEquals('user', ini_get('session.save_handler')); + } + + public function testSession() + { + session_id('nullsessionstorage'); + $storage = new NullSessionStorage(); + $session = new Session($storage); + $this->assertNull($session->get('something')); + $session->set('something', 'unique'); + $this->assertEquals('unique', $session->get('something')); + } + + public function testNothingIsPersisted() + { + session_id('nullsessionstorage'); + $storage = new NullSessionStorage(); + $session = new Session($storage); + $session->start(); + $this->assertEquals('nullsessionstorage', $session->getId()); + $this->assertNull($session->get('something')); + } +} + 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 060cb0e913..0000000000 --- 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 deleted file mode 100644 index 8318101e66..0000000000 --- a/tests/Symfony/Tests/Component/HttpFoundation/SessionTest.php +++ /dev/null @@ -1,232 +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; - -use Symfony\Component\HttpFoundation\Session; -use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage; - -/** - * SessionTest - * - * @author Fabien Potencier - * @author Robert Schönthal - */ -class SessionTest extends \PHPUnit_Framework_TestCase -{ - protected $storage; - protected $session; - - public function setUp() - { - $this->storage = new ArraySessionStorage(); - $this->session = $this->getSession(); - } - - protected function tearDown() - { - $this->storage = null; - $this->session = null; - } - - public function testFlash() - { - $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()); - } - - public function testFlashesAreFlushedWhenNeeded() - { - $this->session->setFlash('foo', 'bar'); - $this->session->save(); - - $this->session = $this->getSession(); - $this->assertTrue($this->session->hasFlash('foo')); - $this->session->save(); - - $this->session = $this->getSession(); - $this->assertFalse($this->session->hasFlash('foo')); - } - - public function testAll() - { - $this->assertFalse($this->session->has('foo')); - $this->assertNull($this->session->get('foo')); - - $this->session->set('foo', 'bar'); - - $this->assertTrue($this->session->has('foo')); - $this->assertSame('bar', $this->session->get('foo')); - - $this->session = $this->getSession(); - - $this->session->remove('foo'); - $this->session->set('foo', 'bar'); - - $this->session->remove('foo'); - - $this->assertFalse($this->session->has('foo')); - - $attrs = array('foo' => 'bar', 'bar' => 'foo'); - - $this->session = $this->getSession(); - - $this->session->replace($attrs); - - $this->assertSame($attrs, $this->session->all()); - - $this->session->clear(); - - $this->assertSame(array(), $this->session->all()); - } - - public function testMigrateAndInvalidate() - { - $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(); - - $this->assertSame('bar', $this->session->get('foo')); - $this->assertSame('bar', $this->session->getFlash('foo')); - - $this->session = $this->getSession(); - $this->session->invalidate(); - - $this->assertSame(array(), $this->session->all()); - $this->assertSame(array(), $this->session->getFlashes()); - } - - public function testSerialize() - { - $this->session = new Session($this->storage); - - $compare = serialize($this->storage); - - $this->assertSame($compare, $this->session->serialize()); - - $this->session->unserialize($compare); - - $_storage = new \ReflectionProperty(get_class($this->session), 'storage'); - $_storage->setAccessible(true); - - $this->assertEquals($_storage->getValue($this->session), $this->storage, 'storage match'); - } - - public function testSave() - { - $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); - } - - public function testGetId() - { - $this->assertNull($this->session->getId()); - } - - public function testStart() - { - $this->session->start(); - - $this->assertSame(array(), $this->session->getFlashes()); - $this->assertSame(array(), $this->session->all()); - } - - public function testSavedOnDestruct() - { - $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')); - } - - protected function getSession() - { - return new Session($this->storage); - } -} diff --git a/tests/Symfony/Tests/Component/HttpKernel/EventListener/LocaleListenerTest.php b/tests/Symfony/Tests/Component/HttpKernel/EventListener/LocaleListenerTest.php index cf12ec79fc..ecce76ec5e 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/EventListener/LocaleListenerTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/EventListener/LocaleListenerTest.php @@ -33,7 +33,7 @@ class LocaleListenerTest extends \PHPUnit_Framework_TestCase session_name('foo'); $request->cookies->set('foo', 'value'); - $session = $this->getMock('Symfony\Component\HttpFoundation\Session', array('get'), array(), '', false); + $session = $this->getMock('Symfony\Component\HttpFoundation\Session\Session', array('get'), array(), '', false); $session->expects($this->once())->method('get')->will($this->returnValue('es')); $request->setSession($session); @@ -55,7 +55,7 @@ class LocaleListenerTest extends \PHPUnit_Framework_TestCase $event = $this->getEvent($request); // also updates the session _locale value - $session = $this->getMock('Symfony\Component\HttpFoundation\Session', array('set', 'get'), array(), '', false); + $session = $this->getMock('Symfony\Component\HttpFoundation\Session\Session', array('set', 'get'), array(), '', false); $session->expects($this->once())->method('set')->with('_locale', 'es'); $session->expects($this->once())->method('get')->with('_locale')->will($this->returnValue('es')); $request->setSession($session); diff --git a/tests/Symfony/Tests/Component/Security/Http/Firewall/ContextListenerTest.php b/tests/Symfony/Tests/Component/Security/Http/Firewall/ContextListenerTest.php index dfbcd79e7f..5c810062ef 100644 --- a/tests/Symfony/Tests/Component/Security/Http/Firewall/ContextListenerTest.php +++ b/tests/Symfony/Tests/Component/Security/Http/Firewall/ContextListenerTest.php @@ -4,8 +4,8 @@ namespace Symfony\Test\Component\Security\Http\Firewall; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\Session; -use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; @@ -63,7 +63,7 @@ class ContextListenerTest extends \PHPUnit_Framework_TestCase protected function runSessionOnKernelResponse($newToken, $original = null) { - $session = new Session(new ArraySessionStorage()); + $session = new Session(new MockArraySessionStorage()); if ($original !== null) { $session->set('_security_session', $original); diff --git a/tests/Symfony/Tests/Component/Security/Http/Logout/SessionLogoutHandlerTest.php b/tests/Symfony/Tests/Component/Security/Http/Logout/SessionLogoutHandlerTest.php index 05df6cf8f5..9f38e4c9c1 100644 --- a/tests/Symfony/Tests/Component/Security/Http/Logout/SessionLogoutHandlerTest.php +++ b/tests/Symfony/Tests/Component/Security/Http/Logout/SessionLogoutHandlerTest.php @@ -22,7 +22,7 @@ class SessionLogoutHandlerTest extends \PHPUnit_Framework_TestCase $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); $response = new Response(); - $session = $this->getMock('Symfony\Component\HttpFoundation\Session', array(), array(), '', false); + $session = $this->getMock('Symfony\Component\HttpFoundation\Session\Session', array(), array(), '', false); $request ->expects($this->once())