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\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"
* 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 false;
}
+ if (headers_sent()) {
+ return false;
+ }
+
if (null !== $lifetime) {
ini_set('session.cookie_lifetime', $lifetime);
}
*/
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 custom error handler to catch a possible failure warning during session write
+ 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();
+ $_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();
+ if ($handler instanceof SessionHandlerProxy) {
+ $handler = $handler->getHandler();
+ }
+
+ trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', get_class($handler)), E_USER_WARNING);
+ }
$this->closed = true;
$this->started = false;
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 = new SessionHandlerProxy(new \SessionHandler());
+ $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
}
$this->saveHandler = $saveHandler;
- if ($this->saveHandler instanceof \SessionHandlerInterface) {
+ if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+ return;
+ }
+
+ 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);
}
}
* 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)
{