# NativeSessionStorage

class NativeSessionStorage implements SessionStorageInterface

This provides a base class for session attribute storage.

## Properties

`protected SessionBagInterface[]` | `$bags` |  |
`protected bool` | `$started` |  |
`protected bool` | `$closed` |  |
`protected AbstractProxy|SessionHandlerInterface` | `$saveHandler` |  |
`protected MetadataBag` | `$metadataBag` |  |

## Methods

`public` | `` | `__construct(array $options = array(), SessionHandlerInterface|null $handler = null, MetadataBag $metaBag = null)` | Depending on how you want the storage driver to behave you probably want to override this constructor entirely. |
`public` | `AbstractProxy|SessionHandlerInterface` | `getSaveHandler()` | Gets the save handler instance. |
`public` | `bool` | `start()` | Starts the session. |
`public` | `string` | `getId()` | Returns the session ID. |
`public` | `` | `setId(string $id)` | Sets the session ID. |
`public` | `mixed` | `getName()` | Returns the session name. |
`public` | `` | `setName(string $name)` | Sets the session name. |
`public` | `bool` | `regenerate(bool $destroy = false, int $lifetime = null)` | Regenerates id that represents this storage. |
`public` | `` | `save()` | Force the session to be saved and closed. |
`public` | `` | `clear()` | Clear all session data in memory. |
`public` | `` | `registerBag(SessionBagInterface $bag)` | Registers a SessionBagInterface for use. |
`public` | `SessionBagInterface` | `getBag(string $name)` | Gets a SessionBagInterface by name. |
`public` | `` | `setMetadataBag(MetadataBag $metaBag = null)` | No description |
`public` | `MetadataBag` | `getMetadataBag()` | Gets the MetadataBag. |
`public` | `bool` | `isStarted()` | Checks if the session is started. |
`public` | `` | `setOptions(array $options)` | Sets session.* ini variables. |
`public` | `` | `setSaveHandler(SessionHandlerInterface|null $saveHandler = null)` | Registers session save handler as a PHP session handler. |
`protected` | `` | `loadSession(array $session = null)` | Load the session with attributes. |

## Details

at line 97

### `__construct()`

`public __construct(array $options = array(), SessionHandlerInterface|null $handler = null, MetadataBag $metaBag = null)`

Depending on how you want the storage driver to behave you probably want to override this constructor entirely.

List of options for $options array with their defaults.

#### Parameters

`array` | `$options` | Session configuration options
`SessionHandlerInterface|null` | `$handler` |
`MetadataBag` | `$metaBag` | MetadataBag

#### See also

http://php.net/session.configuration | for options but we omit 'session.' from the beginning of the keys for convenience. ("auto_start", is not supported as it tells PHP to start a session before 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" cookie_path, "/" cookie_secure, "" gc_divisor, "100" gc_maxlifetime, "1440" gc_probability, "1" lazy_write, "1" name, "PHPSESSID" referer_check, "" serialize_handler, "php" use_strict_mode, "0" use_cookies, "1" use_only_cookies, "1" use_trans_sid, "0" upload_progress.enabled, "1" upload_progress.cleanup, "1" upload_progress.prefix, "upload_progress_" upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS" upload_progress.freq, "1%" upload_progress.min-freq, "1" url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset=" sid_length, "32" sid_bits_per_character, "5" trans_sid_hosts, $_SERVER['HTTP_HOST'] trans_sid_tags, "a=href,area=href,frame=src,form="

at line 119

### `getSaveHandler()`

`public AbstractProxy|SessionHandlerInterface getSaveHandler()`

Gets the save handler instance.

#### Return Value

`AbstractProxy|SessionHandlerInterface` |

at line 127

### `start()`

`public bool start()`

Starts the session.

#### Return Value

`bool` | True if started

#### Exceptions

RuntimeException | if something goes wrong starting the session

at line 154

### `getId()`

`public string getId()`

Returns the session ID.

#### Return Value

`string` | The session ID or empty

at line 162

### `setId()`

`public setId(string $id)`

Sets the session ID.

#### Parameters

`string` | `$id` |

at line 170

### `getName()`

`public mixed getName()`

Returns the session name.

#### Return Value

`mixed` | The session name

at line 178

### `setName()`

`public setName(string $name)`

Sets the session name.

#### Parameters

`string` | `$name` |

at line 186

### `regenerate()`

`public bool regenerate(bool $destroy = false, int $lifetime = null)`

Regenerates id that represents this storage.

This method must invoke session_regenerate_id($destroy) unless this interface is used for a storage object designed for unit or functional testing where a real PHP session would interfere with testing.

Note regenerate+destroy should not clear the session data in memory only delete the session data from persistent storage.

Care: When regenerating the session ID no locking is involved in PHP's session design. See https://bugs.php.net/bug.php?id=61470 for a discussion. So you must make sure the regenerated session is saved BEFORE sending the headers with the new ID. Symfony's HttpKernel offers a listener for this. See Symfony\Component\HttpKernel\EventListener\SaveSessionListener. Otherwise session data could get lost again for concurrent requests with the new ID. One result could be that you get logged out after just logging in.

