vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php line 296
<?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;// Help opcache.preload discover always-needed symbolsclass_exists(MetadataBag::class);class_exists(StrictSessionHandler::class);class_exists(SessionHandlerProxy::class);/*** This provides a base class for session attribute storage.** @author Drak <drak@zikula.org>*/class NativeSessionStorage implements SessionStorageInterface{/*** @var SessionBagInterface[]*/protected $bags = [];/*** @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 https://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, ""* cookie_samesite, null* gc_divisor, "100"* gc_maxlifetime, "1440"* gc_probability, "1"* lazy_write, "1"* name, "PHPSESSID"* referer_check, ""* serialize_handler, "php"* use_strict_mode, "1"* use_cookies, "1"* use_only_cookies, "1"* use_trans_sid, "0"* sid_length, "32"* sid_bits_per_character, "5"* trans_sid_hosts, $_SERVER['HTTP_HOST']* trans_sid_tags, "a=href,area=href,frame=src,form="*/public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface $handler = null, MetadataBag $metaBag = null){if (!\extension_loaded('session')) {throw new \LogicException('PHP extension "session" is required.');}$options += ['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.*/public function getSaveHandler(): AbstractProxy|\SessionHandlerInterface{return $this->saveHandler;}public function start(): bool{if ($this->started) {return true;}if (\PHP_SESSION_ACTIVE === session_status()) {throw new \RuntimeException('Failed to start the session: already started by PHP.');}if (filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOL) && 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));}$sessionId = $_COOKIE[session_name()] ?? null;/** Explanation of the session ID regular expression: `/^[a-zA-Z0-9,-]{22,250}$/`.** ---------- Part 1** The part `[a-zA-Z0-9,-]` is related to the PHP ini directive `session.sid_bits_per_character` defined as 6.* See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character.* Allowed values are integers such as:* - 4 for range `a-f0-9`* - 5 for range `a-v0-9`* - 6 for range `a-zA-Z0-9,-`** ---------- Part 2** The part `{22,250}` is related to the PHP ini directive `session.sid_length`.* See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length.* Allowed values are integers between 22 and 256, but we use 250 for the max.** Where does the 250 come from?* - The length of Windows and Linux filenames is limited to 255 bytes. Then the max must not exceed 255.* - The session filename prefix is `sess_`, a 5 bytes string. Then the max must not exceed 255 - 5 = 250.** ---------- Conclusion** The parts 1 and 2 prevent the warning below:* `PHP Warning: SessionHandler::read(): Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, "-", and "," characters are allowed.`** The part 2 prevents the warning below:* `PHP Warning: SessionHandler::read(): open(filepath, O_RDWR) failed: No such file or directory (2).`*/if ($sessionId && $this->saveHandler instanceof AbstractProxy && 'files' === $this->saveHandler->getSaveHandlerName() && !preg_match('/^[a-zA-Z0-9,-]{22,250}$/', $sessionId)) {// the session ID in the header is invalid, create a new onesession_id(session_create_id());}// ok to try and start the sessionif (!session_start()) {throw new \RuntimeException('Failed to start the session.');}$this->loadSession();return true;}public function getId(): string{return $this->saveHandler->getId();}public function setId(string $id){$this->saveHandler->setId($id);}public function getName(): string{return $this->saveHandler->getName();}public function setName(string $name){$this->saveHandler->setName($name);}public function regenerate(bool $destroy = false, int $lifetime = null): bool{// 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 && $lifetime != \ini_get('session.cookie_lifetime')) {$this->save();ini_set('session.cookie_lifetime', $lifetime);$this->start();}if ($destroy) {$this->metadataBag->stampNew();}return session_regenerate_id($destroy);}public function save(){// Store a copy so we can restore the bags in case the session was not left empty$session = $_SESSION;foreach ($this->bags as $bag) {if (empty($_SESSION[$key = $bag->getStorageKey()])) {unset($_SESSION[$key]);}}if ($_SESSION && [$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 && str_starts_with($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', $handler::class);}return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false;});try {session_write_close();} finally {restore_error_handler();// Restore only if not emptyif ($_SESSION) {$_SESSION = $session;}}$this->closed = true;$this->started = false;}public function clear(){// clear out the bagsforeach ($this->bags as $bag) {$bag->clear();}// clear out the session$_SESSION = [];// reconnect the bags to the session$this->loadSession();}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;}public function getBag(string $name): SessionBagInterface{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 (1 > \func_num_args()) {trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);}$this->metadataBag = $metaBag ?? new MetadataBag();}/*** Gets the MetadataBag.*/public function getMetadataBag(): MetadataBag{return $this->metadataBag;}public function isStarted(): bool{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 [key => value]** @see https://php.net/session.configuration*/public function setOptions(array $options){if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {return;}$validOptions = array_flip(['cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly','cookie_lifetime', 'cookie_path', 'cookie_secure', 'cookie_samesite','gc_divisor', 'gc_maxlifetime', 'gc_probability','lazy_write', 'name', 'referer_check','serialize_handler', 'use_strict_mode', 'use_cookies','use_only_cookies', 'use_trans_sid','sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags',]);foreach ($options as $key => $value) {if (isset($validOptions[$key])) {if ('cookie_secure' === $key && 'auto' === $value) {continue;}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.** @see https://php.net/session-set-save-handler* @see https://php.net/sessionhandlerinterface* @see https://php.net/sessionhandler** @throws \InvalidArgumentException*/public function setSaveHandler(AbstractProxy|\SessionHandlerInterface $saveHandler = null){if (1 > \func_num_args()) {trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);}// Wrap $saveHandler in proxy and prevent double wrapping of proxyif (!$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, 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, [$this->metadataBag]);foreach ($bags as $bag) {$key = $bag->getStorageKey();$session[$key] = isset($session[$key]) && \is_array($session[$key]) ? $session[$key] : [];$bag->initialize($session[$key]);}$this->started = true;$this->closed = false;}}