namespace Symfony\Component\HttpFoundation\Session\Storage;
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
-use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;
-use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
class NativeSessionStorage implements SessionStorageInterface
{
/**
- * Array of SessionBagInterface.
- *
* @var SessionBagInterface[]
*/
- protected $bags;
+ protected $bags = array();
/**
* @var bool
protected $closed = false;
/**
- * @var AbstractProxy
+ * @var AbstractProxy|\SessionHandlerInterface
*/
protected $saveHandler;
protected $metadataBag;
/**
- * Constructor.
- *
* Depending on how you want the storage driver to behave you probably
* want to override this constructor entirely.
*
* PHP starts to execute user-land code. Setting during runtime has no effect).
*
* cache_limiter, "" (use "0" to prevent headers from being sent entirely).
+ * cache_expire, "0"
* cookie_domain, ""
* cookie_httponly, ""
* cookie_lifetime, "0"
* gc_probability, "1"
* hash_bits_per_character, "4"
* hash_function, "0"
+ * lazy_write, "1"
* name, "PHPSESSID"
* referer_check, ""
* serialize_handler, "php"
* upload_progress.freq, "1%"
* upload_progress.min-freq, "1"
* url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
+ * sid_length, "32"
+ * sid_bits_per_character, "5"
+ * trans_sid_hosts, $_SERVER['HTTP_HOST']
+ * trans_sid_tags, "a=href,area=href,frame=src,form="
*
- * @param array $options Session configuration options
- * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler
- * @param MetadataBag $metaBag MetadataBag
+ * @param array $options Session configuration options
+ * @param \SessionHandlerInterface|null $handler
+ * @param MetadataBag $metaBag MetadataBag
*/
public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
{
- session_cache_limiter(''); // disable by default because it's managed by HeaderBag (if used)
- ini_set('session.use_cookies', 1);
+ $options += array(
+ 'cache_limiter' => '',
+ 'cache_expire' => 0,
+ 'use_cookies' => 1,
+ 'lazy_write' => 1,
+ );
session_register_shutdown();
/**
* Gets the save handler instance.
*
- * @return AbstractProxy
+ * @return AbstractProxy|\SessionHandlerInterface
*/
public function getSaveHandler()
{
return true;
}
- if (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status()) {
+ if (\PHP_SESSION_ACTIVE === session_status()) {
throw new \RuntimeException('Failed to start the session: already started by PHP.');
}
- if (\PHP_VERSION_ID < 50400 && !$this->closed && isset($_SESSION) && session_id()) {
- // not 100% fool-proof, but is the most reliable way to determine if a session is active in PHP 5.3
- throw new \RuntimeException('Failed to start the session: already started by PHP ($_SESSION is set).');
- }
-
if (ini_get('session.use_cookies') && headers_sent($file, $line)) {
throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
}
}
$this->loadSession();
- if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
- // This condition matches only PHP 5.3 with internal save handlers
- $this->saveHandler->setActive(true);
- }
return true;
}
public function regenerate($destroy = false, $lifetime = null)
{
// Cannot regenerate the session ID for non-active sessions.
- if (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE !== session_status()) {
+ if (\PHP_SESSION_ACTIVE !== session_status()) {
return false;
}
- // Check if session ID exists in PHP 5.3
- if (\PHP_VERSION_ID < 50400 && '' === session_id()) {
+ if (headers_sent()) {
return false;
}
*/
public function save()
{
- session_write_close();
+ $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 error handler to add information about the current save handler
+ $previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) {
+ if (E_WARNING === $type && 0 === strpos($msg, 'session_write_close():')) {
+ $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler;
+ $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler));
+ }
+
+ return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false;
+ });
- if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
- // This condition matches only PHP 5.3 with internal save handlers
- $this->saveHandler->setActive(false);
+ try {
+ session_write_close();
+ } finally {
+ restore_error_handler();
+ $_SESSION = $session;
}
$this->closed = true;
throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
}
- if ($this->saveHandler->isActive() && !$this->started) {
+ if (!$this->started && $this->saveHandler->isActive()) {
$this->loadSession();
} elseif (!$this->started) {
$this->start();
return $this->bags[$name];
}
- /**
- * Sets the MetadataBag.
- *
- * @param MetadataBag $metaBag
- */
public function setMetadataBag(MetadataBag $metaBag = null)
{
if (null === $metaBag) {
*/
public function setOptions(array $options)
{
+ if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+ return;
+ }
+
$validOptions = array_flip(array(
- 'cache_limiter', 'cookie_domain', 'cookie_httponly',
+ 'cache_expire', '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',
+ 'hash_function', 'lazy_write', 'name', 'referer_check',
'serialize_handler', 'use_strict_mode', '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',
+ 'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags',
+ 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags',
));
foreach ($options as $key => $value) {
if (isset($validOptions[$key])) {
- ini_set('session.'.$key, $value);
+ ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value);
}
}
}
* ini_set('session.save_handler', 'files');
* ini_set('session.save_path', '/tmp');
*
- * or pass in a NativeSessionHandler instance which configures session.save_handler in the
+ * or pass in a \SessionHandler instance which configures session.save_handler in the
* constructor, for a template see NativeFileSessionHandler or use handlers in
* composer package drak/native-session
*
* @see http://php.net/sessionhandler
* @see http://github.com/drak/NativeSession
*
- * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $saveHandler
+ * @param \SessionHandlerInterface|null $saveHandler
*
* @throws \InvalidArgumentException
*/
public function setSaveHandler($saveHandler = null)
{
if (!$saveHandler instanceof AbstractProxy &&
- !$saveHandler instanceof NativeSessionHandler &&
!$saveHandler instanceof \SessionHandlerInterface &&
null !== $saveHandler) {
- throw new \InvalidArgumentException('Must be instance of AbstractProxy or NativeSessionHandler; implement \SessionHandlerInterface; or be null.');
+ throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
}
// Wrap $saveHandler in proxy and prevent double wrapping of proxy
if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
$saveHandler = new SessionHandlerProxy($saveHandler);
} elseif (!$saveHandler instanceof AbstractProxy) {
- $saveHandler = \PHP_VERSION_ID >= 50400 ?
- new SessionHandlerProxy(new \SessionHandler()) : new NativeProxy();
+ $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
}
$this->saveHandler = $saveHandler;
- if ($this->saveHandler instanceof \SessionHandlerInterface) {
- if (\PHP_VERSION_ID >= 50400) {
- 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')
- );
- }
+ if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+ return;
+ }
+
+ if ($this->saveHandler instanceof SessionHandlerProxy) {
+ session_set_save_handler($this->saveHandler, false);
}
}
* are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()).
* PHP takes the return value from the read() handler, unserializes it
* and populates $_SESSION with the result automatically.
- *
- * @param array|null $session
*/
protected function loadSession(array &$session = null)
{