#### Parameters

`bool` | `$destroy` | Destroy session when regenerating?
`int` | `$lifetime` | Sets the cookie lifetime for the session cookie. A null value will leave the system settings unchanged, 0 sets the cookie to expire with browser session. Time is in seconds, and is not a Unix timestamp.

#### Return Value

`bool` | True if session regenerated, false if error

#### Exceptions

RuntimeException | If an error occurs while regenerating this storage

at line 217

### `save()`

`public save()`

Force the session to be saved and closed.

This method must invoke session_write_close() unless this interface is used for a storage object design for unit or functional testing where a real PHP session would interfere with testing, in which case it should actually persist the session data if required.

#### Exceptions

RuntimeException | if the session is saved without being started, or if the session is already closed

at line 261

### `clear()`

`public clear()`

Clear all session data in memory.

at line 278

### `registerBag()`

`public registerBag(SessionBagInterface $bag)`

Registers a SessionBagInterface for use.

#### Parameters

`SessionBagInterface` | `$bag` |

at line 290

### `getBag()`

`public SessionBagInterface getBag(string $name)`

Gets a SessionBagInterface by name.

#### Parameters

`string` | `$name` |

#### Return Value

`SessionBagInterface` |

#### Exceptions

InvalidArgumentException | If the bag does not exist

at line 305

### `setMetadataBag()`

`public setMetadataBag(MetadataBag $metaBag = null)`

#### Parameters

`MetadataBag` | `$metaBag` |

at line 319

### `getMetadataBag()`

`public MetadataBag getMetadataBag()`

Gets the MetadataBag.

#### Return Value

`MetadataBag` |

at line 327

### `isStarted()`

`public bool isStarted()`

Checks if the session is started.

#### Return Value

`bool` | True if started, false otherwise

at line 342

### `setOptions()`

`public setOptions(array $options)`

Sets session.* ini variables.

For convenience we omit 'session.' from the beginning of the keys. Explicitly ignores other ini keys.

#### Parameters

`array` | `$options` | Session ini directives array(key => value)

#### See also

http://php.net/session.configuration |

at line 389

### `setSaveHandler()`

`public setSaveHandler(SessionHandlerInterface|null $saveHandler = null)`

Registers session save handler as a PHP session handler.

To use internal PHP session save handlers, override this method using ini_set with session.save_handler and session.save_path e.g.

    ini_set('session.save_handler', 'files');
    ini_set('session.save_path', '/tmp');

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

#### Parameters

`SessionHandlerInterface|null` | `$saveHandler` |

#### Exceptions

InvalidArgumentException |

#### See also

http://php.net/session-set-save-handler |
http://php.net/sessionhandlerinterface |
http://php.net/sessionhandler |
http://github.com/drak/NativeSession |

at line 424

### `loadSession()`

`protected loadSession(array $session = null)`

Load the session with attributes.

After starting the session, PHP retrieves the session from whatever handlers 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.

#### Parameters

`array` | `$session` |

