diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
index e6e83d40b5..80f54afd20 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
@@ -301,6 +301,7 @@ class ConfigurationTest extends TestCase
'gc_probability' => 1,
'save_path' => '%kernel.cache_dir%/sessions',
'metadata_update_threshold' => '0',
+ 'use_strict_mode' => true,
),
'request' => array(
'enabled' => false,
diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md
index 83a3c8e32e..ee5b6cecf2 100644
--- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md
+++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md
@@ -4,8 +4,9 @@ CHANGELOG
3.4.0
-----
- * deprecated the `NativeSessionHandler` class,
- * deprecated the `AbstractProxy`, `NativeProxy` and `SessionHandlerProxy` classes,
+ * implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new
+ `AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper
+ * deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes
* deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()`
* deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead
* deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php
new file mode 100644
index 0000000000..c20a23b20e
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php
@@ -0,0 +1,165 @@
+
+ *
+ * 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;
+
+/**
+ * This abstract session handler provides a generic implementation
+ * of the PHP 7.0 SessionUpdateTimestampHandlerInterface,
+ * enabling strict and lazy session handling.
+ *
+ * @author Nicolas Grekas
+ */
+abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
+{
+ private $sessionName;
+ private $prefetchId;
+ private $prefetchData;
+ private $newSessionId;
+ private $igbinaryEmptyData;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ $this->sessionName = $sessionName;
+
+ return true;
+ }
+
+ /**
+ * @param string $sessionId
+ *
+ * @return string
+ */
+ abstract protected function doRead($sessionId);
+
+ /**
+ * @param string $sessionId
+ * @param string $data
+ *
+ * @return bool
+ */
+ abstract protected function doWrite($sessionId, $data);
+
+ /**
+ * @param string $sessionId
+ *
+ * @return bool
+ */
+ abstract protected function doDestroy($sessionId);
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateId($sessionId)
+ {
+ $this->prefetchData = $this->read($sessionId);
+ $this->prefetchId = $sessionId;
+
+ return '' !== $this->prefetchData;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ if (null !== $this->prefetchId) {
+ $prefetchId = $this->prefetchId;
+ $prefetchData = $this->prefetchData;
+ $this->prefetchId = $this->prefetchData = null;
+
+ if ($prefetchId === $sessionId || '' === $prefetchData) {
+ $this->newSessionId = '' === $prefetchData ? $sessionId : null;
+
+ return $prefetchData;
+ }
+ }
+
+ $data = $this->doRead($sessionId);
+ $this->newSessionId = '' === $data ? $sessionId : null;
+ if (\PHP_VERSION_ID < 70000) {
+ $this->prefetchData = $data;
+ }
+
+ return $data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $data)
+ {
+ if (\PHP_VERSION_ID < 70000 && $this->prefetchData) {
+ $readData = $this->prefetchData;
+ $this->prefetchData = null;
+
+ if ($readData === $data) {
+ return $this->updateTimestamp($sessionId, $data);
+ }
+ }
+ if (null === $this->igbinaryEmptyData) {
+ // see https://github.com/igbinary/igbinary/issues/146
+ $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize(array()) : '';
+ }
+ if ('' === $data || $this->igbinaryEmptyData === $data) {
+ return $this->destroy($sessionId);
+ }
+ $this->newSessionId = null;
+
+ return $this->doWrite($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ if (\PHP_VERSION_ID < 70000) {
+ $this->prefetchData = null;
+ }
+ if (!headers_sent() && ini_get('session.use_cookies')) {
+ if (!$this->sessionName) {
+ throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', get_class($this)));
+ }
+ $sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
+ $sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
+ $sessionCookieFound = false;
+ $otherCookies = array();
+ foreach (headers_list() as $h) {
+ if (0 !== stripos($h, 'Set-Cookie:')) {
+ continue;
+ }
+ if (11 === strpos($h, $sessionCookie, 11)) {
+ $sessionCookieFound = true;
+
+ if (11 !== strpos($h, $sessionCookieWithId, 11)) {
+ $otherCookies[] = $h;
+ }
+ } else {
+ $otherCookies[] = $h;
+ }
+ }
+ if ($sessionCookieFound) {
+ header_remove('Set-Cookie');
+ foreach ($otherCookies as $h) {
+ header('Set-Cookie:'.$h, false);
+ }
+ } else {
+ setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly'));
+ }
+ }
+
+ return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
+ }
+}
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php
index 3bbde5420d..a31642cc83 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php
@@ -19,7 +19,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
*
* @author Drak
*/
-class MemcachedSessionHandler implements \SessionHandlerInterface
+class MemcachedSessionHandler extends AbstractSessionHandler
{
/**
* @var \Memcached Memcached driver
@@ -39,7 +39,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
/**
* List of available options:
* * prefix: The prefix to use for the memcached keys in order to avoid collision
- * * expiretime: The time to live in seconds
+ * * expiretime: The time to live in seconds.
*
* @param \Memcached $memcached A \Memcached instance
* @param array $options An associative array of Memcached options
@@ -60,14 +60,6 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
$this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s';
}
- /**
- * {@inheritdoc}
- */
- public function open($savePath, $sessionName)
- {
- return true;
- }
-
/**
* {@inheritdoc}
*/
@@ -79,7 +71,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
- public function read($sessionId)
+ protected function doRead($sessionId)
{
return $this->memcached->get($this->prefix.$sessionId) ?: '';
}
@@ -87,7 +79,15 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
- public function write($sessionId, $data)
+ public function updateTimestamp($sessionId, $data)
+ {
+ return $this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
{
return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl);
}
@@ -95,7 +95,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
- public function destroy($sessionId)
+ protected function doDestroy($sessionId)
{
$result = $this->memcached->delete($this->prefix.$sessionId);
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php
index d532fb92c6..7d770421c5 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php
@@ -19,7 +19,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
* @see https://packagist.org/packages/mongodb/mongodb
* @see http://php.net/manual/en/set.mongodb.php
*/
-class MongoDbSessionHandler implements \SessionHandlerInterface
+class MongoDbSessionHandler extends AbstractSessionHandler
{
/**
* @var \Mongo|\MongoClient|\MongoDB\Client
@@ -43,7 +43,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
* * id_field: The field name for storing the session id [default: _id]
* * data_field: The field name for storing the session data [default: data]
* * time_field: The field name for storing the timestamp [default: time]
- * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]
+ * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at].
*
* It is strongly recommended to put an index on the `expiry_field` for
* garbage-collection. Alternatively it's possible to automatically expire
@@ -92,14 +92,6 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
), $options);
}
- /**
- * {@inheritdoc}
- */
- public function open($savePath, $sessionName)
- {
- return true;
- }
-
/**
* {@inheritdoc}
*/
@@ -111,7 +103,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
- public function destroy($sessionId)
+ protected function doDestroy($sessionId)
{
$methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
@@ -139,7 +131,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
- public function write($sessionId, $data)
+ protected function doWrite($sessionId, $data)
{
$expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
@@ -171,7 +163,34 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
- public function read($sessionId)
+ public function updateTimestamp($sessionId, $data)
+ {
+ $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
+
+ if ($this->mongo instanceof \MongoDB\Client) {
+ $methodName = 'updateOne';
+ $options = array();
+ } else {
+ $methodName = 'update';
+ $options = array('multiple' => false);
+ }
+
+ $this->getCollection()->$methodName(
+ array($this->options['id_field'] => $sessionId),
+ array('$set' => array(
+ $this->options['time_field'] => $this->createDateTime(),
+ $this->options['expiry_field'] => $expiry,
+ )),
+ $options
+ );
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doRead($sessionId)
{
$dbData = $this->getCollection()->findOne(array(
$this->options['id_field'] => $sessionId,
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php
index daa7dbd15b..9ea4629ca1 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php
@@ -11,12 +11,14 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
-@trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since version 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', E_USER_DEPRECATED);
-
/**
* @deprecated since version 3.4, to be removed in 4.0. Use \SessionHandler instead.
* @see http://php.net/sessionhandler
*/
class NativeSessionHandler extends \SessionHandler
{
+ public function __construct()
+ {
+ @trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since version 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', E_USER_DEPRECATED);
+ }
}
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php
index 981d96d93a..8d193155b0 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php
@@ -16,16 +16,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
*
* @author Drak
*/
-class NullSessionHandler implements \SessionHandlerInterface
+class NullSessionHandler extends AbstractSessionHandler
{
- /**
- * {@inheritdoc}
- */
- public function open($savePath, $sessionName)
- {
- return true;
- }
-
/**
* {@inheritdoc}
*/
@@ -37,15 +29,7 @@ class NullSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
- public function read($sessionId)
- {
- return '';
- }
-
- /**
- * {@inheritdoc}
- */
- public function write($sessionId, $data)
+ public function validateId($sessionId)
{
return true;
}
@@ -53,7 +37,31 @@ class NullSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
- public function destroy($sessionId)
+ protected function doRead($sessionId)
+ {
+ return '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDestroy($sessionId)
{
return true;
}
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php
index 5cdac63939..19bf6e9bca 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php
@@ -38,7 +38,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
* @author Michael Williams
* @author Tobias Schultze
*/
-class PdoSessionHandler implements \SessionHandlerInterface
+class PdoSessionHandler extends AbstractSessionHandler
{
/**
* No locking is done. This means sessions are prone to loss of data due to
@@ -260,11 +260,13 @@ class PdoSessionHandler implements \SessionHandlerInterface
*/
public function open($savePath, $sessionName)
{
+ $this->sessionExpired = false;
+
if (null === $this->pdo) {
$this->connect($this->dsn ?: $savePath);
}
- return true;
+ return parent::open($savePath, $sessionName);
}
/**
@@ -273,7 +275,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
public function read($sessionId)
{
try {
- return $this->doRead($sessionId);
+ return parent::read($sessionId);
} catch (\PDOException $e) {
$this->rollback();
@@ -296,7 +298,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
- public function destroy($sessionId)
+ protected function doDestroy($sessionId)
{
// delete the record associated with this id
$sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
@@ -317,7 +319,7 @@ class PdoSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
- public function write($sessionId, $data)
+ protected function doWrite($sessionId, $data)
{
$maxlifetime = (int) ini_get('session.gc_maxlifetime');
@@ -372,6 +374,30 @@ class PdoSessionHandler implements \SessionHandlerInterface
return true;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ $maxlifetime = (int) ini_get('session.gc_maxlifetime');
+
+ try {
+ $updateStmt = $this->pdo->prepare(
+ "UPDATE $this->table SET $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"
+ );
+ $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+ $updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+ $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
+ $updateStmt->execute();
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+
+ return true;
+ }
+
/**
* {@inheritdoc}
*/
@@ -491,10 +517,8 @@ class PdoSessionHandler implements \SessionHandlerInterface
*
* @return string The session data
*/
- private function doRead($sessionId)
+ protected function doRead($sessionId)
{
- $this->sessionExpired = false;
-
if (self::LOCK_ADVISORY === $this->lockMode) {
$this->unlockStatements[] = $this->doAdvisoryLock($sessionId);
}
@@ -517,7 +541,9 @@ class PdoSessionHandler implements \SessionHandlerInterface
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
}
- if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
+ if (!ini_get('session.use_strict_mode') && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
+ // In strict mode, session fixation is not possible: new sessions always start with a unique
+ // random id, so that concurrency is not possible and this code path can be skipped.
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
// until other connections to the session are committed.
try {
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php
new file mode 100644
index 0000000000..1bad0641e8
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php
@@ -0,0 +1,89 @@
+
+ *
+ * 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 basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`.
+ *
+ * @author Nicolas Grekas
+ */
+class StrictSessionHandler extends AbstractSessionHandler
+{
+ private $handler;
+
+ public function __construct(\SessionHandlerInterface $handler)
+ {
+ if ($handler instanceof \SessionUpdateTimestampHandlerInterface) {
+ throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_class($handler), self::class));
+ }
+
+ $this->handler = $handler;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ parent::open($savePath, $sessionName);
+
+ return $this->handler->open($savePath, $sessionName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doRead($sessionId)
+ {
+ return $this->handler->read($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ return $this->write($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
+ {
+ return $this->handler->write($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDestroy($sessionId)
+ {
+ return $this->handler->destroy($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return $this->handler->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ return $this->handler->gc($maxlifetime);
+ }
+}
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php
index d49c36cae5..638a633076 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php
@@ -11,10 +11,14 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+@trigger_error(sprintf('The %s class is deprecated since version 3.4 and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.', WriteCheckSessionHandler::class), E_USER_DEPRECATED);
+
/**
* Wraps another SessionHandlerInterface to only write the session when it has been modified.
*
* @author Adrien Brault
+ *
+ * @deprecated since version 3.4, to be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.
*/
class WriteCheckSessionHandler implements \SessionHandlerInterface
{
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php
index 623d38721b..092d21830d 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php
@@ -11,8 +11,8 @@
namespace Symfony\Component\HttpFoundation\Session\Storage;
-use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
@@ -26,7 +26,7 @@ class NativeSessionStorage implements SessionStorageInterface
/**
* @var SessionBagInterface[]
*/
- protected $bags;
+ protected $bags = array();
/**
* @var bool
@@ -100,9 +100,9 @@ class NativeSessionStorage implements SessionStorageInterface
public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
{
$options += array(
- // disable by default because it's managed by HeaderBag (if used)
- 'cache_limiter' => '',
+ 'cache_limiter' => 'private_no_expire',
'use_cookies' => 1,
+ 'lazy_write' => 1,
);
session_register_shutdown();
@@ -217,15 +217,31 @@ class NativeSessionStorage implements SessionStorageInterface
*/
public function save()
{
+ $session = $_SESSION;
+
+ foreach ($this->bags as $bag) {
+ if (empty($_SESSION[$key = $bag->getStorageKey()])) {
+ unset($_SESSION[$key]);
+ }
+ }
+ if (array($key = $this->metadataBag->getStorageKey()) === array_keys($_SESSION)) {
+ unset($_SESSION[$key]);
+ }
+
// Register custom error handler to catch a possible failure warning during session write
- set_error_handler(function ($errno, $errstr, $errfile, $errline, $errcontext) {
- throw new ContextErrorException($errstr, $errno, E_WARNING, $errfile, $errline, $errcontext);
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) {
+ throw new \ErrorException($errstr, $errno, E_WARNING, $errfile, $errline);
}, E_WARNING);
try {
+ $e = null;
session_write_close();
+ } catch (\ErrorException $e) {
+ } finally {
restore_error_handler();
- } catch (ContextErrorException $e) {
+ $_SESSION = $session;
+ }
+ if (null !== $e) {
// The default PHP error message is not very helpful, as it does not give any information on the current save handler.
// Therefore, we catch this error and trigger a warning with a better error message
$handler = $this->getSaveHandler();
@@ -233,7 +249,6 @@ class NativeSessionStorage implements SessionStorageInterface
$handler = $handler->getHandler();
}
- restore_error_handler();
trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', get_class($handler)), E_USER_WARNING);
}
@@ -386,13 +401,6 @@ class NativeSessionStorage implements SessionStorageInterface
throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
}
- if ($saveHandler instanceof AbstractProxy) {
- @trigger_error(
- 'Using session save handlers that are instances of AbstractProxy is deprecated since version 3.4 and will be removed in 4.0.',
- E_USER_DEPRECATED
- );
- }
-
if (headers_sent($file, $line)) {
throw new \RuntimeException(sprintf('Failed to set the session handler because headers have already been sent by "%s" at line %d.', $file, $line));
}
@@ -401,11 +409,13 @@ class NativeSessionStorage implements SessionStorageInterface
if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
$saveHandler = new SessionHandlerProxy($saveHandler);
} elseif (!$saveHandler instanceof AbstractProxy) {
- $saveHandler = new SessionHandlerProxy(new \SessionHandler());
+ $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
}
$this->saveHandler = $saveHandler;
- if ($this->saveHandler instanceof \SessionHandlerInterface) {
+ if ($this->saveHandler instanceof SessionHandlerProxy) {
+ session_set_save_handler($this->saveHandler->getHandler(), false);
+ } elseif ($this->saveHandler instanceof \SessionHandlerInterface) {
session_set_save_handler($this->saveHandler, false);
}
}
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php
index c1c8b9b1f7..09c92483c7 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php
@@ -11,11 +11,7 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
-@trigger_error('The '.__NAMESPACE__.'\AbstractProxy class is deprecated since version 3.4 and will be removed in 4.0. Use your session handler implementation directly.', E_USER_DEPRECATED);
-
/**
- * @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly.
- *
* @author Drak
*/
abstract class AbstractProxy
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php
index d6adef82db..359bb877b5 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php
@@ -11,11 +11,7 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
-@trigger_error('The '.__NAMESPACE__.'\SessionHandlerProxy class is deprecated since version 3.4 and will be removed in 4.0. Use your session handler implementation directly.', E_USER_DEPRECATED);
-
/**
- * @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly.
- *
* @author Drak
*/
class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
new file mode 100644
index 0000000000..3ac081e388
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @requires PHP 7.0
+ */
+class AbstractSessionHandlerTest extends TestCase
+{
+ private static $server;
+
+ public static function setUpBeforeClass()
+ {
+ $spec = array(
+ 1 => array('file', '/dev/null', 'w'),
+ 2 => array('file', '/dev/null', 'w'),
+ );
+ if (!self::$server = @proc_open('exec php -S localhost:8053', $spec, $pipes, __DIR__.'/Fixtures')) {
+ self::markTestSkipped('PHP server unable to start.');
+ }
+ sleep(1);
+ }
+
+ public static function tearDownAfterClass()
+ {
+ if (self::$server) {
+ proc_terminate(self::$server);
+ proc_close(self::$server);
+ }
+ }
+
+ /**
+ * @dataProvider provideSession
+ */
+ public function testSession($fixture)
+ {
+ $context = array('http' => array('header' => "Cookie: sid=123abc\r\n"));
+ $context = stream_context_create($context);
+ $result = file_get_contents(sprintf('http://localhost:8053/%s.php', $fixture), false, $context);
+
+ $this->assertStringEqualsFile(__DIR__.sprintf('/Fixtures/%s.expected', $fixture), $result);
+ }
+
+ public function provideSession()
+ {
+ foreach (glob(__DIR__.'/Fixtures/*.php') as $file) {
+ yield array(pathinfo($file, PATHINFO_FILENAME));
+ }
+ }
+}
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc
new file mode 100644
index 0000000000..5c183acfff
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc
@@ -0,0 +1,152 @@
+data = $data;
+ }
+
+ public function open($path, $name)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::open($path, $name);
+ }
+
+ public function validateId($sessionId)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::validateId($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::read($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ echo __FUNCTION__, "\n";
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $data)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::write($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::destroy($sessionId);
+ }
+
+ public function close()
+ {
+ echo __FUNCTION__, "\n";
+
+ return true;
+ }
+
+ public function gc($maxLifetime)
+ {
+ echo __FUNCTION__, "\n";
+
+ return true;
+ }
+
+ protected function doRead($sessionId)
+ {
+ echo __FUNCTION__.': ', $this->data, "\n";
+
+ return $this->data;
+ }
+
+ protected function doWrite($sessionId, $data)
+ {
+ echo __FUNCTION__.': ', $data, "\n";
+
+ return true;
+ }
+
+ protected function doDestroy($sessionId)
+ {
+ echo __FUNCTION__, "\n";
+
+ return true;
+ }
+}
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected
new file mode 100644
index 0000000000..1720bf0558
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected
@@ -0,0 +1,17 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+
+write
+destroy
+doDestroy
+close
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: private, max-age=10800
+ [2] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly
+)
+shutdown
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php
new file mode 100644
index 0000000000..3cfc1250ad
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php
@@ -0,0 +1,8 @@
+ Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: private, max-age=10800
+)
+shutdown
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/read_only.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/read_only.php
new file mode 100644
index 0000000000..3e62fb9ecb
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/read_only.php
@@ -0,0 +1,8 @@
+ Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: private, max-age=10800
+ [2] => Set-Cookie: sid=random_session_id; path=/; secure; HttpOnly
+)
+shutdown
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php
new file mode 100644
index 0000000000..a0f635c871
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php
@@ -0,0 +1,10 @@
+ bar
+)
+$_SESSION is not empty
+write
+destroy
+close
+$_SESSION is not empty
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: private, max-age=10800
+)
+shutdown
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php
new file mode 100644
index 0000000000..96dca3c2c0
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php
@@ -0,0 +1,24 @@
+setSaveHandler(new TestSessionHandler());
+$flash = new FlashBag();
+$storage->registerBag($flash);
+$storage->start();
+
+$flash->add('foo', 'bar');
+
+print_r($flash->get('foo'));
+echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty';
+echo "\n";
+
+$storage->save();
+
+echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty';
+
+ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); });
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected
new file mode 100644
index 0000000000..47ae4da824
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected
@@ -0,0 +1,15 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+
+updateTimestamp
+close
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: private, max-age=10800
+ [2] => Set-Cookie: abc=def
+)
+shutdown
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php
new file mode 100644
index 0000000000..ffb5b20a37
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php
@@ -0,0 +1,8 @@
+markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
}
+ if (ini_get('session.use_strict_mode')) {
+ $this->markTestSkipped('Strict mode needs no locking for new sessions.');
+ }
$pdo = new MockPdo('pgsql');
$selectStmt = $this->getMockBuilder('PDOStatement')->getMock();
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php
new file mode 100644
index 0000000000..9d2c1949f3
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php
@@ -0,0 +1,189 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
+
+class StrictSessionHandlerTest extends TestCase
+{
+ public function testOpen()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('open')
+ ->with('path', 'name')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertInstanceof('SessionUpdateTimestampHandlerInterface', $proxy);
+ $this->assertInstanceof(AbstractSessionHandler::class, $proxy);
+ $this->assertTrue($proxy->open('path', 'name'));
+ }
+
+ public function testCloseSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('close')
+ ->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->close());
+ }
+
+ public function testValidateIdOK()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('data');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->validateId('id'));
+ }
+
+ public function testValidateIdKO()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertFalse($proxy->validateId('id'));
+ }
+
+ public function testRead()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('data');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertSame('data', $proxy->read('id'));
+ }
+
+ public function testReadWithValidateIdOK()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('data');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->validateId('id'));
+ $this->assertSame('data', $proxy->read('id'));
+ }
+
+ public function testReadWithValidateIdMismatch()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->exactly(2))->method('read')
+ ->withConsecutive(array('id1'), array('id2'))
+ ->will($this->onConsecutiveCalls('data1', 'data2'));
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->validateId('id1'));
+ $this->assertSame('data2', $proxy->read('id2'));
+ }
+
+ public function testUpdateTimestamp()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('write')
+ ->with('id', 'data')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->updateTimestamp('id', 'data'));
+ }
+
+ public function testWrite()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('write')
+ ->with('id', 'data')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->write('id', 'data'));
+ }
+
+ public function testWriteEmptyNewSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('');
+ $handler->expects($this->never())->method('write');
+ $handler->expects($this->never())->method('destroy');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertFalse($proxy->validateId('id'));
+ $this->assertSame('', $proxy->read('id'));
+ $this->assertTrue($proxy->write('id', ''));
+ }
+
+ public function testWriteEmptyExistingSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('data');
+ $handler->expects($this->never())->method('write');
+ $handler->expects($this->once())->method('destroy')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertSame('data', $proxy->read('id'));
+ $this->assertTrue($proxy->write('id', ''));
+ }
+
+ public function testDestroy()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('destroy')
+ ->with('id')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->destroy('id'));
+ }
+
+ public function testDestroyNewSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('');
+ $handler->expects($this->never())->method('destroy');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertSame('', $proxy->read('id'));
+ $this->assertTrue($proxy->destroy('id'));
+ }
+
+ public function testDestroyNonEmptyNewSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('');
+ $handler->expects($this->once())->method('write')
+ ->with('id', 'data')->willReturn(true);
+ $handler->expects($this->once())->method('destroy')
+ ->with('id')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertSame('', $proxy->read('id'));
+ $this->assertTrue($proxy->write('id', 'data'));
+ $this->assertTrue($proxy->destroy('id'));
+ }
+
+ public function testGc()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('gc')
+ ->with(123)->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->gc(123));
+ }
+}
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php
index 5e41a4743e..898a7d11a5 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php
@@ -16,6 +16,8 @@ use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHa
/**
* @author Adrien Brault
+ *
+ * @group legacy
*/
class WriteCheckSessionHandlerTest extends TestCase
{
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php
index 818c63a9d2..93de175fd3 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php
@@ -14,7 +14,7 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
-use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
@@ -152,7 +152,7 @@ class NativeSessionStorageTest extends TestCase
$this->iniSet('session.cache_limiter', 'nocache');
$storage = new NativeSessionStorage();
- $this->assertEquals('', ini_get('session.cache_limiter'));
+ $this->assertEquals('private_no_expire', ini_get('session.cache_limiter'));
}
public function testExplicitSessionCacheLimiter()
@@ -201,9 +201,9 @@ class NativeSessionStorageTest extends TestCase
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
$storage->setSaveHandler(null);
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
- $storage->setSaveHandler(new SessionHandlerProxy(new NativeSessionHandler()));
+ $storage->setSaveHandler(new SessionHandlerProxy(new NativeFileSessionHandler()));
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
- $storage->setSaveHandler(new NativeSessionHandler());
+ $storage->setSaveHandler(new NativeFileSessionHandler());
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
$storage->setSaveHandler(new SessionHandlerProxy(new NullSessionHandler()));
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php
index f2be722c8f..cbb291f19f 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php
@@ -18,8 +18,6 @@ use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
/**
* Test class for AbstractProxy.
*
- * @group legacy
- *
* @author Drak
*/
class AbstractProxyTest extends TestCase
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php
index fdd1dae254..682825356a 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php
@@ -21,7 +21,6 @@ use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
- * @group legacy
*/
class SessionHandlerProxyTest extends TestCase
{
diff --git a/src/Symfony/Component/HttpFoundation/composer.json b/src/Symfony/Component/HttpFoundation/composer.json
index 4f13511e9a..f6c6f2e623 100644
--- a/src/Symfony/Component/HttpFoundation/composer.json
+++ b/src/Symfony/Component/HttpFoundation/composer.json
@@ -17,7 +17,8 @@
],
"require": {
"php": "^5.5.9|>=7.0.8",
- "symfony/polyfill-mbstring": "~1.1"
+ "symfony/polyfill-mbstring": "~1.1",
+ "symfony/polyfill-php70": "~1.6"
},
"require-dev": {
"symfony/expression-language": "~2.8|~3.0|~4.0"
diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php
index 5ce2774114..d441ba6ed3 100644
--- a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php
+++ b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php
@@ -97,12 +97,18 @@ class NativeSessionTokenStorage implements TokenStorageInterface
$this->startSession();
}
- $token = isset($_SESSION[$this->namespace][$tokenId])
- ? (string) $_SESSION[$this->namespace][$tokenId]
- : null;
+ if (!isset($_SESSION[$this->namespace][$tokenId])) {
+ return;
+ }
+
+ $token = (string) $_SESSION[$this->namespace][$tokenId];
unset($_SESSION[$this->namespace][$tokenId]);
+ if (!$_SESSION[$this->namespace]) {
+ unset($_SESSION[$this->namespace]);
+ }
+
return $token;
}