annotate vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @ 16:c2387f117808

Routine composer update
author Chris Cannam
date Tue, 10 Jul 2018 15:07:59 +0100
parents 1fec387a4317
children 129ea1e6d783
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /*
Chris@0 4 * This file is part of the Symfony package.
Chris@0 5 *
Chris@0 6 * (c) Fabien Potencier <fabien@symfony.com>
Chris@0 7 *
Chris@0 8 * For the full copyright and license information, please view the LICENSE
Chris@0 9 * file that was distributed with this source code.
Chris@0 10 */
Chris@0 11
Chris@0 12 namespace Symfony\Component\HttpFoundation\Session\Storage;
Chris@0 13
Chris@0 14 use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
Chris@14 15 use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
Chris@0 16 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
Chris@0 17 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
Chris@0 18
Chris@0 19 /**
Chris@0 20 * This provides a base class for session attribute storage.
Chris@0 21 *
Chris@0 22 * @author Drak <drak@zikula.org>
Chris@0 23 */
Chris@0 24 class NativeSessionStorage implements SessionStorageInterface
Chris@0 25 {
Chris@0 26 /**
Chris@0 27 * @var SessionBagInterface[]
Chris@0 28 */
Chris@14 29 protected $bags = array();
Chris@0 30
Chris@0 31 /**
Chris@0 32 * @var bool
Chris@0 33 */
Chris@0 34 protected $started = false;
Chris@0 35
Chris@0 36 /**
Chris@0 37 * @var bool
Chris@0 38 */
Chris@0 39 protected $closed = false;
Chris@0 40
Chris@0 41 /**
Chris@14 42 * @var AbstractProxy|\SessionHandlerInterface
Chris@0 43 */
Chris@0 44 protected $saveHandler;
Chris@0 45
Chris@0 46 /**
Chris@0 47 * @var MetadataBag
Chris@0 48 */
Chris@0 49 protected $metadataBag;
Chris@0 50
Chris@0 51 /**
Chris@0 52 * Depending on how you want the storage driver to behave you probably
Chris@0 53 * want to override this constructor entirely.
Chris@0 54 *
Chris@0 55 * List of options for $options array with their defaults.
Chris@0 56 *
Chris@0 57 * @see http://php.net/session.configuration for options
Chris@0 58 * but we omit 'session.' from the beginning of the keys for convenience.
Chris@0 59 *
Chris@0 60 * ("auto_start", is not supported as it tells PHP to start a session before
Chris@0 61 * PHP starts to execute user-land code. Setting during runtime has no effect).
Chris@0 62 *
Chris@0 63 * cache_limiter, "" (use "0" to prevent headers from being sent entirely).
Chris@14 64 * cache_expire, "0"
Chris@0 65 * cookie_domain, ""
Chris@0 66 * cookie_httponly, ""
Chris@0 67 * cookie_lifetime, "0"
Chris@0 68 * cookie_path, "/"
Chris@0 69 * cookie_secure, ""
Chris@0 70 * entropy_file, ""
Chris@0 71 * entropy_length, "0"
Chris@0 72 * gc_divisor, "100"
Chris@0 73 * gc_maxlifetime, "1440"
Chris@0 74 * gc_probability, "1"
Chris@0 75 * hash_bits_per_character, "4"
Chris@0 76 * hash_function, "0"
Chris@14 77 * lazy_write, "1"
Chris@0 78 * name, "PHPSESSID"
Chris@0 79 * referer_check, ""
Chris@0 80 * serialize_handler, "php"
Chris@0 81 * use_strict_mode, "0"
Chris@0 82 * use_cookies, "1"
Chris@0 83 * use_only_cookies, "1"
Chris@0 84 * use_trans_sid, "0"
Chris@0 85 * upload_progress.enabled, "1"
Chris@0 86 * upload_progress.cleanup, "1"
Chris@0 87 * upload_progress.prefix, "upload_progress_"
Chris@0 88 * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS"
Chris@0 89 * upload_progress.freq, "1%"
Chris@0 90 * upload_progress.min-freq, "1"
Chris@0 91 * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
Chris@12 92 * sid_length, "32"
Chris@12 93 * sid_bits_per_character, "5"
Chris@12 94 * trans_sid_hosts, $_SERVER['HTTP_HOST']
Chris@12 95 * trans_sid_tags, "a=href,area=href,frame=src,form="
Chris@0 96 *
Chris@14 97 * @param array $options Session configuration options
Chris@14 98 * @param \SessionHandlerInterface|null $handler
Chris@14 99 * @param MetadataBag $metaBag MetadataBag
Chris@0 100 */
Chris@0 101 public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
Chris@0 102 {
Chris@14 103 $options += array(
Chris@14 104 'cache_limiter' => '',
Chris@14 105 'cache_expire' => 0,
Chris@14 106 'use_cookies' => 1,
Chris@14 107 'lazy_write' => 1,
Chris@14 108 );
Chris@0 109
Chris@0 110 session_register_shutdown();
Chris@0 111
Chris@0 112 $this->setMetadataBag($metaBag);
Chris@0 113 $this->setOptions($options);
Chris@0 114 $this->setSaveHandler($handler);
Chris@0 115 }
Chris@0 116
Chris@0 117 /**
Chris@0 118 * Gets the save handler instance.
Chris@0 119 *
Chris@14 120 * @return AbstractProxy|\SessionHandlerInterface
Chris@0 121 */
Chris@0 122 public function getSaveHandler()
Chris@0 123 {
Chris@0 124 return $this->saveHandler;
Chris@0 125 }
Chris@0 126
Chris@0 127 /**
Chris@0 128 * {@inheritdoc}
Chris@0 129 */
Chris@0 130 public function start()
Chris@0 131 {
Chris@0 132 if ($this->started) {
Chris@0 133 return true;
Chris@0 134 }
Chris@0 135
Chris@0 136 if (\PHP_SESSION_ACTIVE === session_status()) {
Chris@0 137 throw new \RuntimeException('Failed to start the session: already started by PHP.');
Chris@0 138 }
Chris@0 139
Chris@0 140 if (ini_get('session.use_cookies') && headers_sent($file, $line)) {
Chris@0 141 throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
Chris@0 142 }
Chris@0 143
Chris@0 144 // ok to try and start the session
Chris@0 145 if (!session_start()) {
Chris@0 146 throw new \RuntimeException('Failed to start the session');
Chris@0 147 }
Chris@0 148
Chris@0 149 $this->loadSession();
Chris@0 150
Chris@0 151 return true;
Chris@0 152 }
Chris@0 153
Chris@0 154 /**
Chris@0 155 * {@inheritdoc}
Chris@0 156 */
Chris@0 157 public function getId()
Chris@0 158 {
Chris@0 159 return $this->saveHandler->getId();
Chris@0 160 }
Chris@0 161
Chris@0 162 /**
Chris@0 163 * {@inheritdoc}
Chris@0 164 */
Chris@0 165 public function setId($id)
Chris@0 166 {
Chris@0 167 $this->saveHandler->setId($id);
Chris@0 168 }
Chris@0 169
Chris@0 170 /**
Chris@0 171 * {@inheritdoc}
Chris@0 172 */
Chris@0 173 public function getName()
Chris@0 174 {
Chris@0 175 return $this->saveHandler->getName();
Chris@0 176 }
Chris@0 177
Chris@0 178 /**
Chris@0 179 * {@inheritdoc}
Chris@0 180 */
Chris@0 181 public function setName($name)
Chris@0 182 {
Chris@0 183 $this->saveHandler->setName($name);
Chris@0 184 }
Chris@0 185
Chris@0 186 /**
Chris@0 187 * {@inheritdoc}
Chris@0 188 */
Chris@0 189 public function regenerate($destroy = false, $lifetime = null)
Chris@0 190 {
Chris@0 191 // Cannot regenerate the session ID for non-active sessions.
Chris@0 192 if (\PHP_SESSION_ACTIVE !== session_status()) {
Chris@0 193 return false;
Chris@0 194 }
Chris@0 195
Chris@14 196 if (headers_sent()) {
Chris@14 197 return false;
Chris@14 198 }
Chris@14 199
Chris@0 200 if (null !== $lifetime) {
Chris@0 201 ini_set('session.cookie_lifetime', $lifetime);
Chris@0 202 }
Chris@0 203
Chris@0 204 if ($destroy) {
Chris@0 205 $this->metadataBag->stampNew();
Chris@0 206 }
Chris@0 207
Chris@0 208 $isRegenerated = session_regenerate_id($destroy);
Chris@0 209
Chris@0 210 // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it.
Chris@0 211 // @see https://bugs.php.net/bug.php?id=70013
Chris@0 212 $this->loadSession();
Chris@0 213
Chris@0 214 return $isRegenerated;
Chris@0 215 }
Chris@0 216
Chris@0 217 /**
Chris@0 218 * {@inheritdoc}
Chris@0 219 */
Chris@0 220 public function save()
Chris@0 221 {
Chris@14 222 $session = $_SESSION;
Chris@14 223
Chris@14 224 foreach ($this->bags as $bag) {
Chris@14 225 if (empty($_SESSION[$key = $bag->getStorageKey()])) {
Chris@14 226 unset($_SESSION[$key]);
Chris@14 227 }
Chris@14 228 }
Chris@14 229 if (array($key = $this->metadataBag->getStorageKey()) === array_keys($_SESSION)) {
Chris@14 230 unset($_SESSION[$key]);
Chris@14 231 }
Chris@14 232
Chris@14 233 // Register custom error handler to catch a possible failure warning during session write
Chris@14 234 set_error_handler(function ($errno, $errstr, $errfile, $errline) {
Chris@14 235 throw new \ErrorException($errstr, $errno, E_WARNING, $errfile, $errline);
Chris@14 236 }, E_WARNING);
Chris@14 237
Chris@14 238 try {
Chris@14 239 $e = null;
Chris@14 240 session_write_close();
Chris@14 241 } catch (\ErrorException $e) {
Chris@14 242 } finally {
Chris@14 243 restore_error_handler();
Chris@14 244 $_SESSION = $session;
Chris@14 245 }
Chris@14 246 if (null !== $e) {
Chris@14 247 // The default PHP error message is not very helpful, as it does not give any information on the current save handler.
Chris@14 248 // Therefore, we catch this error and trigger a warning with a better error message
Chris@14 249 $handler = $this->getSaveHandler();
Chris@14 250 if ($handler instanceof SessionHandlerProxy) {
Chris@14 251 $handler = $handler->getHandler();
Chris@14 252 }
Chris@14 253
Chris@14 254 trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', get_class($handler)), E_USER_WARNING);
Chris@14 255 }
Chris@0 256
Chris@0 257 $this->closed = true;
Chris@0 258 $this->started = false;
Chris@0 259 }
Chris@0 260
Chris@0 261 /**
Chris@0 262 * {@inheritdoc}
Chris@0 263 */
Chris@0 264 public function clear()
Chris@0 265 {
Chris@0 266 // clear out the bags
Chris@0 267 foreach ($this->bags as $bag) {
Chris@0 268 $bag->clear();
Chris@0 269 }
Chris@0 270
Chris@0 271 // clear out the session
Chris@0 272 $_SESSION = array();
Chris@0 273
Chris@0 274 // reconnect the bags to the session
Chris@0 275 $this->loadSession();
Chris@0 276 }
Chris@0 277
Chris@0 278 /**
Chris@0 279 * {@inheritdoc}
Chris@0 280 */
Chris@0 281 public function registerBag(SessionBagInterface $bag)
Chris@0 282 {
Chris@0 283 if ($this->started) {
Chris@0 284 throw new \LogicException('Cannot register a bag when the session is already started.');
Chris@0 285 }
Chris@0 286
Chris@0 287 $this->bags[$bag->getName()] = $bag;
Chris@0 288 }
Chris@0 289
Chris@0 290 /**
Chris@0 291 * {@inheritdoc}
Chris@0 292 */
Chris@0 293 public function getBag($name)
Chris@0 294 {
Chris@0 295 if (!isset($this->bags[$name])) {
Chris@0 296 throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
Chris@0 297 }
Chris@0 298
Chris@14 299 if (!$this->started && $this->saveHandler->isActive()) {
Chris@0 300 $this->loadSession();
Chris@0 301 } elseif (!$this->started) {
Chris@0 302 $this->start();
Chris@0 303 }
Chris@0 304
Chris@0 305 return $this->bags[$name];
Chris@0 306 }
Chris@0 307
Chris@0 308 public function setMetadataBag(MetadataBag $metaBag = null)
Chris@0 309 {
Chris@0 310 if (null === $metaBag) {
Chris@0 311 $metaBag = new MetadataBag();
Chris@0 312 }
Chris@0 313
Chris@0 314 $this->metadataBag = $metaBag;
Chris@0 315 }
Chris@0 316
Chris@0 317 /**
Chris@0 318 * Gets the MetadataBag.
Chris@0 319 *
Chris@0 320 * @return MetadataBag
Chris@0 321 */
Chris@0 322 public function getMetadataBag()
Chris@0 323 {
Chris@0 324 return $this->metadataBag;
Chris@0 325 }
Chris@0 326
Chris@0 327 /**
Chris@0 328 * {@inheritdoc}
Chris@0 329 */
Chris@0 330 public function isStarted()
Chris@0 331 {
Chris@0 332 return $this->started;
Chris@0 333 }
Chris@0 334
Chris@0 335 /**
Chris@0 336 * Sets session.* ini variables.
Chris@0 337 *
Chris@0 338 * For convenience we omit 'session.' from the beginning of the keys.
Chris@0 339 * Explicitly ignores other ini keys.
Chris@0 340 *
Chris@0 341 * @param array $options Session ini directives array(key => value)
Chris@0 342 *
Chris@0 343 * @see http://php.net/session.configuration
Chris@0 344 */
Chris@0 345 public function setOptions(array $options)
Chris@0 346 {
Chris@14 347 if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
Chris@14 348 return;
Chris@14 349 }
Chris@14 350
Chris@0 351 $validOptions = array_flip(array(
Chris@16 352 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
Chris@0 353 'cookie_lifetime', 'cookie_path', 'cookie_secure',
Chris@0 354 'entropy_file', 'entropy_length', 'gc_divisor',
Chris@0 355 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
Chris@14 356 'hash_function', 'lazy_write', 'name', 'referer_check',
Chris@0 357 'serialize_handler', 'use_strict_mode', 'use_cookies',
Chris@0 358 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
Chris@0 359 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
Chris@16 360 'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags',
Chris@12 361 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags',
Chris@0 362 ));
Chris@0 363
Chris@0 364 foreach ($options as $key => $value) {
Chris@0 365 if (isset($validOptions[$key])) {
Chris@16 366 ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value);
Chris@0 367 }
Chris@0 368 }
Chris@0 369 }
Chris@0 370
Chris@0 371 /**
Chris@0 372 * Registers session save handler as a PHP session handler.
Chris@0 373 *
Chris@0 374 * To use internal PHP session save handlers, override this method using ini_set with
Chris@0 375 * session.save_handler and session.save_path e.g.
Chris@0 376 *
Chris@0 377 * ini_set('session.save_handler', 'files');
Chris@0 378 * ini_set('session.save_path', '/tmp');
Chris@0 379 *
Chris@14 380 * or pass in a \SessionHandler instance which configures session.save_handler in the
Chris@0 381 * constructor, for a template see NativeFileSessionHandler or use handlers in
Chris@0 382 * composer package drak/native-session
Chris@0 383 *
Chris@0 384 * @see http://php.net/session-set-save-handler
Chris@0 385 * @see http://php.net/sessionhandlerinterface
Chris@0 386 * @see http://php.net/sessionhandler
Chris@0 387 * @see http://github.com/drak/NativeSession
Chris@0 388 *
Chris@14 389 * @param \SessionHandlerInterface|null $saveHandler
Chris@0 390 *
Chris@0 391 * @throws \InvalidArgumentException
Chris@0 392 */
Chris@0 393 public function setSaveHandler($saveHandler = null)
Chris@0 394 {
Chris@0 395 if (!$saveHandler instanceof AbstractProxy &&
Chris@0 396 !$saveHandler instanceof \SessionHandlerInterface &&
Chris@0 397 null !== $saveHandler) {
Chris@14 398 throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
Chris@0 399 }
Chris@0 400
Chris@0 401 // Wrap $saveHandler in proxy and prevent double wrapping of proxy
Chris@0 402 if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
Chris@0 403 $saveHandler = new SessionHandlerProxy($saveHandler);
Chris@0 404 } elseif (!$saveHandler instanceof AbstractProxy) {
Chris@14 405 $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
Chris@0 406 }
Chris@0 407 $this->saveHandler = $saveHandler;
Chris@0 408
Chris@14 409 if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
Chris@14 410 return;
Chris@14 411 }
Chris@14 412
Chris@14 413 if ($this->saveHandler instanceof SessionHandlerProxy) {
Chris@14 414 session_set_save_handler($this->saveHandler->getHandler(), false);
Chris@14 415 } elseif ($this->saveHandler instanceof \SessionHandlerInterface) {
Chris@0 416 session_set_save_handler($this->saveHandler, false);
Chris@0 417 }
Chris@0 418 }
Chris@0 419
Chris@0 420 /**
Chris@0 421 * Load the session with attributes.
Chris@0 422 *
Chris@0 423 * After starting the session, PHP retrieves the session from whatever handlers
Chris@0 424 * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()).
Chris@0 425 * PHP takes the return value from the read() handler, unserializes it
Chris@0 426 * and populates $_SESSION with the result automatically.
Chris@0 427 */
Chris@0 428 protected function loadSession(array &$session = null)
Chris@0 429 {
Chris@0 430 if (null === $session) {
Chris@0 431 $session = &$_SESSION;
Chris@0 432 }
Chris@0 433
Chris@0 434 $bags = array_merge($this->bags, array($this->metadataBag));
Chris@0 435
Chris@0 436 foreach ($bags as $bag) {
Chris@0 437 $key = $bag->getStorageKey();
Chris@0 438 $session[$key] = isset($session[$key]) ? $session[$key] : array();
Chris@0 439 $bag->initialize($session[$key]);
Chris@0 440 }
Chris@0 441
Chris@0 442 $this->started = true;
Chris@0 443 $this->closed = false;
Chris@0 444 }
Chris@0 445 }