## Source code

    <?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;

    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;

    /**
     * This provides a base class for session attribute storage.
     *
     * @author Drak <drak@zikula.org>
     */
    class NativeSessionStorage implements SessionStorageInterface
    {
        /**
         * @var SessionBagInterface[]
         */
        protected $bags = array();

        /**
         * @var bool
         */
        protected $started = false;

        /**
         * @var bool
         */
        protected $closed = false;

        /**
         * @var AbstractProxy|\SessionHandlerInterface
         */
        protected $saveHandler;

        /**
         * @var MetadataBag
         */
        protected $metadataBag;

        /**
         * Depending on how you want the storage driver to behave you probably
         * want to override this constructor entirely.
         *
         * List of options for $options array with their defaults.
         *
         * @see http://php.net/session.configuration for options
         * but we omit 'session.' from the beginning of the keys for convenience.
         *
         * ("auto_start", is not supported as it tells PHP to start a session before
         * 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"
         * cookie_path, "/"
         * cookie_secure, ""
         * gc_divisor, "100"
         * gc_maxlifetime, "1440"
         * gc_probability, "1"
         * lazy_write, "1"
         * name, "PHPSESSID"
         * referer_check, ""
         * serialize_handler, "php"
         * use_strict_mode, "0"
         * use_cookies, "1"
         * use_only_cookies, "1"
         * use_trans_sid, "0"
         * upload_progress.enabled, "1"
         * upload_progress.cleanup, "1"
         * upload_progress.prefix, "upload_progress_"
         * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS"
         * upload_progress.freq, "1%"
         * upload_progress.min-freq, "1"
         * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
         * 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 \SessionHandlerInterface|null $handler
         * @param MetadataBag                   $metaBag MetadataBag
         */
        public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
        {
            $options += array(
                'cache_limiter' => '',
                'cache_expire' => 0,
                'use_cookies' => 1,
                'lazy_write' => 1,
                'use_strict_mode' => 1,
            );

            session_register_shutdown();

            $this->setMetadataBag($metaBag);
            $this->setOptions($options);
            $this->setSaveHandler($handler);
        }

        /**
         * Gets the save handler instance.
         *
         * @return AbstractProxy|\SessionHandlerInterface
         */
        public function getSaveHandler()
        {
            return $this->saveHandler;
        }

        /**
         * {@inheritdoc}
         */
        public function start()
        {
            if ($this->started) {
                return true;
            }

            if (\PHP_SESSION_ACTIVE === session_status()) {
                throw new \RuntimeException('Failed to start the session: already started by PHP.');
            }

            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));
            }

            // ok to try and start the session
            if (!session_start()) {
                throw new \RuntimeException('Failed to start the session');
            }

            $this->loadSession();

            return true;
        }

        /**
         * {@inheritdoc}
         */
        public function getId()
        {
            return $this->saveHandler->getId();
        }

        /**
         * {@inheritdoc}
         */
        public function setId($id)
        {
            $this->saveHandler->setId($id);
        }

        /**
         * {@inheritdoc}
         */
        public function getName()
        {
            return $this->saveHandler->getName();
        }

        /**
         * {@inheritdoc}
         */
        public function setName($name)
        {
            $this->saveHandler->setName($name);
        }

        /**
         * {@inheritdoc}
         */
        public function regenerate($destroy = false, $lifetime = null)
        {
            // Cannot regenerate the session ID for non-active sessions.
            if (\PHP_SESSION_ACTIVE !== session_status()) {
                return false;
            }

            if (headers_sent()) {
                return false;
            }

            if (null !== $lifetime) {
                ini_set('session.cookie_lifetime', $lifetime);
            }

            if ($destroy) {
                $this->metadataBag->stampNew();
            }

            $isRegenerated = session_regenerate_id($destroy);

            // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it.
            // @see https://bugs.php.net/bug.php?id=70013
            $this->loadSession();

            return $isRegenerated;
        }

        /**
         * {@inheritdoc}
         */
        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) {
                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;
        }

        /**
         * {@inheritdoc}
         */
        public function clear()
        {
            // clear out the bags
            foreach ($this->bags as $bag) {
                $bag->clear();
            }

            // clear out the session
            $_SESSION = array();

            // reconnect the bags to the session
            $this->loadSession();
        }

        /**
         * {@inheritdoc}
         */
        public function registerBag(SessionBagInterface $bag)
        {
            if ($this->started) {
                throw new \LogicException('Cannot register a bag when the session is already started.');
            }

            $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->saveHandler->isActive()) {
                $this->loadSession();
            } elseif (!$this->started) {
                $this->start();
            }

            return $this->bags[$name];
        }

        public function setMetadataBag(MetadataBag $metaBag = null)
        {
            if (null === $metaBag) {
                $metaBag = new MetadataBag();
            }

            $this->metadataBag = $metaBag;
        }

        /**
         * Gets the MetadataBag.
         *
         * @return MetadataBag
         */
        public function getMetadataBag()
        {
            return $this->metadataBag;
        }

        /**
         * {@inheritdoc}
         */
        public function isStarted()
        {
            return $this->started;
        }

        /**
         * Sets session.* ini variables.
         *
         * For convenience we omit 'session.' from the beginning of the keys.
         * Explicitly ignores other ini keys.
         *
         * @param array $options Session ini directives array(key => value)
         *
         * @see http://php.net/session.configuration
         */
        public function setOptions(array $options)
        {
            if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
                return;
            }

            $validOptions = array_flip(array(
                'cache_limiter', 'cache_expire', 'cookie_domain', 'cookie_httponly',
                'cookie_lifetime', 'cookie_path', 'cookie_secure',
                'gc_divisor', 'gc_maxlifetime', 'gc_probability',
                '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',
                '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);
                }
            }
        }

        /**
         * Registers session save handler as a PHP session handler.
         *
         * To use internal PHP session save handlers, override this method using ini_set with
         * session.save_handler and session.save_path e.g.
         *
         *     ini_set('session.save_handler', 'files');
         *     ini_set('session.save_path', '/tmp');
         *
         * 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/session-set-save-handler
         * @see http://php.net/sessionhandlerinterface
         * @see http://php.net/sessionhandler
         * @see http://github.com/drak/NativeSession
         *
         * @param \SessionHandlerInterface|null $saveHandler
         *
         * @throws \InvalidArgumentException
         */
        public function setSaveHandler($saveHandler = null)
        {
            if (!$saveHandler instanceof AbstractProxy &&
                !$saveHandler instanceof \SessionHandlerInterface &&
                null !== $saveHandler) {
                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 StrictSessionHandler(new \SessionHandler()));
            }
            $this->saveHandler = $saveHandler;

            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);
            }
        }

        /**
         * Load the session with attributes.
         *
         * After starting the session, PHP retrieves the session from whatever handlers
         * 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.
         */
        protected function loadSession(array &$session = null)
        {
            if (null === $session) {
                $session = &$_SESSION;
            }

            $bags = array_merge($this->bags, array($this->metadataBag));

            foreach ($bags as $bag) {
                $key = $bag->getStorageKey();
                $session[$key] = isset($session[$key]) ? $session[$key] : array();
                $bag->initialize($session[$key]);
            }

            $this->started = true;
            $this->closed = false;
        }
    }