annotate vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
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@17 29 protected $bags = [];
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@17 101 public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null)
Chris@0 102 {
Chris@17 103 $options += [
Chris@14 104 'cache_limiter' => '',
Chris@14 105 'cache_expire' => 0,
Chris@14 106 'use_cookies' => 1,
Chris@14 107 'lazy_write' => 1,
Chris@17 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@17 140 if (filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN) && 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@17 229 if ([$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) {
Chris@14 230 unset($_SESSION[$key]);
Chris@14 231 }
Chris@14 232
Chris@17 233 // Register error handler to add information about the current save handler
Chris@17 234 $previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) {
Chris@17 235 if (E_WARNING === $type && 0 === strpos($msg, 'session_write_close():')) {
Chris@17 236 $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler;
Chris@17 237 $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler));
Chris@17 238 }
Chris@17 239
Chris@17 240 return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false;
Chris@17 241 });
Chris@14 242
Chris@14 243 try {
Chris@14 244 session_write_close();
Chris@14 245 } finally {
Chris@14 246 restore_error_handler();
Chris@14 247 $_SESSION = $session;
Chris@14 248 }
Chris@0 249
Chris@0 250 $this->closed = true;
Chris@0 251 $this->started = false;
Chris@0 252 }
Chris@0 253
Chris@0 254 /**
Chris@0 255 * {@inheritdoc}
Chris@0 256 */
Chris@0 257 public function clear()
Chris@0 258 {
Chris@0 259 // clear out the bags
Chris@0 260 foreach ($this->bags as $bag) {
Chris@0 261 $bag->clear();
Chris@0 262 }
Chris@0 263
Chris@0 264 // clear out the session
Chris@17 265 $_SESSION = [];
Chris@0 266
Chris@0 267 // reconnect the bags to the session
Chris@0 268 $this->loadSession();
Chris@0 269 }
Chris@0 270
Chris@0 271 /**
Chris@0 272 * {@inheritdoc}
Chris@0 273 */
Chris@0 274 public function registerBag(SessionBagInterface $bag)
Chris@0 275 {
Chris@0 276 if ($this->started) {
Chris@0 277 throw new \LogicException('Cannot register a bag when the session is already started.');
Chris@0 278 }
Chris@0 279
Chris@0 280 $this->bags[$bag->getName()] = $bag;
Chris@0 281 }
Chris@0 282
Chris@0 283 /**
Chris@0 284 * {@inheritdoc}
Chris@0 285 */
Chris@0 286 public function getBag($name)
Chris@0 287 {
Chris@0 288 if (!isset($this->bags[$name])) {
Chris@0 289 throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
Chris@0 290 }
Chris@0 291
Chris@14 292 if (!$this->started && $this->saveHandler->isActive()) {
Chris@0 293 $this->loadSession();
Chris@0 294 } elseif (!$this->started) {
Chris@0 295 $this->start();
Chris@0 296 }
Chris@0 297
Chris@0 298 return $this->bags[$name];
Chris@0 299 }
Chris@0 300
Chris@0 301 public function setMetadataBag(MetadataBag $metaBag = null)
Chris@0 302 {
Chris@0 303 if (null === $metaBag) {
Chris@0 304 $metaBag = new MetadataBag();
Chris@0 305 }
Chris@0 306
Chris@0 307 $this->metadataBag = $metaBag;
Chris@0 308 }
Chris@0 309
Chris@0 310 /**
Chris@0 311 * Gets the MetadataBag.
Chris@0 312 *
Chris@0 313 * @return MetadataBag
Chris@0 314 */
Chris@0 315 public function getMetadataBag()
Chris@0 316 {
Chris@0 317 return $this->metadataBag;
Chris@0 318 }
Chris@0 319
Chris@0 320 /**
Chris@0 321 * {@inheritdoc}
Chris@0 322 */
Chris@0 323 public function isStarted()
Chris@0 324 {
Chris@0 325 return $this->started;
Chris@0 326 }
Chris@0 327
Chris@0 328 /**
Chris@0 329 * Sets session.* ini variables.
Chris@0 330 *
Chris@0 331 * For convenience we omit 'session.' from the beginning of the keys.
Chris@0 332 * Explicitly ignores other ini keys.
Chris@0 333 *
Chris@17 334 * @param array $options Session ini directives [key => value]
Chris@0 335 *
Chris@0 336 * @see http://php.net/session.configuration
Chris@0 337 */
Chris@0 338 public function setOptions(array $options)
Chris@0 339 {
Chris@14 340 if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
Chris@14 341 return;
Chris@14 342 }
Chris@14 343
Chris@17 344 $validOptions = array_flip([
Chris@16 345 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
Chris@0 346 'cookie_lifetime', 'cookie_path', 'cookie_secure',
Chris@0 347 'entropy_file', 'entropy_length', 'gc_divisor',
Chris@0 348 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
Chris@14 349 'hash_function', 'lazy_write', 'name', 'referer_check',
Chris@0 350 'serialize_handler', 'use_strict_mode', 'use_cookies',
Chris@0 351 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
Chris@0 352 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
Chris@16 353 'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags',
Chris@12 354 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags',
Chris@17 355 ]);
Chris@0 356
Chris@0 357 foreach ($options as $key => $value) {
Chris@0 358 if (isset($validOptions[$key])) {
Chris@16 359 ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value);
Chris@0 360 }
Chris@0 361 }
Chris@0 362 }
Chris@0 363
Chris@0 364 /**
Chris@0 365 * Registers session save handler as a PHP session handler.
Chris@0 366 *
Chris@0 367 * To use internal PHP session save handlers, override this method using ini_set with
Chris@0 368 * session.save_handler and session.save_path e.g.
Chris@0 369 *
Chris@0 370 * ini_set('session.save_handler', 'files');
Chris@0 371 * ini_set('session.save_path', '/tmp');
Chris@0 372 *
Chris@14 373 * or pass in a \SessionHandler instance which configures session.save_handler in the
Chris@0 374 * constructor, for a template see NativeFileSessionHandler or use handlers in
Chris@0 375 * composer package drak/native-session
Chris@0 376 *
Chris@0 377 * @see http://php.net/session-set-save-handler
Chris@0 378 * @see http://php.net/sessionhandlerinterface
Chris@0 379 * @see http://php.net/sessionhandler
Chris@0 380 * @see http://github.com/drak/NativeSession
Chris@0 381 *
Chris@14 382 * @param \SessionHandlerInterface|null $saveHandler
Chris@0 383 *
Chris@0 384 * @throws \InvalidArgumentException
Chris@0 385 */
Chris@0 386 public function setSaveHandler($saveHandler = null)
Chris@0 387 {
Chris@0 388 if (!$saveHandler instanceof AbstractProxy &&
Chris@0 389 !$saveHandler instanceof \SessionHandlerInterface &&
Chris@0 390 null !== $saveHandler) {
Chris@14 391 throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
Chris@0 392 }
Chris@0 393
Chris@0 394 // Wrap $saveHandler in proxy and prevent double wrapping of proxy
Chris@0 395 if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
Chris@0 396 $saveHandler = new SessionHandlerProxy($saveHandler);
Chris@0 397 } elseif (!$saveHandler instanceof AbstractProxy) {
Chris@14 398 $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
Chris@0 399 }
Chris@0 400 $this->saveHandler = $saveHandler;
Chris@0 401
Chris@14 402 if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
Chris@14 403 return;
Chris@14 404 }
Chris@14 405
Chris@14 406 if ($this->saveHandler instanceof SessionHandlerProxy) {
Chris@0 407 session_set_save_handler($this->saveHandler, false);
Chris@0 408 }
Chris@0 409 }
Chris@0 410
Chris@0 411 /**
Chris@0 412 * Load the session with attributes.
Chris@0 413 *
Chris@0 414 * After starting the session, PHP retrieves the session from whatever handlers
Chris@0 415 * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()).
Chris@0 416 * PHP takes the return value from the read() handler, unserializes it
Chris@0 417 * and populates $_SESSION with the result automatically.
Chris@0 418 */
Chris@0 419 protected function loadSession(array &$session = null)
Chris@0 420 {
Chris@0 421 if (null === $session) {
Chris@0 422 $session = &$_SESSION;
Chris@0 423 }
Chris@0 424
Chris@17 425 $bags = array_merge($this->bags, [$this->metadataBag]);
Chris@0 426
Chris@0 427 foreach ($bags as $bag) {
Chris@0 428 $key = $bag->getStorageKey();
Chris@17 429 $session[$key] = isset($session[$key]) ? $session[$key] : [];
Chris@0 430 $bag->initialize($session[$key]);
Chris@0 431 }
Chris@0 432
Chris@0 433 $this->started = true;
Chris@0 434 $this->closed = false;
Chris@0 435 }
Chris@0 436 }