merged branch drak/sessionhandler (PR #3493)

Commits
-------

eb9bf05 [HttpFoundation] Remove hard coded assumptions and replace with API calls.
9a5fc65 [HttpFoundation] Add more tests.
68074a2 Changelog and upgrading changes.
7f33b33 Refactor SessionStorage to NativeSessionStorage.
b12ece0 [HttpFoundation][FrameworkBundle] Separate out mock session storage and stop polluting global namespace.
d687801 [HttpKernel] Mock must invoke constructor.
7b36d0c [DoctrineBridge][HttpFoundation] Refactored tests.
39526df [HttpFoundation] Refactor away options property.
21221f7 [FrameworkBundle] Make use of session API.
cb873b2 [HttpFoundation] Add tests and some CS/docblocks.
a6a9280 [DoctrineBridge] Refactor session storage to handler.
a1c678e [FrameworkBundle] Add session.handler service and handler_id configuration property.
1308312 [HttpFoundation] Add and relocate tests.
88b1170 [HttpFoundation] Refactor tests.
2257a3d [HttpFoundation] Move session handler classes.
0a064d8 [HttpFoundation] Refactor session handlers.
2326707 [HttpFoundation] Split session handler callbacks to separate object.
bb30a44 [HttpFoundation] Prepare to split out session handler callback from session storage.

Discussion
----------

[2.1] Support PHP 5.4 \SessionHandler

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
Fixes the following tickets: -
Todo: -

This patch allows us to add services, like an encryption layer into any session handler without having to alter or inherit any code across any session handler, internal or custom.

The `\SessionHandler` class exposes internal PHP's native internal session save handlers like files, memcache, and sqlite by wrapping the internal callbacks through the class giving user-space the chance to intercept, override and filter them by inheriting from `\SessionHandler`.  I've written a pretty nice use-case at http://docs.php.net/sessionhandler which really shows the power of it. I never considered how to make proper use of the `\SessionHandler` in Symfony2 until I wrote the code example you see in that documentation and also because of the `AbstractSessionStorage` base class got in the way.

It's really trivial to enable support for this in Symfony2 but requires to separate out the actual handlers because inheritance is not suitable.

Obviously, the feature will only work with internal PHP-extension provided handlers under PHP 5.4 and will already work in PHP 5.3 with any custom handler (since they all implement `\SessionHandlerInterface`). Symfony2 will also be the first framework to support these amazing features :-D

The necessary changes are really small but beautiful:

The basic idea is this: 1d55d1ff14  removed inheritance and separates out the actual session handler callbacks - the part PHP processes internally.

This is supported by an internal proxy mechanism: 10a36c901e

In terms of BC, not much changes net from 2.0:

  - We can restore the deprecated service ID: `session.storage.native`
  - We add a new service ID `session.handler` (and configuration alias `handler_id`) for the actual session handlers.  This defaults to the renamed `session.handler.native_file` session handler (same behaviour just new name and as it's a default there is no BC break).

---------------------------------------------------------------------------

by fabpot at 2012-03-03T12:15:10Z

Looks good to me. Can you update the CHANGELOG and UPGRADE file accordingly and start to update the documentation at symfony/symfony-docs? Thanks for your work, the session handling in Symfony2 is starting to become amazing!

---------------------------------------------------------------------------

by drak at 2012-03-04T11:09:31Z

@fabpot I will start working on documentation this week and get the CHANGELOG/UPGRADING committed shortly.  I'll ping when done.

---------------------------------------------------------------------------

by drak at 2012-03-14T16:48:37Z

@fabpot - This PR is ready now.
This commit is contained in:
Fabien Potencier 2012-03-15 06:59:41 +01:00
commit 85d40686ab
61 changed files with 1405 additions and 576 deletions

View File

@ -27,8 +27,10 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
### DoctrineBundle
* This bundle has been moved to the Doctrine organization
* added optional `group_by` property to `EntityType` that supports either a `PropertyPath` or a `\Closure` that is evaluated on the entity choices
* The `em` option for the `UniqueEntity` constraint is now optional (and should probably not be used anymore).
* added optional `group_by` property to `EntityType` that supports either a
`PropertyPath` or a `\Closure` that is evaluated on the entity choices
* The `em` option for the `UniqueEntity` constraint is now optional (and should
probably not be used anymore).
### FrameworkBundle
@ -39,13 +41,17 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
* added Controller::getUser()
* [BC BREAK] assets_base_urls and base_urls merging strategy has changed
* changed the default profiler storage to use the filesystem instead of SQLite
* added support for placeholders in route defaults and requirements (replaced by the value set in the service container)
* added support for placeholders in route defaults and requirements (replaced
by the value set in the service container)
* added Filesystem component as a dependency
* added support for hinclude (use ``standalone: 'js'`` in render tag)
* session options: lifetime, path, domain, secure, httponly were deprecated.
Prefixed versions should now be used instead: cookie_lifetime, cookie_path, cookie_domain, cookie_secure, cookie_httponly
* [BC BREAK] following session options: 'lifetime', 'path', 'domain', 'secure', 'httponly'
are now prefixed with cookie_ when dumped to the container
Prefixed versions should now be used instead: cookie_lifetime, cookie_path,
cookie_domain, cookie_secure, cookie_httponly
* [BC BREAK] following session options: 'lifetime', 'path', 'domain', 'secure',
'httponly' are now prefixed with cookie_ when dumped to the container
* Added `handler_id` configuration under `session` key to represent `session.handler`
service, defaults to `session.handler.native_file`.
### MonologBundle
@ -257,32 +263,47 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
* 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
Session handlers are located in the subnamespace `Symfony\Component\HttpFoudation\Session\Handler`.
* SessionHandlers must implement `\SessionHandlerInterface` or extend from the
`Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class.
* Added internal storage driver proxy mechanism for forward compatibility with
PHP 5.4 `\SessionHandler` class.
* Added session handlers for PHP native Memcache, Memcached and SQLite session
save handlers.
* Added session handlers for custom Memcache, Memcached and Null session save handlers.
* [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`.
* [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and
`remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class
is a mediator for the session storage internals including the session handlers
which do the real work of participating in the internal PHP session workflow.
* [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit
and functional testing without starting real PHP sessions. Removed
`ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit
tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage`
for functional tests. These do not interact with global session ini
configuration values, session functions or `$_SESSION` supreglobal. This means
they can be configured directly allowing multiple instances to work without
conflicting in the same PHP process.
* [BC BREAK] Removed the `close()` method from the `Session` class, as this is
now redundant.
* 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 `SessionHandlerInterface` interface which storage drivers should implement after inheriting from
`Symfony\Component\HttpFoundation\Session\Storage\AbstractSessionStorage` when writing custom
session save handlers using PHP 5.3. This interface is a stub for the PHP 5.4 interface.
* [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.
`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.
* Session data is now managed by `SessionBagInterface` which to better encapsulate
session data.
* Refactored session attribute and flash messages system to their own
`SessionBagInterface` implementations.
* Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This
implementation is 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 retrieved by `get()` or `all()`.
* 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.
### HttpKernel

View File

@ -334,13 +334,11 @@ UPGRADE FROM 2.0 to 2.1
{% endfor %}
```
* Session storage drivers should inherit from
`Symfony\Component\HttpFoundation\Session\Storage\AbstractSessionStorage`
and should no longer implement `read()`, `write()`, and `remove()`, which
were removed from `SessionStorageInterface`.
* Session handler drivers should implement `\SessionHandlerInterface` or extend from
`Symfony\Component\HttpFoudation\Session\Storage\Handler\NativeHandlerInterface` base class and renamed
to `Handler\FooSessionHandler`. E.g. `PdoSessionStorage` becomes `Handler\PdoSessionHandler`.
Any session storage driver that wants to use custom save handlers should
implement `SessionHandlerInterface`.
* Refactor code using `$session->*flash*()` methods to use `$session->getFlashBag()->*()`.
### FrameworkBundle
@ -371,3 +369,20 @@ UPGRADE FROM 2.0 to 2.1
cookie_httponly: true
```
Added `handler_id`, defaults to `session.handler.native_file`.
```
framework:
session:
storage_id: session.storage.native
handler_id: session.handler.native_file
```
To use mock session storage use the following. `handler_id` is irrelevant in this context.
```
framework:
session:
storage_id: session.storage.mock_file
```

View File

@ -1,9 +1,17 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\HttpFoundation;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Symfony\Component\HttpFoundation\Session\Storage\AbstractSessionStorage;
use Doctrine\DBAL\Driver\Connection;
/**
@ -12,7 +20,7 @@ use Doctrine\DBAL\Driver\Connection;
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class DbalSessionStorage extends AbstractSessionStorage implements \SessionHandlerInterface
class DbalSessionHandler implements \SessionHandlerInterface
{
/**
* @var Connection
@ -25,26 +33,19 @@ class DbalSessionStorage extends AbstractSessionStorage implements \SessionHandl
private $tableName;
/**
* Constructor.
*
* @param Connection $con An instance of Connection.
* @param string $tableName Table name.
* @param array $options Session configuration options
* @param Connection $con An instance of Connection.
* @param string $tableName Table name.
*/
public function __construct(Connection $con, $tableName = 'sessions', array $options = array())
public function __construct(Connection $con, $tableName = 'sessions')
{
$this->con = $con;
$this->tableName = $tableName;
parent::__construct($options);
}
/**
* Opens a session.
*
* @param string $path (ignored)
* @param string $name (ignored)
*
* @return Boolean true, if the session was opened, otherwise an exception is thrown
* {@inheritdoc}
*/
public function open($path = null, $name = null)
{
@ -52,9 +53,7 @@ class DbalSessionStorage extends AbstractSessionStorage implements \SessionHandl
}
/**
* Closes a session.
*
* @return Boolean true, if the session was closed, otherwise false
* {@inheritdoc}
*/
public function close()
{
@ -63,13 +62,7 @@ class DbalSessionStorage extends AbstractSessionStorage implements \SessionHandl
}
/**
* Destroys a session.
*
* @param string $id A session ID
*
* @return Boolean true, if the session was destroyed, otherwise an exception is thrown
*
* @throws \RuntimeException If the session cannot be destroyed
* {@inheritdoc}
*/
public function destroy($id)
{
@ -85,13 +78,7 @@ class DbalSessionStorage extends AbstractSessionStorage implements \SessionHandl
}
/**
* 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
*
* @throws \RuntimeException If any old sessions cannot be cleaned
* {@inheritdoc}
*/
public function gc($lifetime)
{
@ -107,13 +94,7 @@ class DbalSessionStorage extends AbstractSessionStorage implements \SessionHandl
}
/**
* 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
*
* @throws \RuntimeException If the session cannot be read
* {@inheritdoc}
*/
public function read($id)
{
@ -136,14 +117,7 @@ class DbalSessionStorage extends AbstractSessionStorage implements \SessionHandl
}
/**
* 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
*
* @throws \RuntimeException If the session data cannot be written
* {@inheritdoc}
*/
public function write($id, $data)
{

View File

@ -1,5 +1,14 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\HttpFoundation;
use Doctrine\DBAL\Schema\Schema;
@ -9,7 +18,7 @@ use Doctrine\DBAL\Schema\Schema;
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
final class DbalSessionStorageSchema extends Schema
final class DbalSessionHandlerSchema extends Schema
{
private $tableName;

View File

@ -167,7 +167,8 @@ class Configuration implements ConfigurationInterface
->canBeUnset()
->children()
->booleanNode('auto_start')->defaultFalse()->end()
->scalarNode('storage_id')->defaultValue('session.storage.native_file')->end()
->scalarNode('storage_id')->defaultValue('session.storage.native')->end()
->scalarNode('handler_id')->defaultValue('session.handler.native_file')->end()
->scalarNode('name')->end()
->scalarNode('cookie_lifetime')->end()
->scalarNode('cookie_path')->end()

View File

@ -310,10 +310,16 @@ class FrameworkExtension extends Extension
}
$container->setParameter('session.storage.options', $options);
// session handler (the internal callback registered with PHP session management)
$container->setAlias('session.handler', $config['handler_id']);
$this->addClassesToCompile(array(
'Symfony\\Bundle\\FrameworkBundle\\EventListener\\SessionListener',
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface',
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\AbstractSessionStorage',
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage',
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\NativeSessionHandler',
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\AbstractProxy',
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\SessionHandlerProxy',
$container->getDefinition('session')->getClass(),
));

View File

@ -28,7 +28,14 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*/
class SessionListener implements EventSubscriberInterface
{
/**
* @var ContainerInterface
*/
private $container;
/**
* @var boolean
*/
private $autoStart;
public function __construct(ContainerInterface $container, $autoStart = false)

View File

@ -43,15 +43,16 @@ class TestSessionListener implements EventSubscriberInterface
}
// bootstrap the session
if ($this->container->has('session')) {
$this->container->get('session');
if (!$this->container->has('session')) {
return;
}
$session = $this->container->get('session');
$cookies = $event->getRequest()->cookies;
if ($cookies->has(session_name())) {
session_id($cookies->get(session_name()));
if ($cookies->has($session->getName())) {
$session->setId($cookies->get($session->getName()));
} else {
session_id('');
$session->setId('');
}
}
@ -72,7 +73,7 @@ class TestSessionListener implements EventSubscriberInterface
$params = session_get_cookie_params();
$event->getResponse()->headers->setCookie(new Cookie(session_name(), session_id(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']));
$event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']));
}
}

View File

@ -74,6 +74,7 @@
<xsd:complexType name="session">
<xsd:attribute name="storage-id" type="xsd:string" />
<xsd:attribute name="handler-id" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="cookie-lifetime" type="xsd:integer" />
<xsd:attribute name="cookie-path" type="xsd:string" />

View File

@ -8,8 +8,9 @@
<parameter key="session.class">Symfony\Component\HttpFoundation\Session\Session</parameter>
<parameter key="session.flashbag.class">Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag</parameter>
<parameter key="session.attribute_bag.class">Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag</parameter>
<parameter key="session.storage.native_file.class">Symfony\Component\HttpFoundation\Session\Storage\NativeFileSessionStorage</parameter>
<parameter key="session.storage.native.class">Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage</parameter>
<parameter key="session.storage.mock_file.class">Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage</parameter>
<parameter key="session.handler.native_file.class">Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler</parameter>
<parameter key="session_listener.class">Symfony\Bundle\FrameworkBundle\EventListener\SessionListener</parameter>
</parameters>
@ -20,18 +21,21 @@
<argument type="service" id="session.flash_bag" />
</service>
<service id="session.storage.native" class="%session.storage.native.class%">
<argument>%session.storage.options%</argument>
<argument type="service" id="session.handler" />
</service>
<service id="session.flash_bag" class="%session.flashbag.class%" public="false" />
<service id="session.attribute_bag" class="%session.attribute_bag.class%" public="false" />
<service id="session.storage.mock_file" class="%session.storage.mock_file.class%" public="false">
<argument>%kernel.cache_dir%/sessions</argument>
<argument>%session.storage.options%</argument>
</service>
<service id="session.storage.native_file" class="%session.storage.native_file.class%" public="false">
<service id="session.handler.native_file" class="%session.handler.native_file.class%" public="false">
<argument>%kernel.cache_dir%/sessions</argument>
<argument>%session.storage.options%</argument>
</service>
<service id="session_listener" class="%session_listener.class%">
@ -40,7 +44,6 @@
</service>
<!-- for BC -->
<service id="session.storage.native" alias="session.storage.native_file" />
<service id="session.storage.filesystem" alias="session.storage.mock_file" />
</services>
</container>

View File

@ -4,7 +4,8 @@ $container->loadFromExtension('framework', array(
'secret' => 's3cr3t',
'session' => array(
'auto_start' => true,
'storage_id' => 'session.storage.native_file',
'storage_id' => 'session.storage.native',
'handler_id' => 'session.handler.native_file',
'name' => '_SYMFONY',
'lifetime' => 2012,
'path' => '/sf2',

View File

@ -4,7 +4,8 @@ $container->loadFromExtension('framework', array(
'secret' => 's3cr3t',
'session' => array(
'auto_start' => true,
'storage_id' => 'session.storage.native_file',
'storage_id' => 'session.storage.native',
'handler_id' => 'session.handler.native_file',
'name' => '_SYMFONY',
'lifetime' => 2012,
'path' => '/sf2',

View File

@ -20,7 +20,8 @@ $container->loadFromExtension('framework', array(
),
'session' => array(
'auto_start' => true,
'storage_id' => 'session.storage.native_file',
'storage_id' => 'session.storage.native',
'handler_id' => 'session.handler.native_file',
'name' => '_SYMFONY',
'lifetime' => 86400,
'path' => '/',

View File

@ -7,6 +7,6 @@
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config secret="s3cr3t">
<framework:session auto-start="true" storage-id="session.storage.native_file" name="_SYMFONY" lifetime="2012" path="/sf2" domain="sf2.example.com" secure="false" httponly="false" cookie-lifetime="86400" cookie-path="/" cookie-domain="example.com" cookie-secure="true" cookie-httponly="true" />
<framework:session auto-start="true" storage-id="session.storage.native" handler-id="session.handler.native_file" name="_SYMFONY" lifetime="2012" path="/sf2" domain="sf2.example.com" secure="false" httponly="false" cookie-lifetime="86400" cookie-path="/" cookie-domain="example.com" cookie-secure="true" cookie-httponly="true" />
</framework:config>
</container>

View File

@ -7,6 +7,6 @@
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config secret="s3cr3t">
<framework:session auto-start="true" storage-id="session.storage.native_file" name="_SYMFONY" lifetime="2012" path="/sf2" domain="sf2.example.com" secure="false" httponly="false" cookie-lifetime="86400" cookie-path="/" cookie-httponly="true" />
<framework:session auto-start="true" storage-id="session.storage.native" handler-id="session.handler.native_file" name="_SYMFONY" lifetime="2012" path="/sf2" domain="sf2.example.com" secure="false" httponly="false" cookie-lifetime="86400" cookie-path="/" cookie-httponly="true" />
</framework:config>
</container>

View File

@ -12,7 +12,7 @@
<framework:esi enabled="true" />
<framework:profiler only-exceptions="true" />
<framework:router resource="%kernel.root_dir%/config/routing.xml" type="xml" />
<framework:session auto-start="true" storage-id="session.storage.native_file" name="_SYMFONY" cookie-lifetime="86400" cookie-path="/" cookie-domain="example.com" cookie-secure="true" cookie-httponly="true" />
<framework:session auto-start="true" storage-id="session.storage.native" handler-id="session.handler.native_file" name="_SYMFONY" cookie-lifetime="86400" cookie-path="/" cookie-domain="example.com" cookie-secure="true" cookie-httponly="true" />
<framework:templating assets-version="SomeVersionScheme" cache="/path/to/cache" >
<framework:loader>loader.foo</framework:loader>
<framework:loader>loader.bar</framework:loader>

View File

@ -2,7 +2,8 @@ framework:
secret: s3cr3t
session:
auto_start: true
storage_id: session.storage.native_file
storage_id: session.storage.native
handler_id: session.handler.native_file
name: _SYMFONY
lifetime: 2012
path: /sf2

View File

@ -2,7 +2,8 @@ framework:
secret: s3cr3t
session:
auto_start: true
storage_id: session.storage.native_file
storage_id: session.storage.native
handler_id: session.handler.native_file
name: _SYMFONY
lifetime: 2012
path: /sf2

View File

@ -14,7 +14,8 @@ framework:
type: xml
session:
auto_start: true
storage_id: session.storage.native_file
storage_id: session.storage.native
handler_id: session.handler.native_file
name: _SYMFONY
lifetime: 86400
path: /

View File

@ -78,7 +78,8 @@ 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_file', (string) $container->getAlias('session.storage'));
$this->assertEquals('session.storage.native', (string) $container->getAlias('session.storage'));
$this->assertEquals('session.handler.native_file', (string) $container->getAlias('session.handler'));
$options = $container->getParameter('session.storage.options');
$this->assertEquals('_SYMFONY', $options['name']);

View File

@ -94,8 +94,13 @@ class TestSessionListenerTest extends \PHPUnit_Framework_TestCase
private function getSession()
{
return $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Session')
$mock = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Session')
->disableOriginalConstructor()
->getMock();
// set return value for getName()
$mock->expects($this->any())->method('getName')->will($this->returnValue('MOCKSESSID'));
return $mock;
}
}

View File

@ -496,7 +496,8 @@ class Request
public function hasPreviousSession()
{
// the check for $this->session avoids malicious users trying to fake a session cookie with proper name
return $this->cookies->has(session_name()) && null !== $this->session;
$sessionName = $this->hasSession() ? $this->session->getName() : null;
return $this->cookies->has($sessionName) && $this->hasSession();
}
/**

View File

@ -17,6 +17,7 @@ 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;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
/**
* Session.
@ -35,18 +36,34 @@ class Session implements SessionInterface
*/
protected $storage;
/**
* @var string
*/
private $flashName;
/**
* @var string
*/
private $attributeName;
/**
* Constructor.
*
* @param SessionStorageInterface $storage A SessionStorageInterface instance.
* @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)
public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null)
{
$this->storage = $storage;
$this->registerBag($attributes ?: new AttributeBag());
$this->registerBag($flashes ?: new FlashBag());
$this->storage = $storage ?: new NativeSessionStorage();
$attributeBag = $attributes ?: new AttributeBag();
$this->attributeName = $attributeBag->getName();
$this->registerBag($attributeBag);
$flashBag = $flashes ?: new FlashBag();
$this->flashName = $flashBag->getName();
$this->registerBag($flashBag);
}
/**
@ -62,7 +79,7 @@ class Session implements SessionInterface
*/
public function has($name)
{
return $this->storage->getBag('attributes')->has($name);
return $this->storage->getBag($this->attributeName)->has($name);
}
/**
@ -70,7 +87,7 @@ class Session implements SessionInterface
*/
public function get($name, $default = null)
{
return $this->storage->getBag('attributes')->get($name, $default);
return $this->storage->getBag($this->attributeName)->get($name, $default);
}
/**
@ -78,7 +95,7 @@ class Session implements SessionInterface
*/
public function set($name, $value)
{
$this->storage->getBag('attributes')->set($name, $value);
$this->storage->getBag($this->attributeName)->set($name, $value);
}
/**
@ -86,7 +103,7 @@ class Session implements SessionInterface
*/
public function all()
{
return $this->storage->getBag('attributes')->all();
return $this->storage->getBag($this->attributeName)->all();
}
/**
@ -94,7 +111,7 @@ class Session implements SessionInterface
*/
public function replace(array $attributes)
{
$this->storage->getBag('attributes')->replace($attributes);
$this->storage->getBag($this->attributeName)->replace($attributes);
}
/**
@ -102,7 +119,7 @@ class Session implements SessionInterface
*/
public function remove($name)
{
return $this->storage->getBag('attributes')->remove($name);
return $this->storage->getBag($this->attributeName)->remove($name);
}
/**
@ -110,7 +127,7 @@ class Session implements SessionInterface
*/
public function clear()
{
$this->storage->getBag('attributes')->clear();
$this->storage->getBag($this->attributeName)->clear();
}
/**
@ -140,11 +157,7 @@ class Session implements SessionInterface
}
/**
* Returns the session ID
*
* @return mixed The session ID
*
* @api
* {@inheritdoc}
*/
public function getId()
{
@ -152,7 +165,31 @@ class Session implements SessionInterface
}
/**
* Registers a SessionBagInterface with the sessio.
* {@inheritdoc}
*/
public function setId($id)
{
$this->storage->setId($id);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->storage->getName();
}
/**
* {@inheritdoc}
*/
public function setName($name)
{
$this->storage->setName($name);
}
/**
* Registers a SessionBagInterface with the session.
*
* @param SessionBagInterface $bag
*/
@ -180,7 +217,7 @@ class Session implements SessionInterface
*/
public function getFlashBag()
{
return $this->getBag('flashes');
return $this->getBag($this->flashName);
}
// the following methods are kept for compatibility with Symfony 2.0 (they will be removed for Symfony 2.3)

View File

@ -29,6 +29,42 @@ interface SessionInterface
*/
function start();
/**
* Returns the session ID.
*
* @return string The session ID.
*
* @api
*/
function getId();
/**
* Sets the session ID
*
* @param string $id
*
* @api
*/
function setId($id);
/**
* Returns the session name.
*
* @return mixed The session name.
*
* @api
*/
function getName();
/**
* Sets the session name.
*
* @param string $name
*
* @api
*/
function setName($name);
/**
* Invalidates the current session.
*

View File

@ -9,14 +9,14 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage;
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* MemcacheSessionStorage.
*
* @author Drak <drak@zikula.org>
*/
class MemcacheSessionStorage extends AbstractSessionStorage implements \SessionHandlerInterface
class MemcacheSessionHandler implements \SessionHandlerInterface
{
/**
* Memcache driver.
@ -68,8 +68,6 @@ class MemcacheSessionStorage extends AbstractSessionStorage implements \SessionH
$this->prefix = isset($memcacheOptions['prefix']) ? $memcacheOptions['prefix'] : 'sf2s';
$this->memcacheOptions = $memcacheOptions;
parent::__construct($options);
}
protected function addServer(array $server)

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage;
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* MemcachedSessionStorage.
@ -21,7 +21,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage;
*
* @author Drak <drak@zikula.org>
*/
class MemcachedSessionStorage extends AbstractSessionStorage implements \SessionHandlerInterface
class MemcachedSessionHandler implements \SessionHandlerInterface
{
/**
* Memcached driver.
@ -63,8 +63,6 @@ class MemcachedSessionStorage extends AbstractSessionStorage implements \Session
$this->memcached->setOption(\Memcached::OPT_PREFIX_KEY, isset($memcachedOptions['prefix']) ? $memcachedOptions['prefix'] : 'sf2s');
$this->memcachedOptions = $memcachedOptions;
parent::__construct($options);
}
/**

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* NativeFileSessionHandler.
*
* Native session handler using PHP's built in file storage.
*
* @author Drak <drak@zikula.org>
*/
class NativeFileSessionHandler extends NativeSessionHandler
{
/**
* Constructor.
*
* @param string $savePath Path of directory to save session files. Default null will leave setting as defined by PHP.
*/
public function __construct($savePath = null)
{
if (null === $savePath) {
$savePath = ini_get('session.save_path');
}
if ($savePath && !is_dir($savePath)) {
mkdir($savePath, 0777, true);
}
ini_set('session.save_handler', 'files');
ini_set('session.save_path', $savePath);
}
}

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage;
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* NativeMemcacheSessionStorage.
@ -20,13 +20,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage;
*
* @author Drak <drak@zikula.org>
*/
class NativeMemcacheSessionStorage extends AbstractSessionStorage
class NativeMemcacheSessionHandler extends NativeSessionHandler
{
/**
* @var string
*/
private $savePath;
/**
* Constructor.
*
@ -41,17 +36,14 @@ class NativeMemcacheSessionStorage extends AbstractSessionStorage
throw new \RuntimeException('PHP does not have "memcache" session module registered');
}
$this->savePath = $savePath;
parent::__construct($options);
}
if (null === $savePath) {
$savePath = ini_get('session.save_path');
}
/**
* {@inheritdoc}
*/
protected function registerSaveHandlers()
{
ini_set('session.save_handler', 'memcache');
ini_set('session.save_path', $this->savePath);
ini_set('session.save_path', $savePath);
$this->setOptions($options);
}
/**
@ -73,7 +65,5 @@ class NativeMemcacheSessionStorage extends AbstractSessionStorage
ini_set($key, $value);
}
}
parent::setOptions($options);
}
}

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage;
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* NativeMemcachedSessionStorage.
@ -20,13 +20,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage;
*
* @author Drak <drak@zikula.org>
*/
class NativeMemcachedSessionStorage extends AbstractSessionStorage
class NativeMemcachedSessionHandler extends NativeSessionHandler
{
/**
* @var string
*/
private $savePath;
/**
* Constructor.
*
@ -41,17 +36,14 @@ class NativeMemcachedSessionStorage extends AbstractSessionStorage
throw new \RuntimeException('PHP does not have "memcached" session module registered');
}
$this->savePath = $savePath;
parent::__construct($options);
}
if (null === $savePath) {
$savePath = ini_get('session.save_path');
}
/**
* {@inheritdoc}
*/
protected function registerSaveHandlers()
{
ini_set('session.save_handler', 'memcached');
ini_set('session.save_path', $this->savePath);
ini_set('session.save_path', $savePath);
$this->setOptions($options);
}
/**
@ -72,7 +64,5 @@ class NativeMemcachedSessionStorage extends AbstractSessionStorage
ini_set($key, $value);
}
}
parent::setOptions($options);
}
}

View File

@ -0,0 +1,24 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* Adds SessionHandler functionality if available.
*
* @see http://php.net/sessionhandler
*/
if (version_compare(phpversion(), '5.4.0', '>=')) {
class NativeSessionHandler extends \SessionHandler {}
} else {
class NativeSessionHandler {}
}

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage;
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* NativeSqliteSessionStorage.
@ -18,38 +18,30 @@ namespace Symfony\Component\HttpFoundation\Session\Storage;
*
* @author Drak <drak@zikula.org>
*/
class NativeSqliteSessionStorage extends AbstractSessionStorage
class NativeSqliteSessionHandler extends NativeSessionHandler
{
/**
* @var string
*/
private $dbPath;
/**
* Constructor.
*
* @param string $dbPath Path to SQLite database file.
* @param array $options Session configuration options.
* @param string $savePath Path to SQLite database file itself.
* @param array $options Session configuration options.
*
* @see AbstractSessionStorage::__construct()
*/
public function __construct($dbPath, array $options = array())
public function __construct($savePath, array $options = array())
{
if (!extension_loaded('sqlite')) {
throw new \RuntimeException('PHP does not have "sqlite" session module registered');
}
$this->dbPath = $dbPath;
parent::__construct($options);
}
if (null === $savePath) {
$savePath = ini_get('session.save_path');
}
/**
* {@inheritdoc}
*/
protected function registerSaveHandlers()
{
ini_set('session.save_handler', 'sqlite');
ini_set('session.save_path', $this->dbPath);
ini_set('session.save_path', $savePath);
$this->setOptions($options);
}
/**
@ -66,7 +58,5 @@ class NativeSqliteSessionStorage extends AbstractSessionStorage
ini_set($key, $value);
}
}
parent::setOptions($options);
}
}

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage;
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* NullSessionStorage.
@ -20,7 +20,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage;
*
* @api
*/
class NullSessionStorage extends AbstractSessionStorage implements \SessionHandlerInterface
class NullSessionHandler implements \SessionHandlerInterface
{
/**
* {@inheritdoc}

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage;
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* PdoSessionStorage.
@ -17,7 +17,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage;
* @author Fabien Potencier <fabien@symfony.com>
* @author Michael Williams <michael.williams@funsational.com>
*/
class PdoSessionStorage extends AbstractSessionStorage implements \SessionHandlerInterface
class PdoSessionHandler implements \SessionHandlerInterface
{
/**
* PDO instance.
@ -58,8 +58,6 @@ class PdoSessionStorage extends AbstractSessionStorage implements \SessionHandle
'db_data_col' => 'sess_data',
'db_time_col' => 'sess_time',
), $dbOptions);
parent::__construct($options);
}
/**

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\HttpFoundation\Session\Storage;
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
/**
* MockArraySessionStorage mocks the session for unit tests.
*
@ -23,21 +25,51 @@ namespace Symfony\Component\HttpFoundation\Session\Storage;
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Drak <drak@zikula.org>
*/
class MockArraySessionStorage extends AbstractSessionStorage
class MockArraySessionStorage implements SessionStorageInterface
{
/**
* @var string
*/
protected $sessionId;
protected $id = '';
/**
* @var string
*/
protected $name;
/**
* @var boolean
*/
protected $started = false;
/**
* @var boolean
*/
protected $closed = false;
/**
* @var array
*/
protected $sessionData = array();
protected $data = array();
/**
* Constructor.
*
* @param string $name Session name
*/
public function __construct($name = 'MOCKSESSID')
{
$this->name = $name;
}
/**
* Sets the session data.
*
* @param array $array
*/
public function setSessionData(array $array)
{
$this->sessionData = $array;
$this->data = $array;
}
/**
@ -49,11 +81,11 @@ class MockArraySessionStorage extends AbstractSessionStorage
return true;
}
$this->started = true;
$this->loadSession($this->sessionData);
if (empty($this->id)) {
$this->id = $this->generateId();
}
$this->sessionId = $this->generateSessionId();
session_id($this->sessionId);
$this->loadSession();
return true;
}
@ -64,12 +96,11 @@ class MockArraySessionStorage extends AbstractSessionStorage
*/
public function regenerate($destroy = false)
{
if ($this->options['auto_start'] && !$this->started) {
if (!$this->started) {
$this->start();
}
$this->sessionId = $this->generateSessionId();
session_id($this->sessionId);
$this->id = $this->generateId();
return true;
}
@ -79,11 +110,35 @@ class MockArraySessionStorage extends AbstractSessionStorage
*/
public function getId()
{
if (!$this->started) {
return '';
return $this->id;
}
/**
* {@inheritdoc}
*/
public function setId($id)
{
if ($this->started) {
throw new \LogicException('Cannot set session ID after the session has started.');
}
return $this->sessionId;
$this->id = $id;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function setName($name)
{
$this->name = $name;
}
/**
@ -106,19 +161,58 @@ class MockArraySessionStorage extends AbstractSessionStorage
}
// clear out the session
$this->sessionData = array();
$this->data = array();
// reconnect the bags to the session
$this->loadSession($this->sessionData);
$this->loadSession();
}
/**
* {@inheritdoc}
*/
public function registerBag(SessionBagInterface $bag)
{
$this->bags[$bag->getName()] = $bag;
}
/**
* {@inheritdoc}
*/
public function getBag($name)
{
if (!isset($this->bags[$name])) {
throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
}
if (!$this->started) {
$this->start();
}
return $this->bags[$name];
}
/**
* Generates a session ID.
*
* This doesn't need to be particularly cryptographically secure since this is just
* a mock.
*
* @return string
*/
protected function generateSessionId()
protected function generateId()
{
return sha1(uniqid(mt_rand(), true));
return sha1(uniqid(mt_rand()));
}
protected function loadSession()
{
foreach ($this->bags as $bag) {
$key = $bag->getStorageKey();
$this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : array();
$bag->initialize($this->data[$key]);
}
$this->started = true;
$this->closed = false;
}
}

View File

@ -16,7 +16,9 @@ namespace Symfony\Component\HttpFoundation\Session\Storage;
* 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.
* and shutdown only once per PHP execution cycle and this class does
* not pollute any session related globals, including session_*() functions
* or session.* PHP ini directives.
*
* @author Drak <drak@zikula.org>
*/
@ -31,11 +33,9 @@ class MockFileSessionStorage extends MockArraySessionStorage
* Constructor.
*
* @param string $savePath Path of directory to save session files.
* @param array $options Session options.
*
* @see AbstractSessionStorage::__construct()
* @param string $name Session name.
*/
public function __construct($savePath = null, array $options = array())
public function __construct($savePath = null, $name = 'MOCKSESSID')
{
if (null === $savePath) {
$savePath = sys_get_temp_dir();
@ -47,7 +47,7 @@ class MockFileSessionStorage extends MockArraySessionStorage
$this->savePath = $savePath;
parent::__construct($options);
parent::__construct($name);
}
/**
@ -59,12 +59,10 @@ class MockFileSessionStorage extends MockArraySessionStorage
return true;
}
if (!session_id()) {
session_id($this->generateSessionId());
if (!$this->id) {
$this->id = $this->generateId();
}
$this->sessionId = session_id();
$this->read();
$this->started = true;
@ -81,10 +79,7 @@ class MockFileSessionStorage extends MockArraySessionStorage
$this->destroy();
}
session_id($this->generateSessionId());
$this->sessionId = session_id();
$this->save();
$this->id = $this->generateId();
return true;
}
@ -92,23 +87,15 @@ class MockFileSessionStorage extends MockArraySessionStorage
/**
* {@inheritdoc}
*/
public function getId()
public function save()
{
if (!$this->started) {
return '';
}
return $this->sessionId;
file_put_contents($this->getFilePath(), serialize($this->data));
}
/**
* {@inheritdoc}
* Deletes a session from persistent storage.
* Deliberately leaves session data in memory intact.
*/
public function save()
{
file_put_contents($this->getFilePath(), serialize($this->sessionData));
}
private function destroy()
{
if (is_file($this->getFilePath())) {
@ -121,16 +108,19 @@ class MockFileSessionStorage extends MockArraySessionStorage
*
* @return string File path
*/
public function getFilePath()
private function getFilePath()
{
return $this->savePath.'/'.$this->sessionId.'.sess';
return $this->savePath.'/'.$this->id.'.mocksess';
}
/**
* Reads session from storage and loads session.
*/
private function read()
{
$filePath = $this->getFilePath();
$this->sessionData = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : array();
$this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : array();
$this->loadSession($this->sessionData);
$this->loadSession();
}
}

View File

@ -1,59 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage;
/**
* NativeFileSessionStorage.
*
* Native session handler using PHP's built in file storage.
*
* @author Drak <drak@zikula.org>
*/
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);
}
}

View File

@ -12,20 +12,16 @@
namespace Symfony\Component\HttpFoundation\Session\Storage;
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
/**
* This provides a base class for session attribute storage.
*
* This can be used to implement internal PHP session handlers
* provided by PHP extensions or custom session save handlers
* implementing the \SessionHandlerInterface
*
* @see http://php.net/session.customhandler
* @see http://php.net/sessionhandlerinterface
*
* @author Drak <drak@zikula.org>
*/
abstract class AbstractSessionStorage implements SessionStorageInterface
class NativeSessionStorage implements SessionStorageInterface
{
/**
* Array of SessionBagInterface
@ -34,11 +30,6 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
*/
protected $bags;
/**
* @var array
*/
protected $options = array();
/**
* @var boolean
*/
@ -49,6 +40,11 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
*/
protected $closed = false;
/**
* @var AbstractProxy
*/
protected $saveHandler;
/**
* Constructor.
*
@ -75,7 +71,6 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
* hash_function, "0"
* name, "PHPSESSID"
* referer_check, ""
* save_path, ""
* serialize_handler, "php"
* use_cookies, "1"
* use_only_cookies, "1"
@ -88,13 +83,34 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
* upload_progress.min-freq, "1"
* url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
*
* @param array $options Session configuration options.
* @param array $options Session configuration options.
* @param object $handler SessionHandlerInterface.
*/
public function __construct(array $options = array())
public function __construct(array $options = array(), $handler = null)
{
// sensible defaults
ini_set('session.auto_start', 0); // by default we prefer to explicitly start the session using the class.
ini_set('session.cache_limiter', ''); // disable by default because it's managed by HeaderBag (if used)
ini_set('session.use_cookies', 1);
if (version_compare(phpversion(), '5.4.0', '>=')) {
session_register_shutdown();
} else {
register_shutdown_function('session_write_close');
}
$this->setOptions($options);
$this->registerSaveHandlers();
$this->registerShutdownFunction();
$this->setSaveHandler($handler);
}
/**
* Gets the save handler instance.
*
* @return AbstractProxy
*/
public function getSaveHandler()
{
return $this->saveHandler;
}
/**
@ -106,8 +122,16 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
return true;
}
if ($this->options['use_cookies'] && headers_sent()) {
throw new \RuntimeException('Failed to start the session because header have already been sent.');
// catch condition where session was started automatically by PHP
if (!$this->started && !$this->closed && $this->saveHandler->isActive()
&& $this->saveHandler->isSessionHandlerInterface()) {
$this->loadSession();
return true;
}
if (ini_get('session.use_cookies') && headers_sent()) {
throw new \RuntimeException('Failed to start the session because headers have already been sent.');
}
// start the session
@ -117,8 +141,9 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
$this->loadSession();
$this->started = true;
$this->closed = false;
if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
$this->saveHandler->setActive(false);
}
return true;
}
@ -132,7 +157,31 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
return ''; // returning empty is consistent with session_id() behaviour
}
return session_id();
return $this->saveHandler->getId();
}
/**
* {@inheritdoc}
*/
public function setId($id)
{
return $this->saveHandler->setId($id);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->saveHandler->getName();
}
/**
* {@inheritdoc}
*/
public function setName($name)
{
$this->saveHandler->setName($name);
}
/**
@ -149,6 +198,11 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
public function save()
{
session_write_close();
if (!$this->saveHandler->isWrapper() && !$this->getSaveHandler()->isSessionHandlerInterface()) {
$this->saveHandler->setActive(false);
}
$this->closed = true;
}
@ -186,8 +240,10 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
}
if ($this->options['auto_start'] && !$this->started) {
if (ini_get('session.auto_start') && !$this->started) {
$this->start();
} else if ($this->saveHandler->isActive() && !$this->started) {
$this->loadSession();
}
return $this->bags[$name];
@ -199,38 +255,20 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
* 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
* @param array $options Session ini directives array(key => value).
*
* @see http://php.net/session.configuration
*/
protected function setOptions(array $options)
public function setOptions(array $options)
{
$this->options = $options;
// set defaults for certain values
$defaults = array(
'cache_limiter' => '', // disable by default because it's managed by HeaderBag (if used)
'auto_start' => false,
'use_cookies' => true,
'cookie_httponly' => true,
);
foreach ($defaults as $key => $value) {
if (!isset($this->options[$key])) {
$this->options[$key] = $value;
}
}
foreach ($this->options as $key => $value) {
foreach ($options as $key => $value) {
if (in_array($key, array(
'auto_start', 'cache_limiter', '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',
'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'))) {
@ -240,7 +278,7 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
}
/**
* Registers this storage device as a PHP session handler.
* Registers save handler as a PHP session handler.
*
* To use internal PHP session save handlers, override this method using ini_set with
* session.save_handlers and session.save_path e.g.
@ -250,34 +288,35 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
*
* @see http://php.net/session-set-save-handler
* @see http://php.net/sessionhandlerinterface
* @see http://php.net/sessionhandler
*
* @param object $saveHandler Default null means NativeProxy.
*/
protected function registerSaveHandlers()
public function setSaveHandler($saveHandler = null)
{
// 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 \SessionHandlerInterface) {
session_set_save_handler(
array($this, 'open'),
array($this, 'close'),
array($this, 'read'),
array($this, 'write'),
array($this, 'destroy'),
array($this, 'gc')
);
// Wrap $saveHandler in proxy
if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
$saveHandler = new SessionHandlerProxy($saveHandler);
} elseif (!$saveHandler instanceof AbstractProxy) {
$saveHandler = new NativeProxy($saveHandler);
}
}
/**
* Registers PHP shutdown function.
*
* This method is required to avoid strange issues when using PHP objects as
* session save handlers.
*
* @see http://php.net/register-shutdown-function
*/
protected function registerShutdownFunction()
{
register_shutdown_function('session_write_close');
$this->saveHandler = $saveHandler;
if ($this->saveHandler instanceof \SessionHandlerInterface) {
if (version_compare(phpversion(), '5.4.0', '>=')) {
session_set_save_handler($this->saveHandler, false);
} else {
session_set_save_handler(
array($this->saveHandler, 'open'),
array($this->saveHandler, 'close'),
array($this->saveHandler, 'read'),
array($this->saveHandler, 'write'),
array($this->saveHandler, 'destroy'),
array($this->saveHandler, 'gc')
);
}
}
}
/**
@ -301,5 +340,8 @@ abstract class AbstractSessionStorage implements SessionStorageInterface
$session[$key] = isset($session[$key]) ? $session[$key] : array();
$bag->initialize($session[$key]);
}
$this->started = true;
$this->closed = false;
}
}

View File

@ -0,0 +1,135 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
/**
* AbstractProxy.
*
* @author Drak <drak@zikula.org>
*/
abstract class AbstractProxy
{
/**
* Flag if handler wraps an internal PHP session handler (using \SessionHandler).
*
* @var boolean
*/
protected $wrapper = false;
/**
* @var boolean
*/
protected $active = false;
/**
* @var string
*/
protected $saveHandlerName;
/**
* Gets the session.save_handler name.
*
* @return string
*/
public function getSaveHandlerName()
{
return $this->saveHandlerName;
}
/**
* Is this proxy handler and instance of \SessionHandlerInterface.
*
* @return boolean
*/
public function isSessionHandlerInterface()
{
return ($this instanceof \SessionHandlerInterface);
}
/**
* Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
*
* @return bool
*/
public function isWrapper()
{
return $this->wrapper;
}
/**
* Has a session started?
*
* @return bool
*/
public function isActive()
{
return $this->active;
}
/**
* Sets the active flag.
*
* @param bool $flag
*/
public function setActive($flag)
{
$this->active = (bool) $flag;
}
/**
* Gets the session ID.
*
* @return string
*/
public function getId()
{
return session_id();
}
/**
* Sets the session ID.
*
* @param string $id
*/
public function setId($id)
{
if ($this->isActive()) {
throw new \LogicException('Cannot change the ID of an active session');
}
session_id($id);
}
/**
* Gets the session name.
*
* @return string
*/
public function getName()
{
return session_name();
}
/**
* Sets the session name.
*
* @param string $name
*/
public function setName($name)
{
if ($this->isActive()) {
throw new \LogicException('Cannot change the name of an active session');
}
session_name($name);
}
}

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
/**
* NativeProxy.
*
* This proxy is built-in session handlers in PHP 5.3.x
*
* @author Drak <drak@zikula.org>
*/
class NativeProxy extends AbstractProxy
{
/**
* Constructor.
*/
public function __construct()
{
// this makes an educated guess as to what the handler is since it should already be set.
$this->saveHandlerName = ini_get('session.save_handler');
}
/**
* Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
*
* @return bool False.
*/
public function isWrapper()
{
return false;
}
}

View File

@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
/**
* SessionHandler proxy.
*
* @author Drak <drak@zikula.org>
*/
class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface
{
/**
* @var \SessionHandlerInterface
*/
protected $handler;
/**
* Constructor.
*
* @param \SessionHandlerInterface $handler
*/
public function __construct(\SessionHandlerInterface $handler)
{
$this->handler = $handler;
$this->wrapper = ($handler instanceof \SessionHandler);
$this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user';
}
// \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
$return = (bool)$this->handler->open($savePath, $sessionName);
if (true === $return) {
$this->active = true;
}
return $return;
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->active = false;
return (bool) $this->handler->close();
}
/**
* {@inheritdoc}
*/
public function read($id)
{
return (string) $this->handler->read($id);
}
/**
* {@inheritdoc}
*/
public function write($id, $data)
{
return (bool) $this->handler->write($id, $data);
}
/**
* {@inheritdoc}
*/
public function destroy($id)
{
return (bool) $this->handler->destroy($id);
}
/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
return (bool) $this->handler->gc($maxlifetime);
}
}

View File

@ -37,12 +37,39 @@ interface SessionStorageInterface
/**
* Returns the session ID
*
* @return mixed The session ID or false if the session has not started.
* @return string The session ID or empty.
*
* @api
*/
function getId();
/**
* Sets the session ID
*
* @param string $id
*
* @api
*/
function setId($id);
/**
* Returns the session name
*
* @return mixed The session name.
*
* @api
*/
function getName();
/**
* Sets the session name
*
* @param string $name
*
* @api
*/
function setName($name);
/**
* Regenerates id that represents this storage.
*
@ -51,6 +78,9 @@ interface SessionStorageInterface
* or functional testing where a real PHP session would interfere
* with testing.
*
* Note regenerate+destroy should not clear the session data in memory
* only delete the session data from persistent storage.
*
* @param Boolean $destroy Destroy session when regenerating?
*
* @return Boolean True if session regenerated, false if error

View File

@ -11,19 +11,19 @@
namespace Symfony\Bridge\Doctrine\HttpFoundation;
use Symfony\Bridge\Doctrine\HttpFoundation\DbalSessionStorage;
use Symfony\Bridge\Doctrine\HttpFoundation\DbalSessionHandler;
/**
* Test class for DbalSessionStorage.
*
* @author Drak <drak@zikula.org>
*/
class DbalSessionStorageTest extends \PHPUnit_Framework_TestCase
class DbalSessionHandlerTest extends \PHPUnit_Framework_TestCase
{
public function test__Construct()
{
$this->connection = $this->getMock('Doctrine\DBAL\Driver\Connection');
$mock = $this->getMockBuilder('Symfony\Bridge\Doctrine\HttpFoundation\DbalSessionStorage');
$mock = $this->getMockBuilder('Symfony\Bridge\Doctrine\HttpFoundation\DbalSessionHandler');
$mock->setConstructorArgs(array($this->connection));
$this->driver = $mock->getMock();
}

View File

@ -866,7 +866,7 @@ class RequestTest extends \PHPUnit_Framework_TestCase
public function testHasSession()
{
$request = new Request;
$request = new Request();
$this->assertFalse($request->hasSession());
$request->setSession(new Session(new MockArraySessionStorage()));
@ -875,10 +875,10 @@ class RequestTest extends \PHPUnit_Framework_TestCase
public function testHasPreviousSession()
{
$request = new Request;
$request = new Request();
$this->assertFalse($request->hasPreviousSession());
$request->cookies->set(session_name(), 'foo');
$request->cookies->set('MOCKSESSID', 'foo');
$this->assertFalse($request->hasPreviousSession());
$request->setSession(new Session(new MockArraySessionStorage()));
$this->assertTrue($request->hasPreviousSession());

View File

@ -1,10 +1,10 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage;
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage\Handler;
use Symfony\Component\HttpFoundation\Session\Storage\MemcacheSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcacheSessionHandler;
class MemcacheSessionStorageTest extends \PHPUnit_Framework_TestCase
class MemcacheSessionHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @var MemcacheSessionStorage
@ -20,7 +20,7 @@ class MemcacheSessionStorageTest extends \PHPUnit_Framework_TestCase
}
$this->memcache = $this->getMock('Memcache');
$this->storage = new MemcacheSessionStorage($this->memcache);
$this->storage = new MemcacheSessionHandler($this->memcache);
}
protected function tearDown()
@ -42,7 +42,7 @@ class MemcacheSessionStorageTest extends \PHPUnit_Framework_TestCase
{
$mock = $this->getMock('Memcache');
$storage = new MemcacheSessionStorage($mock, array(
$storage = new MemcacheSessionHandler($mock, array(
'serverpool' => array(
array('host' => '127.0.0.2'),
array('host' => '127.0.0.3',

View File

@ -1,13 +1,13 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage;
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage\Handler;
use Symfony\Component\HttpFoundation\Session\Storage\MemcachedSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler;
class MemcacheddSessionStorageTest extends \PHPUnit_Framework_TestCase
class MemcacheddSessionHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @var MemcachedSessionStorage
* @var MemcachedSessionHandler
*/
protected $storage;
@ -20,7 +20,7 @@ class MemcacheddSessionStorageTest extends \PHPUnit_Framework_TestCase
}
$this->memcached = $this->getMock('Memcached');
$this->storage = new MemcachedSessionStorage($this->memcached);
$this->storage = new MemcachedSessionHandler($this->memcached);
}
protected function tearDown()

View File

@ -0,0 +1,40 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage\Handler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
/**
* Test class for NativeFileSessionHandler.
*
* @author Drak <drak@zikula.org>
*
* @runTestsInSeparateProcesses
*/
class NativeFileSessionHandlerTest extends \PHPUnit_Framework_TestCase
{
public function testConstruct()
{
$storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeFileSessionHandler(sys_get_temp_dir()));
if (version_compare(phpversion(), '5.4.0', '<')) {
$this->assertEquals('files', $storage->getSaveHandler()->getSaveHandlerName());
$this->assertEquals('files', ini_get('session.save_handler'));
} else {
$this->assertEquals('files', $storage->getSaveHandler()->getSaveHandlerName());
$this->assertEquals('user', ini_get('session.save_handler'));
}
$this->assertEquals(sys_get_temp_dir(), ini_get('session.save_path'));
$this->assertEquals('TESTING', ini_get('session.name'));
}
public function testConstructDefault()
{
$path = ini_get('session.save_path');
$storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeFileSessionHandler());
$this->assertEquals($path, ini_get('session.save_path'));
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage\Handler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeMemcacheSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
/**
* Test class for NativeMemcacheSessionHandler.
*
* @author Drak <drak@zikula.org>
*
* @runTestsInSeparateProcesses
*/
class NativeMemcacheSessionHandlerTest extends \PHPUnit_Framework_TestCase
{
public function testSaveHandlers()
{
if (!extension_loaded('memcache')) {
$this->markTestSkipped('Skipped tests SQLite extension is not present');
}
$storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeMemcacheSessionHandler('tcp://127.0.0.1:11211?persistent=0'));
if (version_compare(phpversion(), '5.4.0', '<')) {
$this->assertEquals('memcache', $storage->getSaveHandler()->getSaveHandlerName());
$this->assertEquals('memcache', ini_get('session.save_handler'));
} else {
$this->assertEquals('memcache', $storage->getSaveHandler()->getSaveHandlerName());
$this->assertEquals('user', 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'));
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage\Handler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeMemcachedSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
/**
* Test class for NativeMemcachedSessionHandler.
*
* @author Drak <drak@zikula.org>
*
* @runTestsInSeparateProcesses
*/
class NativeMemcachedSessionHandlerTest 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 NativeSessionStorage(array('name' => 'TESTING'), new NativeMemcachedSessionHandler('127.0.0.1:11211'));
if (version_compare(phpversion(), '5.4.0', '<')) {
$this->assertEquals('memcached', $storage->getSaveHandler()->getSaveHandlerName());
$this->assertEquals('memcached', ini_get('session.save_handler'));
} else {
$this->assertEquals('memcached', $storage->getSaveHandler()->getSaveHandlerName());
$this->assertEquals('user', ini_get('session.save_handler'));
}
$this->assertEquals('127.0.0.1:11211', ini_get('session.save_path'));
$this->assertEquals('TESTING', ini_get('session.name'));
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage\Handler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSqliteSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
/**
* Test class for NativeSqliteSessionStorage.
*
* @author Drak <drak@zikula.org>
*
* @runTestsInSeparateProcesses
*/
class NativeSqliteSessionHandlerTest extends \PHPUnit_Framework_TestCase
{
public function testSaveHandlers()
{
if (!extension_loaded('sqlite')) {
$this->markTestSkipped('Skipped tests SQLite extension is not present');
}
$storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeSqliteSessionHandler(sys_get_temp_dir().'/sqlite.db'));
if (version_compare(phpversion(), '5.4.0', '<')) {
$this->assertEquals('sqlite', $storage->getSaveHandler()->getSaveHandlerName());
$this->assertEquals('sqlite', ini_get('session.save_handler'));
} else {
$this->assertEquals('sqlite', $storage->getSaveHandler()->getSaveHandlerName());
$this->assertEquals('user', 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'));
}
}

View File

@ -1,11 +1,12 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage;
use Symfony\Component\HttpFoundation\Session\Storage\NullSessionStorage;
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage\Handler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\HttpFoundation\Session\Session;
/**
* Test class for NullSessionStorage.
* Test class for NullSessionHandler.
*
* @author Drak <drak@zikula.org>
*
@ -15,14 +16,14 @@ class NullSessionStorageTest extends \PHPUnit_Framework_TestCase
{
public function testSaveHandlers()
{
$storage = new NullSessionStorage();
$storage = $this->getStorage();
$this->assertEquals('user', ini_get('session.save_handler'));
}
public function testSession()
{
session_id('nullsessionstorage');
$storage = new NullSessionStorage();
$storage = $this->getStorage();
$session = new Session($storage);
$this->assertNull($session->get('something'));
$session->set('something', 'unique');
@ -32,11 +33,16 @@ class NullSessionStorageTest extends \PHPUnit_Framework_TestCase
public function testNothingIsPersisted()
{
session_id('nullsessionstorage');
$storage = new NullSessionStorage();
$storage = $this->getStorage();
$session = new Session($storage);
$session->start();
$this->assertEquals('nullsessionstorage', $session->getId());
$this->assertNull($session->get('something'));
}
public function getStorage()
{
return new NativeSessionStorage(array(), new NullSessionHandler());
}
}

View File

@ -9,11 +9,11 @@
* file that was distributed with this source code.
*/
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage;
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage\Handler;
use Symfony\Component\HttpFoundation\Session\Storage\PdoSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
class PdoSessionStorageTest extends \PHPUnit_Framework_TestCase
class PdoSessionHandlerTest extends \PHPUnit_Framework_TestCase
{
private $pdo;
@ -26,16 +26,16 @@ class PdoSessionStorageTest extends \PHPUnit_Framework_TestCase
public function testMultipleInstances()
{
$storage1 = new PdoSessionStorage($this->pdo, array('db_table' => 'sessions'), array());
$storage1 = new PdoSessionHandler($this->pdo, array('db_table' => 'sessions'), array());
$storage1->write('foo', 'bar');
$storage2 = new PdoSessionStorage($this->pdo, array('db_table' => 'sessions'), array());
$storage2 = new PdoSessionHandler($this->pdo, array('db_table' => 'sessions'), array());
$this->assertEquals('bar', $storage2->read('foo'), 'values persist between instances');
}
public function testSessionDestroy()
{
$storage = new PdoSessionStorage($this->pdo, array('db_table' => 'sessions'), array());
$storage = new PdoSessionHandler($this->pdo, array('db_table' => 'sessions'), array());
$storage->write('foo', 'bar');
$this->assertEquals(1, count($this->pdo->query('SELECT * FROM sessions')->fetchAll()));
@ -46,7 +46,7 @@ class PdoSessionStorageTest extends \PHPUnit_Framework_TestCase
public function testSessionGC()
{
$storage = new PdoSessionStorage($this->pdo, array('db_table' => 'sessions'), array());
$storage = new PdoSessionHandler($this->pdo, array('db_table' => 'sessions'), array());
$storage->write('foo', 'bar');
$storage->write('baz', 'bar');

View File

@ -69,6 +69,7 @@ class MockFileSessionStorageTest extends \PHPUnit_Framework_TestCase
public function testSave()
{
$this->storage->start();
$id = $this->storage->getId();
$this->assertNotEquals('108', $this->storage->getBag('attributes')->get('new'));
$this->assertFalse($this->storage->getBag('flashes')->has('newkey'));
$this->storage->getBag('attributes')->set('new', '108');
@ -76,6 +77,7 @@ class MockFileSessionStorageTest extends \PHPUnit_Framework_TestCase
$this->storage->save();
$storage = $this->getStorage();
$storage->setId($id);
$storage->start();
$this->assertEquals('108', $storage->getBag('attributes')->get('new'));
$this->assertTrue($storage->getBag('flashes')->has('newkey'));
@ -90,13 +92,14 @@ class MockFileSessionStorageTest extends \PHPUnit_Framework_TestCase
$storage1->save();
$storage2 = $this->getStorage();
$storage2->setId($storage1->getId());
$storage2->start();
$this->assertEquals('bar', $storage2->getBag('attributes')->get('foo'), 'values persist between instances');
}
private function getStorage(array $options = array())
private function getStorage()
{
$storage = new MockFileSessionStorage($this->sessionDir, $options);
$storage = new MockFileSessionStorage($this->sessionDir);
$storage->registerBag(new FlashBag);
$storage->registerBag(new AttributeBag);

View File

@ -1,23 +0,0 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage;
use Symfony\Component\HttpFoundation\Session\Storage\NativeFileSessionStorage;
/**
* Test class for NativeFileSessionStorage.
*
* @author Drak <drak@zikula.org>
*
* @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'));
}
}

View File

@ -1,27 +0,0 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage;
use Symfony\Component\HttpFoundation\Session\Storage\NativeMemcacheSessionStorage;
/**
* Test class for NativeMemcacheSessionStorage.
*
* @author Drak <drak@zikula.org>
*
* @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'));
}
}

View File

@ -1,32 +0,0 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage;
use Symfony\Component\HttpFoundation\Session\Storage\NativeMemcachedSessionStorage;
/**
* Test class for NativeMemcachedSessionStorage.
*
* @author Drak <drak@zikula.org>
*
* @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'));
}
}

View File

@ -2,46 +2,12 @@
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage;
use Symfony\Component\HttpFoundation\Session\Storage\AbstractSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
/**
* Turn AbstractSessionStorage into something concrete because
* certain mocking features are broken in PHPUnit-Mock-Objects < 1.1.2
* @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/73
*/
class ConcreteSessionStorage extends AbstractSessionStorage
{
}
class CustomHandlerSessionStorage extends AbstractSessionStorage implements \SessionHandlerInterface
{
public function open($path, $id)
{
}
public function close()
{
}
public function read($id)
{
}
public function write($id, $data)
{
}
public function destroy($id)
{
}
public function gc($lifetime)
{
}
}
/**
* Test class for AbstractSessionStorage.
*
@ -51,14 +17,14 @@ class CustomHandlerSessionStorage extends AbstractSessionStorage implements \Ses
*
* @runTestsInSeparateProcesses
*/
class AbstractSessionStorageTest extends \PHPUnit_Framework_TestCase
class NativeSessionStorageTest extends \PHPUnit_Framework_TestCase
{
/**
* @return AbstractSessionStorage
* @return NativeSessionStorage
*/
protected function getStorage($options = array())
protected function getStorage(array $options = array())
{
$storage = new CustomHandlerSessionStorage($options);
$storage = new NativeSessionStorage($options);
$storage->registerBag(new AttributeBag);
return $storage;
@ -112,23 +78,11 @@ class AbstractSessionStorageTest extends \PHPUnit_Framework_TestCase
$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'));
}
public function testDefaultSessionCacheLimiter()
{
ini_set('session.cache_limiter', 'nocache');
$storage = new ConcreteSessionStorage();
$storage = new NativeSessionStorage();
$this->assertEquals('', ini_get('session.cache_limiter'));
}
@ -136,7 +90,7 @@ class AbstractSessionStorageTest extends \PHPUnit_Framework_TestCase
{
ini_set('session.cache_limiter', 'nocache');
$storage = new ConcreteSessionStorage(array('cache_limiter' => 'public'));
$storage = new NativeSessionStorage(array('cache_limiter' => 'public'));
$this->assertEquals('public', ini_get('session.cache_limiter'));
}
@ -160,4 +114,33 @@ class AbstractSessionStorageTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($options, $gco);
}
public function testSetSaveHandler()
{
$storage = $this->getStorage();
$storage->setSaveHandler(new \StdClass());
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy', $storage->getSaveHandler());
}
public function testSetSaveHandlerPHP53()
{
if (version_compare(phpversion(), '5.4.0', '>=')) {
$this->markTestSkipped('Test skipped, for PHP 5.3 only.');
}
$storage = $this->getStorage();
$storage->setSaveHandler(new NativeFileSessionHandler());
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy', $storage->getSaveHandler());
}
public function testSetSaveHandlerPHP54()
{
if (version_compare(phpversion(), '5.4.0', '<')) {
$this->markTestSkipped('Test skipped, for PHP 5.4+ only.');
}
$storage = $this->getStorage();
$storage->setSaveHandler(new NullSessionHandler());
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
}
}

View File

@ -1,28 +0,0 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSqliteSessionStorage;
/**
* Test class for NativeSqliteSessionStorage.
*
* @author Drak <drak@zikula.org>
*
* @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'));
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage\Proxy;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
// Note until PHPUnit_Mock_Objects 1.2 is released you cannot mock abstracts due to
// https://github.com/sebastianbergmann/phpunit-mock-objects/issues/73
class ConcreteProxy extends AbstractProxy
{
}
class ConcreteSessionHandlerInterfaceProxy extends AbstractProxy implements \SessionHandlerInterface
{
public function open($savePath, $sessionName)
{
}
public function close()
{
}
public function read($id)
{
}
public function write($id, $data)
{
}
public function destroy($id)
{
}
public function gc($maxlifetime)
{
}
}
/**
* Test class for AbstractProxy.
*
* @author Drak <drak@zikula.org>
*/
class AbstractProxyTest extends \PHPUnit_Framework_TestCase
{
/**
* @var AbstractProxy
*/
protected $proxy;
protected function setUp()
{
$this->proxy = new ConcreteProxy();
}
protected function tearDown()
{
$this->proxy = null;
}
public function testGetSaveHandlerName()
{
$this->assertNull($this->proxy->getSaveHandlerName());
}
public function testIsSessionHandlerInterface()
{
$this->assertFalse($this->proxy->isSessionHandlerInterface());
$sh = new ConcreteSessionHandlerInterfaceProxy();
$this->assertTrue($sh->isSessionHandlerInterface());
}
public function testIsWrapper()
{
$this->assertFalse($this->proxy->isWrapper());
}
public function testIsActive()
{
$this->assertFalse($this->proxy->isActive());
}
public function testSetActive()
{
$this->proxy->setActive(true);
$this->assertTrue($this->proxy->isActive());
$this->proxy->setActive(false);
$this->assertFalse($this->proxy->isActive());
}
/**
* @runInSeparateProcess
*/
public function testName()
{
$this->assertEquals(session_name(), $this->proxy->getName());
$this->proxy->setName('foo');
$this->assertEquals('foo', $this->proxy->getName());
$this->assertEquals(session_name(), $this->proxy->getName());
}
/**
* @expectedException \LogicException
*/
public function testNameException()
{
$this->proxy->setActive(true);
$this->proxy->setName('foo');
}
/**
* @runInSeparateProcess
*/
public function testId()
{
$this->assertEquals(session_id(), $this->proxy->getId());
$this->proxy->setId('foo');
$this->assertEquals('foo', $this->proxy->getId());
$this->assertEquals(session_id(), $this->proxy->getId());
}
/**
* @expectedException \LogicException
*/
public function testIdException()
{
$this->proxy->setActive(true);
$this->proxy->setId('foo');
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage\Proxy;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
/**
* Test class for NativeProxy.
*
* @author Drak <drak@zikula.org>
*/
class NativeProxyTest extends \PHPUnit_Framework_TestCase
{
public function testIsWrapper()
{
$proxy = new NativeProxy();
$this->assertFalse($proxy->isWrapper());
}
public function testGetSaveHandlerName()
{
$name = ini_get('session.save_handler');
$proxy = new NativeProxy();
$this->assertEquals($name, $proxy->getSaveHandlerName());
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace Symfony\Tests\Component\HttpFoundation\Session\Storage\Proxy;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
/**
* Tests for SessionHandlerProxy class.
*
* @author Drak <drak@zikula.org>
*
* @runTestsInSeparateProcesses
*/
class SessionHandlerProxyTest extends \PHPUnit_Framework_TestCase
{
/**
* @var PHPUnit_Framework_MockObject_Matcher
*/
private $mock;
/**
* @var SessionHandlerProxy
*/
private $proxy;
protected function setUp()
{
$this->mock = $this->getMock('SessionHandlerInterface');
$this->proxy = new SessionHandlerProxy($this->mock);
}
protected function tearDown()
{
$this->mock = null;
$this->proxy = null;
}
public function testOpen()
{
$this->mock->expects($this->once())
->method('open')
->will($this->returnValue(true));
$this->assertFalse($this->proxy->isActive());
$this->proxy->open('name', 'id');
$this->assertTrue($this->proxy->isActive());
}
public function testOpenFalse()
{
$this->mock->expects($this->once())
->method('open')
->will($this->returnValue(false));
$this->assertFalse($this->proxy->isActive());
$this->proxy->open('name', 'id');
$this->assertFalse($this->proxy->isActive());
}
public function testClose()
{
$this->mock->expects($this->once())
->method('close')
->will($this->returnValue(true));
$this->assertFalse($this->proxy->isActive());
$this->proxy->close();
$this->assertFalse($this->proxy->isActive());
}
public function testCloseFalse()
{
$this->mock->expects($this->once())
->method('close')
->will($this->returnValue(false));
$this->assertFalse($this->proxy->isActive());
$this->proxy->close();
$this->assertFalse($this->proxy->isActive());
}
public function testRead()
{
$this->mock->expects($this->once())
->method('read');
$this->proxy->read('id');
}
public function testWrite()
{
$this->mock->expects($this->once())
->method('write');
$this->proxy->write('id', 'data');
}
public function testDestroy()
{
$this->mock->expects($this->once())
->method('destroy');
$this->proxy->destroy('id');
}
public function testGc()
{
$this->mock->expects($this->once())
->method('gc');
$this->proxy->gc(86400);
}
}

View File

@ -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\Session', array('get'), array(), '', false);
$session = $this->getMock('Symfony\Component\HttpFoundation\Session\Session', array('get'), array(), '', true);
$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\Session', array('set', 'get'), array(), '', false);
$session = $this->getMock('Symfony\Component\HttpFoundation\Session\Session', array('set', 'get'), array(), '', true);
$session->expects($this->once())->method('set')->with('_locale', 'es');
$session->expects($this->once())->method('get')->with('_locale')->will($this->returnValue('es'));
$request->setSession($session);