├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── AbstractContainer.php ├── AbstractManager.php ├── Config ├── ConfigInterface.php ├── SessionConfig.php └── StandardConfig.php ├── ConfigProvider.php ├── Container.php ├── Exception ├── BadMethodCallException.php ├── ExceptionInterface.php ├── InvalidArgumentException.php └── RuntimeException.php ├── ManagerInterface.php ├── Module.php ├── SaveHandler ├── Cache.php ├── DbTableGateway.php ├── DbTableGatewayOptions.php ├── MongoDB.php ├── MongoDBOptions.php └── SaveHandlerInterface.php ├── Service ├── ContainerAbstractServiceFactory.php ├── SessionConfigFactory.php ├── SessionManagerFactory.php └── StorageFactory.php ├── SessionManager.php ├── Storage ├── AbstractSessionArrayStorage.php ├── ArrayStorage.php ├── Factory.php ├── SessionArrayStorage.php ├── SessionStorage.php ├── StorageInitializationInterface.php └── StorageInterface.php ├── Validator ├── AbstractValidatorChainEM2.php ├── AbstractValidatorChainEM3.php ├── HttpUserAgent.php ├── Id.php ├── RemoteAddr.php ├── ValidatorChainTrait.php └── ValidatorInterface.php ├── ValidatorChain.php └── compatibility └── autoload.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 2.9.2 - TBD 6 | 7 | ### Added 8 | 9 | - Nothing. 10 | 11 | ### Changed 12 | 13 | - Nothing. 14 | 15 | ### Deprecated 16 | 17 | - Nothing. 18 | 19 | ### Removed 20 | 21 | - Nothing. 22 | 23 | ### Fixed 24 | 25 | - Nothing. 26 | 27 | ## 2.9.1 - 2019-10-28 28 | 29 | ### Added 30 | 31 | - Nothing. 32 | 33 | ### Changed 34 | 35 | - Nothing. 36 | 37 | ### Deprecated 38 | 39 | - Nothing. 40 | 41 | ### Removed 42 | 43 | - Nothing. 44 | 45 | ### Fixed 46 | 47 | - [#123](https://github.com/zendframework/zend-session/pull/123) fixes a bug preventing two first hash functions from `hash_algos()` 48 | (usually `md2` and `md4`) from being used in `SessionConfig::setHashFunction`. 49 | 50 | ## 2.9.0 - 2019-09-20 51 | 52 | ### Added 53 | 54 | - [#115](https://github.com/zendframework/zend-session/pull/115) adds support for PHP 7.3. 55 | 56 | ### Changed 57 | 58 | - Nothing. 59 | 60 | ### Deprecated 61 | 62 | - Nothing. 63 | 64 | ### Removed 65 | 66 | - [#115](https://github.com/zendframework/zend-session/pull/115) removes support for zend-stdlib v2 releases. 67 | 68 | ### Fixed 69 | 70 | - Nothing. 71 | 72 | ## 2.8.7 - 2019-09-19 73 | 74 | ### Added 75 | 76 | - Nothing. 77 | 78 | ### Changed 79 | 80 | - Nothing. 81 | 82 | ### Deprecated 83 | 84 | - Nothing. 85 | 86 | ### Removed 87 | 88 | - Nothing. 89 | 90 | ### Fixed 91 | 92 | - [#122](https://github.com/zendframework/zend-session/pull/122) fixes 93 | type check for configuration of session storage. Allows input to be 94 | an instance of ArrayAccess or an array. 95 | 96 | ## 2.8.6 - 2019-08-11 97 | 98 | ### Added 99 | 100 | - Nothing. 101 | 102 | ### Changed 103 | 104 | - Nothing. 105 | 106 | ### Deprecated 107 | 108 | - Nothing. 109 | 110 | ### Removed 111 | 112 | - Nothing. 113 | 114 | ### Fixed 115 | 116 | - [#120](https://github.com/zendframework/zend-session/pull/120) fixes issue 117 | "Commands out of sync; you can't run this command now" with DbTableGateway 118 | save handler while using Mysqli adapter. 119 | 120 | - [#106](https://github.com/zendframework/zend-session/pull/106) fixes issue 121 | with Garbage collection of MongoDB save handler where maxlifetime 122 | is provided in seconds. 123 | 124 | - [#114](https://github.com/zendframework/zend-session/pull/114) fixes 125 | Validator\Id compatibility with PHP 7.1. INI setting `session.sid_bits_per_character` 126 | can be now used with PHP 7.1+ instead of `session.hash_bits_per_character` 127 | (used with PHP versions prior to 7.1). 128 | 129 | In some very specific situations this can lead to an issue with previously generated sessions. 130 | See issue [#121](https://github.com/zendframework/zend-session/issues/121). 131 | 132 | - [#118](https://github.com/zendframework/zend-session/pull/118) avoid unnecessary phpinfo() call 133 | when register own save handler which is an object. 134 | 135 | ## 2.8.5 - 2018-02-22 136 | 137 | ### Added 138 | 139 | - Nothing. 140 | 141 | ### Changed 142 | 143 | - Nothing. 144 | 145 | ### Deprecated 146 | 147 | - Nothing. 148 | 149 | ### Removed 150 | 151 | - Nothing. 152 | 153 | ### Fixed 154 | 155 | - [#108](https://github.com/zendframework/zend-session/pull/108) fixes a dependency 156 | conflict in `composer.json` which prevented `phpunit/phpunit` 6.5 or newer from 157 | being installed together with `zendframework/zend-session`. 158 | 159 | ## 2.8.4 - 2018-01-31 160 | 161 | ### Added 162 | 163 | - Nothing. 164 | 165 | ### Changed 166 | 167 | - Nothing. 168 | 169 | ### Deprecated 170 | 171 | - Nothing. 172 | 173 | ### Removed 174 | 175 | - Nothing. 176 | 177 | ### Fixed 178 | 179 | - [#107](https://github.com/zendframework/zend-session/pull/107) fixes an error 180 | raised by `ini_set()` within `SessionConfig::setStorageOption()` that occurs 181 | for certain INI values that cannot be set if the session is active. When this 182 | situation occurs, the class performs a `session_write_close()`, sets the new 183 | INI value, and then restarts the session. As such, we recommend that you 184 | either set production INI values in your production `php.ini`, and/or always 185 | pass your fully configured session manager to container instances you create. 186 | 187 | - [#105](https://github.com/zendframework/zend-session/pull/105) fixes an edge 188 | case whereby if the special `__ZF` session value is a non-array value, 189 | initializing the session would result in errors. 190 | 191 | - [#102](https://github.com/zendframework/zend-session/pull/102) fixes an issue 192 | introduced with 2.8.0 with `AbstractContainer::offsetGet`. Starting in 2.8.0, 193 | if the provided `$key` did not exist, the method would raise an error 194 | regarding an invalid variable reference; this release provides a fix that 195 | resolves that issue. 196 | 197 | ## 2.8.3 - 2017-12-01 198 | 199 | ### Added 200 | 201 | - Nothing. 202 | 203 | ### Changed 204 | 205 | - Nothing. 206 | 207 | ### Deprecated 208 | 209 | - Nothing. 210 | 211 | ### Removed 212 | 213 | - Nothing. 214 | 215 | ### Fixed 216 | 217 | - [#101](https://github.com/zendframework/zend-session/pull/101) fixes an issue 218 | created with the 2.8.2 release with regards to setting a session save path for 219 | non-files save handlers; prior to this patch, incorrect validations were run 220 | on the path provided, leading to unexpected exceptions being raised. 221 | 222 | ## 2.8.2 - 2017-11-29 223 | 224 | ### Added 225 | 226 | - Nothing. 227 | 228 | ### Changed 229 | 230 | - Nothing. 231 | 232 | ### Deprecated 233 | 234 | - Nothing. 235 | 236 | ### Removed 237 | 238 | - Nothing. 239 | 240 | ### Fixed 241 | 242 | - [#85](https://github.com/zendframework/zend-session/pull/85) fixes an issue 243 | with how the expiration seconds are handled when a long-running request 244 | occurs. Previously, when called, it would use the value of 245 | `$_SERVER['REQUEST_TIME']` to calculate the expiration time; this would cause 246 | failures if the expiration seconds had been reached by the time the value was 247 | set. It now correctly uses the current `time()`. 248 | 249 | - [#99](https://github.com/zendframework/zend-session/pull/99) fixes how 250 | `Zend\Session\Config\SessionConfig` handles attaching save handlers to ensure 251 | it will honor any handlers registered with the PHP engine (e.g., redis, 252 | rediscluster, etc.). 253 | 254 | ## 2.8.1 - 2017-11-28 255 | 256 | ### Added 257 | 258 | - [#92](https://github.com/zendframework/zend-session/pull/92) adds PHP 7.2 259 | support. 260 | 261 | ### Deprecated 262 | 263 | - Nothing. 264 | 265 | ### Removed 266 | 267 | - Nothing. 268 | 269 | ### Fixed 270 | 271 | - [#57](https://github.com/zendframework/zend-session/pull/57) and 272 | [#93](https://github.com/zendframework/zend-session/pull/93) provide a fix 273 | for when data found in the session is a `Traversable`; such data is now cast 274 | to an array before merging with new data. 275 | 276 | ## 2.8.0 - 2017-06-19 277 | 278 | ### Added 279 | 280 | - [#78](https://github.com/zendframework/zend-session/pull/78) adds support for 281 | PHP 7.1, and specifically the following options: 282 | - `session.sid_length` 283 | - `session.sid_bits_per_character` 284 | 285 | ### Changed 286 | 287 | - [#73](https://github.com/zendframework/zend-session/pull/73) modifies the 288 | `SessionManagerFactory` to take into account the `$requestedName`; if the 289 | `$requestedName` is the name of a class that implements `ManagerInterface`, 290 | that class will be instantiated instead of `SessionManager`, but using the 291 | same arguments (`$config, $storage, $savehandler, $validators, $options`). 292 | 293 | - [#78](https://github.com/zendframework/zend-session/pull/78) updates the 294 | `SessionConfig` class to emit deprecation notices under PHP 7.1+ when a user 295 | attempts to set INI options no longer supported by PHP 7.1+, including: 296 | - `session.entropy_file` 297 | - `session.entropy_length` 298 | - `session.hash_function` 299 | - `session.hash_bits_per_character` 300 | 301 | ### Deprecated 302 | 303 | - Nothing. 304 | 305 | ### Removed 306 | 307 | - [#78](https://github.com/zendframework/zend-session/pull/78) removes support 308 | for PHP 5.5. 309 | 310 | - [#78](https://github.com/zendframework/zend-session/pull/78) removes support 311 | for HHVM. 312 | 313 | ### Fixed 314 | 315 | - Nothing. 316 | 317 | ## 2.7.4 - 2017-06-19 318 | 319 | ### Added 320 | 321 | - Nothing. 322 | 323 | ### Deprecated 324 | 325 | - Nothing. 326 | 327 | ### Removed 328 | 329 | - Nothing. 330 | 331 | ### Fixed 332 | 333 | - [#66](https://github.com/zendframework/zend-session/pull/66) fixes how the 334 | `Cache` save handler's `destroy()` method works, ensuring it does not attempt 335 | to remove an item by `$id` if it does not already exist in the cache. 336 | - [#79](https://github.com/zendframework/zend-session/pull/79) updates the 337 | signature of `AbstractContainer::offsetGet()` to match 338 | `Zend\Stdlib\ArrayObject` and return by reference, fixing an issue when 339 | running under PHP 7.1+. 340 | 341 | ## 2.7.3 - 2016-07-05 342 | 343 | ### Added 344 | 345 | - Nothing. 346 | 347 | ### Deprecated 348 | 349 | - Nothing. 350 | 351 | ### Removed 352 | 353 | - Nothing. 354 | 355 | ### Fixed 356 | 357 | - [#51](https://github.com/zendframework/zend-session/pull/51) provides a fix to 358 | the `DbTableGateway` save handler to prevent infinite recursion when 359 | attempting to destroy an expired record during initial read operations. 360 | - [#45](https://github.com/zendframework/zend-session/pull/45) updates the 361 | `SessionManager::regenerateId()` method to only regenerate the identifier if a 362 | session already exists. 363 | 364 | ## 2.7.2 - 2016-06-24 365 | 366 | ### Added 367 | 368 | - Nothing. 369 | 370 | ### Deprecated 371 | 372 | - Nothing. 373 | 374 | ### Removed 375 | 376 | - Nothing. 377 | 378 | ### Fixed 379 | 380 | - [#46](https://github.com/zendframework/zend-session/pull/46) provides fixes to 381 | each of the `Cache` and `DbTaleGateway` save handlers to ensure they work 382 | when used under PHP 7. 383 | 384 | ## 2.7.1 - 2016-05-11 385 | 386 | ### Added 387 | 388 | - [#40](https://github.com/zendframework/zend-session/pull/40) adds and 389 | publishes the documentation to https://zendframework.github.io/zend-session/ 390 | 391 | ### Deprecated 392 | 393 | - Nothing. 394 | 395 | ### Removed 396 | 397 | - Nothing. 398 | 399 | ### Fixed 400 | 401 | - [#38](https://github.com/zendframework/zend-session/pull/38) ensures that the 402 | value from `session.gc_maxlifetime` is cast to an integer before assigning 403 | it as the `lifetime` value in the `MongoDB` adapter, ensuring sessions may be 404 | deleted. 405 | 406 | ## 2.7.0 - 2016-04-12 407 | 408 | ### Added 409 | 410 | - [#23](https://github.com/zendframework/zend-session/pull/23) provides a new 411 | `Id` validator to ensure that the session identifier is not malformed. This 412 | validator is now enabled by default; to disable it, pass 413 | `['attach_default_validators' => false]` as the fifth argument to 414 | `SessionManager`, or pass an `options` array with that value under the 415 | `session_manager` configuration key. 416 | - [#34](https://github.com/zendframework/zend-session/pull/34) adds the option 417 | to use `exporeAfterSeconds` with the `MongoDB` save handler. 418 | - [#37](https://github.com/zendframework/zend-session/pull/37) exposes the 419 | package as a standalone config-provider/component, adding: 420 | - `Zend\Session\ConfigProvider`, which maps the default services offered by 421 | the package, including the `ContainerAbstractServiceFactory`. 422 | - `Zend\Session\Module`, which does the same, but for zend-mvc contexts. 423 | 424 | ### Deprecated 425 | 426 | - Nothing. 427 | 428 | ### Removed 429 | 430 | - Nothing. 431 | 432 | ### Fixed 433 | 434 | - [#34](https://github.com/zendframework/zend-session/pull/34) updates the 435 | component to use ext/mongodb + the MongoDB PHP client library, instead of 436 | ext/mongo, for purposes of the `MongoDB` save handler, allowing the component 437 | to be used with modern MongoDB installations. 438 | 439 | ## 2.6.2 - 2016-02-25 440 | 441 | ### Added 442 | 443 | - Nothing. 444 | 445 | ### Deprecated 446 | 447 | - Nothing. 448 | 449 | ### Removed 450 | 451 | - Nothing. 452 | 453 | ### Fixed 454 | 455 | - [#32](https://github.com/zendframework/zend-session/pull/32) provides a better 456 | polfill for the `ValidatorChain` to ensure it can be represented in 457 | auto-generated classmaps (e.g., via `composer dump-autoload --optimize` and/or 458 | `composer dump-autoload --classmap-authoritative`). 459 | 460 | ## 2.6.1 - 2016-02-23 461 | 462 | ### Added 463 | 464 | - Nothing. 465 | 466 | ### Deprecated 467 | 468 | - Nothing. 469 | 470 | ### Removed 471 | 472 | - Nothing. 473 | 474 | ### Fixed 475 | 476 | - [#29](https://github.com/zendframework/zend-session/pull/29) extracts the 477 | constructor defined in `Zend\Session\Validator\ValidatorChainTrait` and pushes 478 | it into each of the `ValidatorChainEM2` and `ValidatorChainEM3` 479 | implementations, to prevent colliding constructor definitions due to 480 | inheritance + trait usage. 481 | 482 | ## 2.6.0 - 2016-02-23 483 | 484 | ### Added 485 | 486 | - [#29](https://github.com/zendframework/zend-session/pull/29) adds two new 487 | classes: `Zend\Session\Validator\ValidatorChainEM2` and `ValidatorChainEM3`. 488 | Due to differences in the `EventManagerInterface::attach()` method between 489 | zend-eventmanager v2 and v3, and the fact that `ValidatorChain` overrides that 490 | method, we now need an implementation targeting each major version. To provide 491 | a consistent use case, we use a polyfill that aliases the appropriate version 492 | to the `Zend\Session\ValidatorChain` class. 493 | 494 | ### Deprecated 495 | 496 | - Nothing. 497 | 498 | ### Removed 499 | 500 | - Nothing. 501 | 502 | ### Fixed 503 | 504 | - [#29](https://github.com/zendframework/zend-session/pull/29) updates the code 505 | to be forwards compatible with the v3 releases of zend-eventmanager and 506 | zend-servicemanager. 507 | - [#7](https://github.com/zendframework/zend-session/pull/7) Mongo save handler 508 | was using sprintf formatting without sprintf. 509 | 510 | ## 2.5.2 - 2015-07-29 511 | 512 | ### Added 513 | 514 | - Nothing. 515 | 516 | ### Deprecated 517 | 518 | - Nothing. 519 | 520 | ### Removed 521 | 522 | - Nothing. 523 | 524 | ### Fixed 525 | 526 | - [#3](https://github.com/zendframework/zend-session/pull/3) Utilize 527 | SaveHandlerInterface vs. our own. 528 | 529 | - [#2](https://github.com/zendframework/zend-session/pull/2) detect session 530 | exists by use of *PHP_SESSION_ACTIVE* 531 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-2019, Zend Technologies USA, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zend-session 2 | 3 | > ## Repository abandoned 2019-12-31 4 | > 5 | > This repository has moved to [laminas/laminas-session](https://github.com/laminas/laminas-session). 6 | 7 | [](https://secure.travis-ci.org/zendframework/zend-session) 8 | [](https://coveralls.io/github/zendframework/zend-session?branch=master) 9 | 10 | zend-session manages PHP sessions using an object oriented interface. 11 | 12 | - File issues at https://github.com/zendframework/zend-session/issues 13 | - Documentation is at https://docs.zendframework.com/zend-session/ 14 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zendframework/zend-session", 3 | "description": "Object-oriented interface to PHP sessions and storage", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "zf", 7 | "zendframework", 8 | "session" 9 | ], 10 | "support": { 11 | "docs": "https://docs.zendframework.com/zend-session/", 12 | "issues": "https://github.com/zendframework/zend-session/issues", 13 | "source": "https://github.com/zendframework/zend-session", 14 | "rss": "https://github.com/zendframework/zend-session/releases.atom", 15 | "chat": "https://zendframework-slack.herokuapp.com", 16 | "forum": "https://discourse.zendframework.com/c/questions/components" 17 | }, 18 | "require": { 19 | "php": "^5.6 || ^7.0", 20 | "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", 21 | "zendframework/zend-stdlib": "^3.2.1" 22 | }, 23 | "require-dev": { 24 | "container-interop/container-interop": "^1.1", 25 | "mongodb/mongodb": "^1.0.1", 26 | "php-mock/php-mock-phpunit": "^1.1.2 || ^2.0", 27 | "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16", 28 | "zendframework/zend-cache": "^2.6.1", 29 | "zendframework/zend-coding-standard": "~1.0.0", 30 | "zendframework/zend-db": "^2.7", 31 | "zendframework/zend-http": "^2.5.4", 32 | "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", 33 | "zendframework/zend-validator": "^2.6" 34 | }, 35 | "suggest": { 36 | "mongodb/mongodb": "If you want to use the MongoDB session save handler", 37 | "zendframework/zend-cache": "Zend\\Cache component", 38 | "zendframework/zend-db": "Zend\\Db component", 39 | "zendframework/zend-http": "Zend\\Http component", 40 | "zendframework/zend-servicemanager": "Zend\\ServiceManager component", 41 | "zendframework/zend-validator": "Zend\\Validator component" 42 | }, 43 | "autoload": { 44 | "psr-4": { 45 | "Zend\\Session\\": "src/" 46 | } 47 | }, 48 | "autoload-dev": { 49 | "files": [ 50 | "test/autoload.php" 51 | ], 52 | "psr-4": { 53 | "ZendTest\\Session\\": "test/" 54 | } 55 | }, 56 | "config": { 57 | "sort-packages": true 58 | }, 59 | "extra": { 60 | "branch-alias": { 61 | "dev-master": "2.9.x-dev", 62 | "dev-develop": "2.10.x-dev" 63 | }, 64 | "zf": { 65 | "component": "Zend\\Session", 66 | "config-provider": "Zend\\Session\\ConfigProvider" 67 | } 68 | }, 69 | "scripts": { 70 | "check": [ 71 | "@cs-check", 72 | "@test" 73 | ], 74 | "cs-check": "phpcs", 75 | "cs-fix": "phpcbf", 76 | "test": "phpunit --colors=always", 77 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/AbstractContainer.php: -------------------------------------------------------------------------------- 1 | name = $name; 75 | $this->setManager($manager); 76 | 77 | // Create namespace 78 | parent::__construct([], ArrayObject::ARRAY_AS_PROPS); 79 | 80 | // Start session 81 | $this->getManager()->start(); 82 | } 83 | 84 | /** 85 | * Set the default ManagerInterface instance to use when none provided to constructor 86 | * 87 | * @param Manager $manager 88 | * @return void 89 | */ 90 | public static function setDefaultManager(Manager $manager = null) 91 | { 92 | static::$defaultManager = $manager; 93 | } 94 | 95 | /** 96 | * Get the default ManagerInterface instance 97 | * 98 | * If none provided, instantiates one of type {@link $managerDefaultClass} 99 | * 100 | * @return Manager 101 | * @throws Exception\InvalidArgumentException if invalid manager default class provided 102 | */ 103 | public static function getDefaultManager() 104 | { 105 | if (null === static::$defaultManager) { 106 | $manager = new static::$managerDefaultClass(); 107 | if (! $manager instanceof Manager) { 108 | throw new Exception\InvalidArgumentException( 109 | 'Invalid default manager type provided; must implement ManagerInterface' 110 | ); 111 | } 112 | static::$defaultManager = $manager; 113 | } 114 | 115 | return static::$defaultManager; 116 | } 117 | 118 | /** 119 | * Get container name 120 | * 121 | * @return string 122 | */ 123 | public function getName() 124 | { 125 | return $this->name; 126 | } 127 | 128 | /** 129 | * Set session manager 130 | * 131 | * @param null|Manager $manager 132 | * @return Container 133 | * @throws Exception\InvalidArgumentException 134 | */ 135 | protected function setManager(Manager $manager = null) 136 | { 137 | if (null === $manager) { 138 | $manager = static::getDefaultManager(); 139 | if (! $manager instanceof Manager) { 140 | throw new Exception\InvalidArgumentException( 141 | 'Manager provided is invalid; must implement ManagerInterface' 142 | ); 143 | } 144 | } 145 | $this->manager = $manager; 146 | 147 | return $this; 148 | } 149 | 150 | /** 151 | * Get manager instance 152 | * 153 | * @return Manager 154 | */ 155 | public function getManager() 156 | { 157 | return $this->manager; 158 | } 159 | 160 | /** 161 | * Get session storage object 162 | * 163 | * Proxies to ManagerInterface::getStorage() 164 | * 165 | * @return Storage 166 | */ 167 | protected function getStorage() 168 | { 169 | return $this->getManager()->getStorage(); 170 | } 171 | 172 | /** 173 | * Create a new container object on which to act 174 | * 175 | * @return ArrayObject 176 | */ 177 | protected function createContainer() 178 | { 179 | return new ArrayObject([], ArrayObject::ARRAY_AS_PROPS); 180 | } 181 | 182 | /** 183 | * Verify container namespace 184 | * 185 | * Checks to see if a container exists within the Storage object already. 186 | * If not, one is created; if so, checks to see if it's an ArrayObject. 187 | * If not, it raises an exception; otherwise, it returns the Storage 188 | * object. 189 | * 190 | * @param bool $createContainer Whether or not to create the container for the namespace 191 | * @return Storage|null Returns null only if $createContainer is false 192 | * @throws Exception\RuntimeException 193 | */ 194 | protected function verifyNamespace($createContainer = true) 195 | { 196 | $storage = $this->getStorage(); 197 | $name = $this->getName(); 198 | if (! isset($storage[$name])) { 199 | if (! $createContainer) { 200 | return; 201 | } 202 | $storage[$name] = $this->createContainer(); 203 | } 204 | if (! is_array($storage[$name]) && ! $storage[$name] instanceof Traversable) { 205 | throw new Exception\RuntimeException('Container cannot write to storage due to type mismatch'); 206 | } 207 | 208 | return $storage; 209 | } 210 | 211 | /** 212 | * Determine whether a given key needs to be expired 213 | * 214 | * Returns true if the key has expired, false otherwise. 215 | * 216 | * @param null|string $key 217 | * @return bool 218 | */ 219 | protected function expireKeys($key = null) 220 | { 221 | $storage = $this->verifyNamespace(); 222 | $name = $this->getName(); 223 | 224 | // Return early if key not found 225 | if ((null !== $key) && ! isset($storage[$name][$key])) { 226 | return true; 227 | } 228 | 229 | if ($this->expireByExpiryTime($storage, $name, $key)) { 230 | return true; 231 | } 232 | 233 | if ($this->expireByHops($storage, $name, $key)) { 234 | return true; 235 | } 236 | 237 | return false; 238 | } 239 | 240 | /** 241 | * Expire a key by expiry time 242 | * 243 | * Checks to see if the entire container has expired based on TTL setting, 244 | * or the individual key. 245 | * 246 | * @param Storage $storage 247 | * @param string $name Container name 248 | * @param string $key Key in container to check 249 | * @return bool 250 | */ 251 | protected function expireByExpiryTime(Storage $storage, $name, $key) 252 | { 253 | $metadata = $storage->getMetadata($name); 254 | 255 | // Global container expiry 256 | if (is_array($metadata) 257 | && isset($metadata['EXPIRE']) 258 | && ($_SERVER['REQUEST_TIME'] > $metadata['EXPIRE']) 259 | ) { 260 | unset($metadata['EXPIRE']); 261 | $storage->setMetadata($name, $metadata, true); 262 | $storage[$name] = $this->createContainer(); 263 | 264 | return true; 265 | } 266 | 267 | // Expire individual key 268 | if ((null !== $key) 269 | && is_array($metadata) 270 | && isset($metadata['EXPIRE_KEYS']) 271 | && isset($metadata['EXPIRE_KEYS'][$key]) 272 | && ($_SERVER['REQUEST_TIME'] > $metadata['EXPIRE_KEYS'][$key]) 273 | ) { 274 | unset($metadata['EXPIRE_KEYS'][$key]); 275 | $storage->setMetadata($name, $metadata, true); 276 | unset($storage[$name][$key]); 277 | 278 | return true; 279 | } 280 | 281 | // Find any keys that have expired 282 | if ((null === $key) 283 | && is_array($metadata) 284 | && isset($metadata['EXPIRE_KEYS']) 285 | ) { 286 | foreach (array_keys($metadata['EXPIRE_KEYS']) as $key) { 287 | if ($_SERVER['REQUEST_TIME'] > $metadata['EXPIRE_KEYS'][$key]) { 288 | unset($metadata['EXPIRE_KEYS'][$key]); 289 | if (isset($storage[$name][$key])) { 290 | unset($storage[$name][$key]); 291 | } 292 | } 293 | } 294 | $storage->setMetadata($name, $metadata, true); 295 | 296 | return true; 297 | } 298 | 299 | return false; 300 | } 301 | 302 | /** 303 | * Expire key by session hops 304 | * 305 | * Determines whether the container or an individual key within it has 306 | * expired based on session hops 307 | * 308 | * @param Storage $storage 309 | * @param string $name 310 | * @param string $key 311 | * @return bool 312 | */ 313 | protected function expireByHops(Storage $storage, $name, $key) 314 | { 315 | $ts = $storage->getRequestAccessTime(); 316 | $metadata = $storage->getMetadata($name); 317 | 318 | // Global container expiry 319 | if (is_array($metadata) 320 | && isset($metadata['EXPIRE_HOPS']) 321 | && ($ts > $metadata['EXPIRE_HOPS']['ts']) 322 | ) { 323 | $metadata['EXPIRE_HOPS']['hops']--; 324 | if (-1 === $metadata['EXPIRE_HOPS']['hops']) { 325 | unset($metadata['EXPIRE_HOPS']); 326 | $storage->setMetadata($name, $metadata, true); 327 | $storage[$name] = $this->createContainer(); 328 | 329 | return true; 330 | } 331 | $metadata['EXPIRE_HOPS']['ts'] = $ts; 332 | $storage->setMetadata($name, $metadata, true); 333 | 334 | return false; 335 | } 336 | 337 | // Single key expiry 338 | if ((null !== $key) 339 | && is_array($metadata) 340 | && isset($metadata['EXPIRE_HOPS_KEYS']) 341 | && isset($metadata['EXPIRE_HOPS_KEYS'][$key]) 342 | && ($ts > $metadata['EXPIRE_HOPS_KEYS'][$key]['ts']) 343 | ) { 344 | $metadata['EXPIRE_HOPS_KEYS'][$key]['hops']--; 345 | if (-1 === $metadata['EXPIRE_HOPS_KEYS'][$key]['hops']) { 346 | unset($metadata['EXPIRE_HOPS_KEYS'][$key]); 347 | $storage->setMetadata($name, $metadata, true); 348 | unset($storage[$name][$key]); 349 | 350 | return true; 351 | } 352 | $metadata['EXPIRE_HOPS_KEYS'][$key]['ts'] = $ts; 353 | $storage->setMetadata($name, $metadata, true); 354 | 355 | return false; 356 | } 357 | 358 | // Find all expired keys 359 | if ((null === $key) 360 | && is_array($metadata) 361 | && isset($metadata['EXPIRE_HOPS_KEYS']) 362 | ) { 363 | foreach (array_keys($metadata['EXPIRE_HOPS_KEYS']) as $key) { 364 | if ($ts > $metadata['EXPIRE_HOPS_KEYS'][$key]['ts']) { 365 | $metadata['EXPIRE_HOPS_KEYS'][$key]['hops']--; 366 | if (-1 === $metadata['EXPIRE_HOPS_KEYS'][$key]['hops']) { 367 | unset($metadata['EXPIRE_HOPS_KEYS'][$key]); 368 | $storage->setMetadata($name, $metadata, true); 369 | unset($storage[$name][$key]); 370 | continue; 371 | } 372 | $metadata['EXPIRE_HOPS_KEYS'][$key]['ts'] = $ts; 373 | } 374 | } 375 | $storage->setMetadata($name, $metadata, true); 376 | 377 | return false; 378 | } 379 | 380 | return false; 381 | } 382 | 383 | /** 384 | * Store a value within the container 385 | * 386 | * @param string $key 387 | * @param mixed $value 388 | * @return void 389 | */ 390 | public function offsetSet($key, $value) 391 | { 392 | $this->expireKeys($key); 393 | $storage = $this->verifyNamespace(); 394 | $name = $this->getName(); 395 | $storage[$name][$key] = $value; 396 | } 397 | 398 | /** 399 | * Determine if the key exists 400 | * 401 | * @param string $key 402 | * @return bool 403 | */ 404 | public function offsetExists($key) 405 | { 406 | // If no container exists, we can't inspect it 407 | if (null === ($storage = $this->verifyNamespace(false))) { 408 | return false; 409 | } 410 | $name = $this->getName(); 411 | 412 | // Return early if the key isn't set 413 | if (! isset($storage[$name][$key])) { 414 | return false; 415 | } 416 | 417 | $expired = $this->expireKeys($key); 418 | 419 | return ! $expired; 420 | } 421 | 422 | /** 423 | * Retrieve a specific key in the container 424 | * 425 | * @param string $key 426 | * @return mixed 427 | */ 428 | public function &offsetGet($key) 429 | { 430 | if (! $this->offsetExists($key)) { 431 | return $this->defaultValue; 432 | } 433 | $storage = $this->getStorage(); 434 | $name = $this->getName(); 435 | 436 | return $storage[$name][$key]; 437 | } 438 | 439 | /** 440 | * Unset a single key in the container 441 | * 442 | * @param string $key 443 | * @return void 444 | */ 445 | public function offsetUnset($key) 446 | { 447 | if (! $this->offsetExists($key)) { 448 | return; 449 | } 450 | $storage = $this->getStorage(); 451 | $name = $this->getName(); 452 | unset($storage[$name][$key]); 453 | } 454 | 455 | /** 456 | * Exchange the current array with another array or object. 457 | * 458 | * @param array|object $input 459 | * @return array Returns the old array 460 | * @see ArrayObject::exchangeArray() 461 | */ 462 | public function exchangeArray($input) 463 | { 464 | // handle arrayobject, iterators and the like: 465 | if (is_object($input) && ($input instanceof ArrayObject || $input instanceof \ArrayObject)) { 466 | $input = $input->getArrayCopy(); 467 | } 468 | if (! is_array($input)) { 469 | $input = (array) $input; 470 | } 471 | 472 | $storage = $this->verifyNamespace(); 473 | $name = $this->getName(); 474 | 475 | $old = $storage[$name]; 476 | $storage[$name] = $input; 477 | if ($old instanceof ArrayObject) { 478 | return $old->getArrayCopy(); 479 | } 480 | 481 | return $old; 482 | } 483 | 484 | /** 485 | * Iterate over session container 486 | * 487 | * @return Iterator 488 | */ 489 | public function getIterator() 490 | { 491 | $this->expireKeys(); 492 | $storage = $this->getStorage(); 493 | $container = $storage[$this->getName()]; 494 | 495 | if ($container instanceof Traversable) { 496 | return $container; 497 | } 498 | 499 | return new ArrayIterator($container); 500 | } 501 | 502 | /** 503 | * Set expiration TTL 504 | * 505 | * Set the TTL for the entire container, a single key, or a set of keys. 506 | * 507 | * @param int $ttl TTL in seconds 508 | * @param string|array|null $vars 509 | * @return Container 510 | * @throws Exception\InvalidArgumentException 511 | */ 512 | public function setExpirationSeconds($ttl, $vars = null) 513 | { 514 | $storage = $this->getStorage(); 515 | $ts = time() + $ttl; 516 | if (is_scalar($vars) && null !== $vars) { 517 | $vars = (array) $vars; 518 | } 519 | 520 | if (null === $vars) { 521 | $this->expireKeys(); // first we need to expire global key, since it can already be expired 522 | $data = ['EXPIRE' => $ts]; 523 | } elseif (is_array($vars)) { 524 | // Cannot pass "$this" to a lambda 525 | $container = $this; 526 | 527 | // Filter out any items not in our container 528 | $expires = array_filter($vars, function ($value) use ($container) { 529 | return $container->offsetExists($value); 530 | }); 531 | 532 | // Map item keys => timestamp 533 | $expires = array_flip($expires); 534 | $expires = array_map(function () use ($ts) { 535 | return $ts; 536 | }, $expires); 537 | 538 | // Create metadata array to merge in 539 | $data = ['EXPIRE_KEYS' => $expires]; 540 | } else { 541 | throw new Exception\InvalidArgumentException( 542 | 'Unknown data provided as second argument to ' . __METHOD__ 543 | ); 544 | } 545 | 546 | $storage->setMetadata( 547 | $this->getName(), 548 | $data 549 | ); 550 | 551 | return $this; 552 | } 553 | 554 | /** 555 | * Set expiration hops for the container, a single key, or set of keys 556 | * 557 | * @param int $hops 558 | * @param null|string|array $vars 559 | * @throws Exception\InvalidArgumentException 560 | * @return Container 561 | */ 562 | public function setExpirationHops($hops, $vars = null) 563 | { 564 | $storage = $this->getStorage(); 565 | $ts = $storage->getRequestAccessTime(); 566 | 567 | if (is_scalar($vars) && (null !== $vars)) { 568 | $vars = (array) $vars; 569 | } 570 | 571 | if (null === $vars) { 572 | $this->expireKeys(); // first we need to expire global key, since it can already be expired 573 | $data = ['EXPIRE_HOPS' => ['hops' => $hops, 'ts' => $ts]]; 574 | } elseif (is_array($vars)) { 575 | // Cannot pass "$this" to a lambda 576 | $container = $this; 577 | 578 | // FilterInterface out any items not in our container 579 | $expires = array_filter($vars, function ($value) use ($container) { 580 | return $container->offsetExists($value); 581 | }); 582 | 583 | // Map item keys => timestamp 584 | $expires = array_flip($expires); 585 | $expires = array_map(function () use ($hops, $ts) { 586 | return ['hops' => $hops, 'ts' => $ts]; 587 | }, $expires); 588 | 589 | // Create metadata array to merge in 590 | $data = ['EXPIRE_HOPS_KEYS' => $expires]; 591 | } else { 592 | throw new Exception\InvalidArgumentException( 593 | 'Unknown data provided as second argument to ' . __METHOD__ 594 | ); 595 | } 596 | 597 | $storage->setMetadata( 598 | $this->getName(), 599 | $data 600 | ); 601 | 602 | return $this; 603 | } 604 | 605 | /** 606 | * Creates a copy of the specific container name 607 | * 608 | * @return array 609 | */ 610 | public function getArrayCopy() 611 | { 612 | $storage = $this->verifyNamespace(); 613 | $container = $storage[$this->getName()]; 614 | 615 | return $container instanceof ArrayObject ? $container->getArrayCopy() : $container; 616 | } 617 | } 618 | -------------------------------------------------------------------------------- /src/AbstractManager.php: -------------------------------------------------------------------------------- 1 | defaultConfigClass)) { 72 | throw new Exception\RuntimeException(sprintf( 73 | 'Unable to locate config class "%s"; class does not exist', 74 | $this->defaultConfigClass 75 | )); 76 | } 77 | 78 | $config = new $this->defaultConfigClass(); 79 | 80 | if (! $config instanceof Config) { 81 | throw new Exception\RuntimeException(sprintf( 82 | 'Default config class %s is invalid; must implement %s\Config\ConfigInterface', 83 | $this->defaultConfigClass, 84 | __NAMESPACE__ 85 | )); 86 | } 87 | } 88 | 89 | $this->config = $config; 90 | 91 | // init storage 92 | if ($storage === null) { 93 | if (! class_exists($this->defaultStorageClass)) { 94 | throw new Exception\RuntimeException(sprintf( 95 | 'Unable to locate storage class "%s"; class does not exist', 96 | $this->defaultStorageClass 97 | )); 98 | } 99 | 100 | $storage = new $this->defaultStorageClass(); 101 | 102 | if (! $storage instanceof Storage) { 103 | throw new Exception\RuntimeException(sprintf( 104 | 'Default storage class %s is invalid; must implement %s\Storage\StorageInterface', 105 | $this->defaultConfigClass, 106 | __NAMESPACE__ 107 | )); 108 | } 109 | } 110 | 111 | $this->storage = $storage; 112 | 113 | // save handler 114 | if ($saveHandler !== null) { 115 | $this->saveHandler = $saveHandler; 116 | } 117 | 118 | $this->validators = $validators; 119 | } 120 | 121 | /** 122 | * Set configuration object 123 | * 124 | * @param Config $config 125 | * @return AbstractManager 126 | */ 127 | public function setConfig(Config $config) 128 | { 129 | $this->config = $config; 130 | return $this; 131 | } 132 | 133 | /** 134 | * Retrieve configuration object 135 | * 136 | * @return Config 137 | */ 138 | public function getConfig() 139 | { 140 | return $this->config; 141 | } 142 | 143 | /** 144 | * Set session storage object 145 | * 146 | * @param Storage $storage 147 | * @return AbstractManager 148 | */ 149 | public function setStorage(Storage $storage) 150 | { 151 | $this->storage = $storage; 152 | return $this; 153 | } 154 | 155 | /** 156 | * Retrieve storage object 157 | * 158 | * @return Storage 159 | */ 160 | public function getStorage() 161 | { 162 | return $this->storage; 163 | } 164 | 165 | /** 166 | * Set session save handler object 167 | * 168 | * @param SaveHandler $saveHandler 169 | * @return AbstractManager 170 | */ 171 | public function setSaveHandler(SaveHandler $saveHandler) 172 | { 173 | $this->saveHandler = $saveHandler; 174 | return $this; 175 | } 176 | 177 | /** 178 | * Get SaveHandler Object 179 | * 180 | * @return SaveHandler 181 | */ 182 | public function getSaveHandler() 183 | { 184 | return $this->saveHandler; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/Config/ConfigInterface.php: -------------------------------------------------------------------------------- 1 | setPhpSaveHandler($value); 102 | return $this; 103 | default: 104 | return parent::setOption($option, $value); 105 | } 106 | } 107 | 108 | /** 109 | * Set storage option in backend configuration store 110 | * 111 | * @param string $storageName 112 | * @param mixed $storageValue 113 | * @return SessionConfig 114 | * @throws Exception\InvalidArgumentException 115 | */ 116 | public function setStorageOption($storageName, $storageValue) 117 | { 118 | switch ($storageName) { 119 | case 'remember_me_seconds': 120 | // do nothing; not an INI option 121 | return; 122 | case 'url_rewriter_tags': 123 | $key = 'url_rewriter.tags'; 124 | break; 125 | case 'save_handler': 126 | // Save handlers must be treated differently due to changes 127 | // introduced in PHP 7.2. Do not alter running INI setting. 128 | return $this; 129 | default: 130 | $key = 'session.' . $storageName; 131 | break; 132 | } 133 | 134 | $iniGet = ini_get($key); 135 | $storageValue = (string) $storageValue; 136 | if (false !== $iniGet && (string) $iniGet === $storageValue) { 137 | return $this; 138 | } 139 | 140 | $sessionRequiresRestart = false; 141 | if (session_status() == PHP_SESSION_ACTIVE) { 142 | session_write_close(); 143 | $sessionRequiresRestart = true; 144 | } 145 | 146 | $result = ini_set($key, $storageValue); 147 | 148 | if ($sessionRequiresRestart) { 149 | session_start(); 150 | } 151 | 152 | if (false === $result) { 153 | throw new Exception\InvalidArgumentException( 154 | "'{$key}' is not a valid sessions-related ini setting." 155 | ); 156 | } 157 | return $this; 158 | } 159 | 160 | /** 161 | * Retrieve a storage option from a backend configuration store 162 | * 163 | * Used to retrieve default values from a backend configuration store. 164 | * 165 | * @param string $storageOption 166 | * @return mixed 167 | */ 168 | public function getStorageOption($storageOption) 169 | { 170 | switch ($storageOption) { 171 | case 'remember_me_seconds': 172 | // No remote storage option; just return the current value 173 | return $this->rememberMeSeconds; 174 | case 'url_rewriter_tags': 175 | return ini_get('url_rewriter.tags'); 176 | // The following all need a transformation on the retrieved value; 177 | // however they use the same key naming scheme 178 | case 'use_cookies': 179 | case 'use_only_cookies': 180 | case 'use_trans_sid': 181 | case 'cookie_httponly': 182 | return (bool) ini_get('session.' . $storageOption); 183 | case 'save_handler': 184 | // Save handlers must be treated differently due to changes 185 | // introduced in PHP 7.2. 186 | return $this->saveHandler ?: session_module_name(); 187 | default: 188 | return ini_get('session.' . $storageOption); 189 | } 190 | } 191 | 192 | /** 193 | * Proxy to setPhpSaveHandler() 194 | * 195 | * Prevents calls to `setSaveHandler()` from hitting `setOption()` instead, 196 | * and thus bypassing the logic of `setPhpSaveHandler()`. 197 | * 198 | * @param string $phpSaveHandler 199 | * @return SessionConfig 200 | * @throws Exception\InvalidArgumentException 201 | */ 202 | public function setSaveHandler($phpSaveHandler) 203 | { 204 | return $this->setPhpSaveHandler($phpSaveHandler); 205 | } 206 | 207 | /** 208 | * Set session.save_handler 209 | * 210 | * @param string $phpSaveHandler 211 | * @return SessionConfig 212 | * @throws Exception\InvalidArgumentException 213 | */ 214 | public function setPhpSaveHandler($phpSaveHandler) 215 | { 216 | $this->saveHandler = $this->performSaveHandlerUpdate($phpSaveHandler); 217 | $this->options['save_handler'] = $this->saveHandler; 218 | return $this; 219 | } 220 | 221 | /** 222 | * Set session.save_path 223 | * 224 | * @param string $savePath 225 | * @return SessionConfig 226 | * @throws Exception\InvalidArgumentException on invalid path 227 | */ 228 | public function setSavePath($savePath) 229 | { 230 | if ($this->getOption('save_handler') === 'files') { 231 | parent::setSavePath($savePath); 232 | } 233 | $this->savePath = $savePath; 234 | $this->setOption('save_path', $savePath); 235 | return $this; 236 | } 237 | 238 | /** 239 | * Set session.serialize_handler 240 | * 241 | * @param string $serializeHandler 242 | * @return SessionConfig 243 | * @throws Exception\InvalidArgumentException 244 | */ 245 | public function setSerializeHandler($serializeHandler) 246 | { 247 | $serializeHandler = (string) $serializeHandler; 248 | 249 | set_error_handler([$this, 'handleError']); 250 | ini_set('session.serialize_handler', $serializeHandler); 251 | restore_error_handler(); 252 | if ($this->phpErrorCode >= E_WARNING) { 253 | throw new Exception\InvalidArgumentException('Invalid serialize handler specified'); 254 | } 255 | 256 | $this->serializeHandler = (string) $serializeHandler; 257 | return $this; 258 | } 259 | 260 | // session.cache_limiter 261 | 262 | /** 263 | * Set cache limiter 264 | * 265 | * @param $cacheLimiter 266 | * @return SessionConfig 267 | * @throws Exception\InvalidArgumentException 268 | */ 269 | public function setCacheLimiter($cacheLimiter) 270 | { 271 | $cacheLimiter = (string) $cacheLimiter; 272 | if (! in_array($cacheLimiter, $this->validCacheLimiters)) { 273 | throw new Exception\InvalidArgumentException('Invalid cache limiter provided'); 274 | } 275 | $this->setOption('cache_limiter', $cacheLimiter); 276 | ini_set('session.cache_limiter', $cacheLimiter); 277 | return $this; 278 | } 279 | 280 | /** 281 | * Set session.hash_function 282 | * 283 | * @param string|int $hashFunction 284 | * @return SessionConfig 285 | * @throws Exception\InvalidArgumentException 286 | */ 287 | public function setHashFunction($hashFunction) 288 | { 289 | if (PHP_VERSION_ID >= 70100) { 290 | trigger_error('session.hash_function is removed starting with PHP 7.1', E_USER_DEPRECATED); 291 | } 292 | 293 | $hashFunction = (string) $hashFunction; 294 | $validHashFunctions = $this->getHashFunctions(); 295 | if (! in_array($hashFunction, $validHashFunctions, true)) { 296 | throw new Exception\InvalidArgumentException('Invalid hash function provided'); 297 | } 298 | 299 | $this->setOption('hash_function', $hashFunction); 300 | ini_set('session.hash_function', $hashFunction); 301 | return $this; 302 | } 303 | 304 | /** 305 | * Set session.hash_bits_per_character 306 | * 307 | * @param int $hashBitsPerCharacter 308 | * @return SessionConfig 309 | * @throws Exception\InvalidArgumentException 310 | */ 311 | public function setHashBitsPerCharacter($hashBitsPerCharacter) 312 | { 313 | if (PHP_VERSION_ID >= 70100) { 314 | trigger_error('session.hash_bits_per_character is removed starting with PHP 7.1', E_USER_DEPRECATED); 315 | } 316 | 317 | if (! is_numeric($hashBitsPerCharacter) 318 | || ! in_array($hashBitsPerCharacter, $this->validHashBitsPerCharacters) 319 | ) { 320 | throw new Exception\InvalidArgumentException('Invalid hash bits per character provided'); 321 | } 322 | 323 | $hashBitsPerCharacter = (int) $hashBitsPerCharacter; 324 | $this->setOption('hash_bits_per_character', $hashBitsPerCharacter); 325 | ini_set('session.hash_bits_per_character', $hashBitsPerCharacter); 326 | return $this; 327 | } 328 | 329 | /** 330 | * Set session.sid_bits_per_character 331 | * 332 | * @param int $sidBitsPerCharacter 333 | * @return SessionConfig 334 | * @throws Exception\InvalidArgumentException 335 | */ 336 | public function setSidBitsPerCharacter($sidBitsPerCharacter) 337 | { 338 | if (! is_numeric($sidBitsPerCharacter) 339 | || ! in_array($sidBitsPerCharacter, $this->validSidBitsPerCharacters) 340 | ) { 341 | throw new Exception\InvalidArgumentException('Invalid sid bits per character provided'); 342 | } 343 | 344 | $sidBitsPerCharacter = (int) $sidBitsPerCharacter; 345 | $this->setOption('sid_bits_per_character', $sidBitsPerCharacter); 346 | ini_set('session.sid_bits_per_character', $sidBitsPerCharacter); 347 | return $this; 348 | } 349 | 350 | /** 351 | * Retrieve list of valid hash functions 352 | * 353 | * @return array 354 | */ 355 | protected function getHashFunctions() 356 | { 357 | if (empty($this->validHashFunctions)) { 358 | /** 359 | * @link http://php.net/manual/en/session.configuration.php#ini.session.hash-function 360 | * "0" and "1" refer to MD5-128 and SHA1-160, respectively, and are 361 | * valid in addition to whatever is reported by hash_algos() 362 | */ 363 | $this->validHashFunctions = array_merge(['0', '1'], hash_algos()); 364 | } 365 | return $this->validHashFunctions; 366 | } 367 | 368 | /** 369 | * Handle PHP errors 370 | * 371 | * @param int $code 372 | * @param string $message 373 | * @return void 374 | */ 375 | protected function handleError($code, $message) 376 | { 377 | $this->phpErrorCode = $code; 378 | $this->phpErrorMessage = $message; 379 | } 380 | 381 | /** 382 | * Determine what save handlers are available. 383 | * 384 | * The only way to get at this information is via phpinfo(), and the output 385 | * of that function varies based on the SAPI. 386 | * 387 | * Strips the handler "user" from the list, as PHP 7.2 does not allow 388 | * setting that as a handler, because it essentially requires you to have 389 | * already set a custom handler via `session_set_save_handler()`. It 390 | * wasn't really valid in prior versions, either; the language simply did 391 | * not complain previously. 392 | * 393 | * @return array 394 | */ 395 | private function locateRegisteredSaveHandlers() 396 | { 397 | if (null !== $this->knownSaveHandlers) { 398 | return $this->knownSaveHandlers; 399 | } 400 | 401 | if (! preg_match('#Registered save handlers.*#m', $this->getPhpInfoForModules(), $matches)) { 402 | $this->knownSaveHandlers = []; 403 | return $this->knownSaveHandlers; 404 | } 405 | 406 | $content = array_shift($matches); 407 | 408 | $handlers = false !== strpos($content, '') 409 | ? $this->parseSaveHandlersFromHtml($content) 410 | : $this->parseSaveHandlersFromPlainText($content); 411 | 412 | if (false !== ($index = array_search('user', $handlers, true))) { 413 | unset($handlers[$index]); 414 | } 415 | 416 | $this->knownSaveHandlers = $handlers; 417 | 418 | return $this->knownSaveHandlers; 419 | } 420 | 421 | /** 422 | * Perform a session.save_handler update. 423 | * 424 | * Determines if the save handler represents a PHP built-in 425 | * save handler, and, if so, passes that value to session_module_name 426 | * in order to activate it. The save handler name is then returned. 427 | * 428 | * If it is not, it tests to see if it is a SessionHandlerInterface 429 | * implementation. If the string is a class implementing that interface, 430 | * it creates an instance of it. In such cases, it then calls 431 | * session_set_save_handler to activate it. The class name of the 432 | * handler is returned. 433 | * 434 | * In all other cases, an exception is raised. 435 | * 436 | * @param string|SessionHandlerInterface $phpSaveHandler 437 | * @return string 438 | * @throws Exception\InvalidArgumentException if an error occurs when 439 | * setting a PHP session save handler module. 440 | * @throws Exception\InvalidArgumentException if the $phpSaveHandler 441 | * is a string that does not represent a class implementing 442 | * SessionHandlerInterface. 443 | * @throws Exception\InvalidArgumentException if $phpSaveHandler is 444 | * a non-string value that does not implement SessionHandlerInterface. 445 | */ 446 | private function performSaveHandlerUpdate($phpSaveHandler) 447 | { 448 | if (is_string($phpSaveHandler)) { 449 | $knownHandlers = $this->locateRegisteredSaveHandlers(); 450 | 451 | if (in_array($phpSaveHandler, $knownHandlers, true)) { 452 | $phpSaveHandler = strtolower($phpSaveHandler); 453 | set_error_handler([$this, 'handleError']); 454 | session_module_name($phpSaveHandler); 455 | restore_error_handler(); 456 | if ($this->phpErrorCode >= E_WARNING) { 457 | throw new Exception\InvalidArgumentException(sprintf( 458 | 'Error setting session save handler module "%s": %s', 459 | $phpSaveHandler, 460 | $this->phpErrorMessage 461 | )); 462 | } 463 | 464 | return $phpSaveHandler; 465 | } 466 | 467 | if (! class_exists($phpSaveHandler) 468 | || ! is_a($phpSaveHandler, SessionHandlerInterface::class, true) 469 | ) { 470 | throw new Exception\InvalidArgumentException(sprintf( 471 | 'Invalid save handler specified ("%s"); must be one of [%s]' 472 | . ' or a class implementing %s', 473 | $phpSaveHandler, 474 | implode(', ', $knownHandlers), 475 | SessionHandlerInterface::class 476 | )); 477 | } 478 | 479 | $phpSaveHandler = new $phpSaveHandler(); 480 | } 481 | 482 | if (! $phpSaveHandler instanceof SessionHandlerInterface) { 483 | throw new Exception\InvalidArgumentException(sprintf( 484 | 'Invalid save handler specified ("%s"); must implement %s', 485 | get_class($phpSaveHandler), 486 | SessionHandlerInterface::class 487 | )); 488 | } 489 | 490 | session_set_save_handler($phpSaveHandler); 491 | 492 | return get_class($phpSaveHandler); 493 | } 494 | 495 | /** 496 | * Grab module information from phpinfo. 497 | * 498 | * Requires capturing an output buffer, as phpinfo does not have an option 499 | * to return the value as a string. 500 | * 501 | * @return string 502 | */ 503 | private function getPhpInfoForModules() 504 | { 505 | ob_start(); 506 | phpinfo(INFO_MODULES); 507 | return ob_get_clean(); 508 | } 509 | 510 | /** 511 | * Parse a list of PHP session save handlers from HTML. 512 | * 513 | * Format is "
34 | * $container = $services->get('MySessionContainer');
35 | *
36 | */
37 | class ContainerAbstractServiceFactory implements AbstractFactoryInterface
38 | {
39 | /**
40 | * Cached container configuration
41 | *
42 | * @var array
43 | */
44 | protected $config;
45 |
46 | /**
47 | * Configuration key in which session containers live
48 | *
49 | * @var string
50 | */
51 | protected $configKey = 'session_containers';
52 |
53 | /**
54 | * @var ManagerInterface
55 | */
56 | protected $sessionManager;
57 |
58 | /**
59 | * Can we create an instance of the given service? (v3 usage).
60 | *
61 | * @param ContainerInterface $container
62 | * @param string $requestedName
63 | * @return bool
64 | */
65 | public function canCreate(ContainerInterface $container, $requestedName)
66 | {
67 | $config = $this->getConfig($container);
68 | if (empty($config)) {
69 | return false;
70 | }
71 |
72 | $containerName = $this->normalizeContainerName($requestedName);
73 | return array_key_exists($containerName, $config);
74 | }
75 |
76 | /**
77 | * Can we create an instance of the given service? (v2 usage)
78 | *
79 | * @param ServiceLocatorInterface $container
80 | * @param string $name
81 | * @param string $requestedName
82 | * @return bool
83 | */
84 | public function canCreateServiceWithName(ServiceLocatorInterface $container, $name, $requestedName)
85 | {
86 | return $this->canCreate($container, $requestedName);
87 | }
88 |
89 | /**
90 | * Create and return a named container (v3 usage).
91 | *
92 | * @param ContainerInterface $container
93 | * @param string $requestedName
94 | * @return Container
95 | */
96 | public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
97 | {
98 | $manager = $this->getSessionManager($container);
99 | return new Container($requestedName, $manager);
100 | }
101 |
102 | /**
103 | * Create and return a named container (v2 usage).
104 | *
105 | * @param ContainerInterface $container
106 | * @param string $requestedName
107 | * @return Container
108 | */
109 | public function createServiceWithName(ServiceLocatorInterface $container, $name, $requestedName)
110 | {
111 | return $this($container, $requestedName);
112 | }
113 |
114 | /**
115 | * Retrieve config from service locator, and cache for later
116 | *
117 | * @param ContainerInterface $container
118 | * @return false|array
119 | */
120 | protected function getConfig(ContainerInterface $container)
121 | {
122 | if (null !== $this->config) {
123 | return $this->config;
124 | }
125 |
126 | if (! $container->has('config')) {
127 | $this->config = [];
128 | return $this->config;
129 | }
130 |
131 | $config = $container->get('config');
132 | if (! isset($config[$this->configKey]) || ! is_array($config[$this->configKey])) {
133 | $this->config = [];
134 | return $this->config;
135 | }
136 |
137 | $config = $config[$this->configKey];
138 | $config = array_flip($config);
139 |
140 | $this->config = array_change_key_case($config);
141 |
142 | return $this->config;
143 | }
144 |
145 | /**
146 | * Retrieve the session manager instance, if any
147 | *
148 | * @param ContainerInterface $container
149 | * @return null|ManagerInterface
150 | */
151 | protected function getSessionManager(ContainerInterface $container)
152 | {
153 | if ($this->sessionManager !== null) {
154 | return $this->sessionManager;
155 | }
156 |
157 | if ($container->has(ManagerInterface::class)) {
158 | $this->sessionManager = $container->get(ManagerInterface::class);
159 | }
160 |
161 | return $this->sessionManager;
162 | }
163 |
164 | /**
165 | * Normalize the container name in order to perform a lookup
166 | *
167 | * @param string $name
168 | * @return string
169 | */
170 | protected function normalizeContainerName($name)
171 | {
172 | return strtolower($name);
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/Service/SessionConfigFactory.php:
--------------------------------------------------------------------------------
1 | get('config');
37 | if (! isset($config['session_config']) || ! is_array($config['session_config'])) {
38 | throw new ServiceNotCreatedException(
39 | 'Configuration is missing a "session_config" key, or the value of that key is not an array'
40 | );
41 | }
42 |
43 | $class = SessionConfig::class;
44 | $config = $config['session_config'];
45 | if (isset($config['config_class'])) {
46 | if (! class_exists($config['config_class'])) {
47 | throw new ServiceNotCreatedException(sprintf(
48 | 'Invalid configuration class "%s" specified in "config_class" session configuration; '
49 | . 'must be a valid class',
50 | $config['config_class']
51 | ));
52 | }
53 | $class = $config['config_class'];
54 | unset($config['config_class']);
55 | }
56 |
57 | $sessionConfig = new $class();
58 | if (! $sessionConfig instanceof ConfigInterface) {
59 | throw new ServiceNotCreatedException(sprintf(
60 | 'Invalid configuration class "%s" specified in "config_class" session configuration; must implement %s',
61 | $class,
62 | ConfigInterface::class
63 | ));
64 | }
65 | $sessionConfig->setOptions($config);
66 |
67 | return $sessionConfig;
68 | }
69 |
70 | /**
71 | * Create and return a config instance (v2 usage).
72 | *
73 | * @param ServiceLocatorInterface $services
74 | * @param null|string $canonicalName
75 | * @param string $requestedName
76 | * @return ConfigInterface
77 | */
78 | public function createService(
79 | ServiceLocatorInterface $services,
80 | $canonicalName = null,
81 | $requestedName = ConfigInterface::class
82 | ) {
83 | return $this($services, $requestedName);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Service/SessionManagerFactory.php:
--------------------------------------------------------------------------------
1 | true,
30 | ];
31 |
32 | /**
33 | * Create session manager object (v3 usage).
34 | *
35 | * Will consume any combination (or zero) of the following services, when
36 | * present, to construct the SessionManager instance:
37 | *
38 | * - Zend\Session\Config\ConfigInterface
39 | * - Zend\Session\Storage\StorageInterface
40 | * - Zend\Session\SaveHandler\SaveHandlerInterface
41 | *
42 | * The first two have corresponding factories inside this namespace. The
43 | * last, however, does not, due to the differences in implementations, and
44 | * the fact that save handlers will often be written in userland. As such
45 | * if you wish to attach a save handler to the manager, you will need to
46 | * write your own factory, and assign it to the service name
47 | * "Zend\Session\SaveHandler\SaveHandlerInterface", (or alias that name
48 | * to your own service).
49 | *
50 | * You can configure limited behaviors via the "session_manager" key of the
51 | * Config service. Currently, these include:
52 | *
53 | * - enable_default_container_manager: whether to inject the created instance
54 | * as the default manager for Container instances. The default value for
55 | * this is true; set it to false to disable.
56 | * - validators: ...
57 | *
58 | * @param ContainerInterface $container
59 | * @param string $requestedName
60 | * @param array $options
61 | * @return SessionManager
62 | */
63 | public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
64 | {
65 | $config = null;
66 | $storage = null;
67 | $saveHandler = null;
68 | $validators = [];
69 | $managerConfig = $this->defaultManagerConfig;
70 | $options = [];
71 |
72 | if ($container->has(ConfigInterface::class)) {
73 | $config = $container->get(ConfigInterface::class);
74 | if (! $config instanceof ConfigInterface) {
75 | throw new ServiceNotCreatedException(sprintf(
76 | 'SessionManager requires that the %s service implement %s; received "%s"',
77 | ConfigInterface::class,
78 | ConfigInterface::class,
79 | (is_object($config) ? get_class($config) : gettype($config))
80 | ));
81 | }
82 | }
83 |
84 | if ($container->has(StorageInterface::class)) {
85 | $storage = $container->get(StorageInterface::class);
86 | if (! $storage instanceof StorageInterface) {
87 | throw new ServiceNotCreatedException(sprintf(
88 | 'SessionManager requires that the %s service implement %s; received "%s"',
89 | StorageInterface::class,
90 | StorageInterface::class,
91 | (is_object($storage) ? get_class($storage) : gettype($storage))
92 | ));
93 | }
94 | }
95 |
96 | if ($container->has(SaveHandlerInterface::class)) {
97 | $saveHandler = $container->get(SaveHandlerInterface::class);
98 | if (! $saveHandler instanceof SaveHandlerInterface) {
99 | throw new ServiceNotCreatedException(sprintf(
100 | 'SessionManager requires that the %s service implement %s; received "%s"',
101 | SaveHandlerInterface::class,
102 | SaveHandlerInterface::class,
103 | (is_object($saveHandler) ? get_class($saveHandler) : gettype($saveHandler))
104 | ));
105 | }
106 | }
107 |
108 | // Get session manager configuration, if any, and merge with default configuration
109 | if ($container->has('config')) {
110 | $configService = $container->get('config');
111 | if (isset($configService['session_manager'])
112 | && is_array($configService['session_manager'])
113 | ) {
114 | $managerConfig = array_merge($managerConfig, $configService['session_manager']);
115 | }
116 |
117 | if (isset($managerConfig['validators'])) {
118 | $validators = $managerConfig['validators'];
119 | }
120 |
121 | if (isset($managerConfig['options'])) {
122 | $options = $managerConfig['options'];
123 | }
124 | }
125 |
126 | $managerClass = class_exists($requestedName) ? $requestedName : SessionManager::class;
127 | if (! is_subclass_of($managerClass, ManagerInterface::class)) {
128 | throw new ServiceNotCreatedException(sprintf(
129 | 'SessionManager requires that the %s service implement %s',
130 | $managerClass,
131 | ManagerInterface::class
132 | ));
133 | }
134 |
135 | $manager = new $managerClass($config, $storage, $saveHandler, $validators, $options);
136 |
137 | // If configuration enables the session manager as the default manager for container
138 | // instances, do so.
139 | if (isset($managerConfig['enable_default_container_manager'])
140 | && $managerConfig['enable_default_container_manager']
141 | ) {
142 | Container::setDefaultManager($manager);
143 | }
144 |
145 | return $manager;
146 | }
147 |
148 | /**
149 | * Create a SessionManager instance (v2 usage)
150 | *
151 | * @param ServiceLocatorInterface $services
152 | * @param null|string $canonicalName
153 | * @param string $requestedName
154 | * @return SessionManager
155 | */
156 | public function createService(
157 | ServiceLocatorInterface $services,
158 | $canonicalName = null,
159 | $requestedName = SessionManager::class
160 | ) {
161 | return $this($services, $requestedName);
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/Service/StorageFactory.php:
--------------------------------------------------------------------------------
1 | get('config');
36 | if (! isset($config['session_storage']) || ! is_array($config['session_storage'])) {
37 | throw new ServiceNotCreatedException(
38 | 'Configuration is missing a "session_storage" key, or the value of that key is not an array'
39 | );
40 | }
41 |
42 | $config = $config['session_storage'];
43 | if (! isset($config['type'])) {
44 | throw new ServiceNotCreatedException(
45 | '"session_storage" configuration is missing a "type" key'
46 | );
47 | }
48 | $type = $config['type'];
49 | $options = isset($config['options']) ? $config['options'] : [];
50 |
51 | try {
52 | $storage = Factory::factory($type, $options);
53 | } catch (SessionException $e) {
54 | throw new ServiceNotCreatedException(sprintf(
55 | 'Factory is unable to create StorageInterface instance: %s',
56 | $e->getMessage()
57 | ), $e->getCode(), $e);
58 | }
59 |
60 | return $storage;
61 | }
62 |
63 | /**
64 | * Create and return a storage instance (v2 usage).
65 | *
66 | * @param ServiceLocatorInterface $services
67 | * @param null|string $canonicalName
68 | * @param string $requestedName
69 | * @return StorageInterface
70 | */
71 | public function createService(
72 | ServiceLocatorInterface $services,
73 | $canonicalName = null,
74 | $requestedName = StorageInterface::class
75 | ) {
76 | return $this($services, $requestedName);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/SessionManager.php:
--------------------------------------------------------------------------------
1 | true,
28 | 'clear_storage' => false,
29 | ];
30 |
31 | /**
32 | * @var array Default session manager options
33 | */
34 | protected $defaultOptions = [
35 | 'attach_default_validators' => true,
36 | ];
37 |
38 | /**
39 | * @var array Default validators
40 | */
41 | protected $defaultValidators = [
42 | Validator\Id::class,
43 | ];
44 |
45 | /**
46 | * @var string value returned by session_name()
47 | */
48 | protected $name;
49 |
50 | /**
51 | * @var EventManagerInterface Validation chain to determine if session is valid
52 | */
53 | protected $validatorChain;
54 |
55 | /**
56 | * Constructor
57 | *
58 | * @param Config\ConfigInterface|null $config
59 | * @param Storage\StorageInterface|null $storage
60 | * @param SaveHandler\SaveHandlerInterface|null $saveHandler
61 | * @param array $validators
62 | * @param array $options
63 | * @throws Exception\RuntimeException
64 | */
65 | public function __construct(
66 | Config\ConfigInterface $config = null,
67 | Storage\StorageInterface $storage = null,
68 | SaveHandler\SaveHandlerInterface $saveHandler = null,
69 | array $validators = [],
70 | array $options = []
71 | ) {
72 | $options = array_merge($this->defaultOptions, $options);
73 | if ($options['attach_default_validators']) {
74 | $validators = array_merge($this->defaultValidators, $validators);
75 | }
76 |
77 | parent::__construct($config, $storage, $saveHandler, $validators);
78 | register_shutdown_function([$this, 'writeClose']);
79 | }
80 |
81 | /**
82 | * Does a session exist and is it currently active?
83 | *
84 | * @return bool
85 | */
86 | public function sessionExists()
87 | {
88 | if (session_status() == PHP_SESSION_ACTIVE) {
89 | return true;
90 | }
91 | $sid = defined('SID') ? constant('SID') : false;
92 | if ($sid !== false && $this->getId()) {
93 | return true;
94 | }
95 | if (headers_sent()) {
96 | return true;
97 | }
98 | return false;
99 | }
100 |
101 | /**
102 | * Start session
103 | *
104 | * if No session currently exists, attempt to start it. Calls
105 | * {@link isValid()} once session_start() is called, and raises an
106 | * exception if validation fails.
107 | *
108 | * @param bool $preserveStorage If set to true, current session storage will not be overwritten by the
109 | * contents of $_SESSION.
110 | * @return void
111 | * @throws Exception\RuntimeException
112 | */
113 | public function start($preserveStorage = false)
114 | {
115 | if ($this->sessionExists()) {
116 | return;
117 | }
118 |
119 | $saveHandler = $this->getSaveHandler();
120 | if ($saveHandler instanceof SaveHandler\SaveHandlerInterface) {
121 | // register the session handler with ext/session
122 | $this->registerSaveHandler($saveHandler);
123 | }
124 |
125 | $oldSessionData = [];
126 | if (isset($_SESSION)) {
127 | $oldSessionData = $_SESSION;
128 |
129 | // convert session data to plain array that’ll be acceptable as
130 | // ArrayUtils::merge parameter
131 | if ($oldSessionData instanceof Storage\StorageInterface) {
132 | $oldSessionData = $oldSessionData->toArray();
133 | } elseif ($oldSessionData instanceof Traversable) {
134 | $oldSessionData = iterator_to_array($oldSessionData);
135 | }
136 | }
137 |
138 | session_start();
139 |
140 | if (! empty($oldSessionData) && is_array($oldSessionData)) {
141 | $_SESSION = ArrayUtils::merge($oldSessionData, $_SESSION, true);
142 | }
143 |
144 | $storage = $this->getStorage();
145 |
146 | // Since session is starting, we need to potentially repopulate our
147 | // session storage
148 | if ($storage instanceof Storage\SessionStorage && $_SESSION !== $storage) {
149 | if (! $preserveStorage) {
150 | $storage->fromArray($_SESSION);
151 | }
152 | $_SESSION = $storage;
153 | } elseif ($storage instanceof Storage\StorageInitializationInterface) {
154 | $storage->init($_SESSION);
155 | }
156 |
157 | $this->initializeValidatorChain();
158 |
159 | if (! $this->isValid()) {
160 | throw new Exception\RuntimeException('Session validation failed');
161 | }
162 | }
163 |
164 | /**
165 | * Create validators, insert reference value and add them to the validator chain
166 | */
167 | protected function initializeValidatorChain()
168 | {
169 | $validatorChain = $this->getValidatorChain();
170 | $validatorValues = $this->getStorage()->getMetadata('_VALID');
171 |
172 | foreach ($this->validators as $validator) {
173 | // Ignore validators which are already present in Storage
174 | if (is_array($validatorValues) && array_key_exists($validator, $validatorValues)) {
175 | continue;
176 | }
177 |
178 | $validator = new $validator(null);
179 | $validatorChain->attach('session.validate', [$validator, 'isValid']);
180 | }
181 | }
182 |
183 | /**
184 | * Destroy/end a session
185 | *
186 | * @param array $options See {@link $defaultDestroyOptions}
187 | * @return void
188 | */
189 | public function destroy(array $options = null)
190 | {
191 | if (! $this->sessionExists()) {
192 | return;
193 | }
194 |
195 | if (null === $options) {
196 | $options = $this->defaultDestroyOptions;
197 | } else {
198 | $options = array_merge($this->defaultDestroyOptions, $options);
199 | }
200 |
201 | session_destroy();
202 | if ($options['send_expire_cookie']) {
203 | $this->expireSessionCookie();
204 | }
205 |
206 | if ($options['clear_storage']) {
207 | $this->getStorage()->clear();
208 | }
209 | }
210 |
211 | /**
212 | * Write session to save handler and close
213 | *
214 | * Once done, the Storage object will be marked as isImmutable.
215 | *
216 | * @return void
217 | */
218 | public function writeClose()
219 | {
220 | // The assumption is that we're using PHP's ext/session.
221 | // session_write_close() will actually overwrite $_SESSION with an
222 | // empty array on completion -- which leads to a mismatch between what
223 | // is in the storage object and $_SESSION. To get around this, we
224 | // temporarily reset $_SESSION to an array, and then re-link it to
225 | // the storage object.
226 | //
227 | // Additionally, while you _can_ write to $_SESSION following a
228 | // session_write_close() operation, no changes made to it will be
229 | // flushed to the session handler. As such, we now mark the storage
230 | // object isImmutable.
231 | $storage = $this->getStorage();
232 | if (! $storage->isImmutable()) {
233 | $_SESSION = $storage->toArray(true);
234 | session_write_close();
235 | $storage->fromArray($_SESSION);
236 | $storage->markImmutable();
237 | }
238 | }
239 |
240 | /**
241 | * Attempt to set the session name
242 | *
243 | * If the session has already been started, or if the name provided fails
244 | * validation, an exception will be raised.
245 | *
246 | * @param string $name
247 | * @return SessionManager
248 | * @throws Exception\InvalidArgumentException
249 | */
250 | public function setName($name)
251 | {
252 | if ($this->sessionExists()) {
253 | throw new Exception\InvalidArgumentException(
254 | 'Cannot set session name after a session has already started'
255 | );
256 | }
257 |
258 | if (! preg_match('/^[a-zA-Z0-9]+$/', $name)) {
259 | throw new Exception\InvalidArgumentException(
260 | 'Name provided contains invalid characters; must be alphanumeric only'
261 | );
262 | }
263 |
264 | $this->name = $name;
265 | session_name($name);
266 | return $this;
267 | }
268 |
269 | /**
270 | * Get session name
271 | *
272 | * Proxies to {@link session_name()}.
273 | *
274 | * @return string
275 | */
276 | public function getName()
277 | {
278 | if (null === $this->name) {
279 | // If we're grabbing via session_name(), we don't need our
280 | // validation routine; additionally, calling setName() after
281 | // session_start() can lead to issues, and often we just need the name
282 | // in order to do things such as setting cookies.
283 | $this->name = session_name();
284 | }
285 | return $this->name;
286 | }
287 |
288 | /**
289 | * Set session ID
290 | *
291 | * Can safely be called in the middle of a session.
292 | *
293 | * @param string $id
294 | * @return SessionManager
295 | */
296 | public function setId($id)
297 | {
298 | if ($this->sessionExists()) {
299 | throw new Exception\RuntimeException(
300 | 'Session has already been started, to change the session ID call regenerateId()'
301 | );
302 | }
303 | session_id($id);
304 | return $this;
305 | }
306 |
307 | /**
308 | * Get session ID
309 | *
310 | * Proxies to {@link session_id()}
311 | *
312 | * @return string
313 | */
314 | public function getId()
315 | {
316 | return session_id();
317 | }
318 |
319 | /**
320 | * Regenerate id
321 | *
322 | * Regenerate the session ID, using session save handler's
323 | * native ID generation Can safely be called in the middle of a session.
324 | *
325 | * @param bool $deleteOldSession
326 | * @return SessionManager
327 | */
328 | public function regenerateId($deleteOldSession = true)
329 | {
330 | if ($this->sessionExists()) {
331 | session_regenerate_id((bool) $deleteOldSession);
332 | }
333 |
334 | return $this;
335 | }
336 |
337 | /**
338 | * Set the TTL (in seconds) for the session cookie expiry
339 | *
340 | * Can safely be called in the middle of a session.
341 | *
342 | * @param null|int $ttl
343 | * @return SessionManager
344 | */
345 | public function rememberMe($ttl = null)
346 | {
347 | if (null === $ttl) {
348 | $ttl = $this->getConfig()->getRememberMeSeconds();
349 | }
350 | $this->setSessionCookieLifetime($ttl);
351 | return $this;
352 | }
353 |
354 | /**
355 | * Set a 0s TTL for the session cookie
356 | *
357 | * Can safely be called in the middle of a session.
358 | *
359 | * @return SessionManager
360 | */
361 | public function forgetMe()
362 | {
363 | $this->setSessionCookieLifetime(0);
364 | return $this;
365 | }
366 |
367 | /**
368 | * Set the validator chain to use when validating a session
369 | *
370 | * In most cases, you should use an instance of {@link ValidatorChain}.
371 | *
372 | * @param EventManagerInterface $chain
373 | * @return SessionManager
374 | */
375 | public function setValidatorChain(EventManagerInterface $chain)
376 | {
377 | $this->validatorChain = $chain;
378 | return $this;
379 | }
380 |
381 | /**
382 | * Get the validator chain to use when validating a session
383 | *
384 | * By default, uses an instance of {@link ValidatorChain}.
385 | *
386 | * @return EventManagerInterface
387 | */
388 | public function getValidatorChain()
389 | {
390 | if (null === $this->validatorChain) {
391 | $this->setValidatorChain(new ValidatorChain($this->getStorage()));
392 | }
393 | return $this->validatorChain;
394 | }
395 |
396 | /**
397 | * Is this session valid?
398 | *
399 | * Notifies the Validator Chain until either all validators have returned
400 | * true or one has failed.
401 | *
402 | * @return bool
403 | */
404 | public function isValid()
405 | {
406 | $validator = $this->getValidatorChain();
407 |
408 | $event = new Event();
409 | $event->setName('session.validate');
410 | $event->setTarget($this);
411 | $event->setParams($this);
412 |
413 | $falseResult = function ($test) {
414 | return false === $test;
415 | };
416 |
417 | $responses = $validator->triggerEventUntil($falseResult, $event);
418 |
419 | if ($responses->stopped()) {
420 | // If execution was halted, validation failed
421 | return false;
422 | }
423 |
424 | // Otherwise, we're good to go
425 | return true;
426 | }
427 |
428 | /**
429 | * Expire the session cookie
430 | *
431 | * Sends a session cookie with no value, and with an expiry in the past.
432 | *
433 | * @return void
434 | */
435 | public function expireSessionCookie()
436 | {
437 | $config = $this->getConfig();
438 | if (! $config->getUseCookies()) {
439 | return;
440 | }
441 | setcookie(
442 | $this->getName(), // session name
443 | '', // value
444 | $_SERVER['REQUEST_TIME'] - 42000, // TTL for cookie
445 | $config->getCookiePath(),
446 | $config->getCookieDomain(),
447 | $config->getCookieSecure(),
448 | $config->getCookieHttpOnly()
449 | );
450 | }
451 |
452 | /**
453 | * Set the session cookie lifetime
454 | *
455 | * If a session already exists, destroys it (without sending an expiration
456 | * cookie), regenerates the session ID, and restarts the session.
457 | *
458 | * @param int $ttl
459 | * @return void
460 | */
461 | protected function setSessionCookieLifetime($ttl)
462 | {
463 | $config = $this->getConfig();
464 | if (! $config->getUseCookies()) {
465 | return;
466 | }
467 |
468 | // Set new cookie TTL
469 | $config->setCookieLifetime($ttl);
470 |
471 | if ($this->sessionExists()) {
472 | // There is a running session so we'll regenerate id to send a new cookie
473 | $this->regenerateId();
474 | }
475 | }
476 |
477 | /**
478 | * Register Save Handler with ext/session
479 | *
480 | * Since ext/session is coupled to this particular session manager
481 | * register the save handler with ext/session.
482 | *
483 | * @param SaveHandler\SaveHandlerInterface $saveHandler
484 | * @return bool
485 | */
486 | protected function registerSaveHandler(SaveHandler\SaveHandlerInterface $saveHandler)
487 | {
488 | return session_set_save_handler($saveHandler);
489 | }
490 | }
491 |
--------------------------------------------------------------------------------
/src/Storage/AbstractSessionArrayStorage.php:
--------------------------------------------------------------------------------
1 | init($input);
34 | }
35 |
36 | /**
37 | * Initialize Storage
38 | *
39 | * @param array $input
40 | * @return void
41 | */
42 | public function init($input = null)
43 | {
44 | if ((null === $input) && isset($_SESSION)) {
45 | $input = $_SESSION;
46 | if (is_object($input) && ! $_SESSION instanceof \ArrayObject) {
47 | $input = (array) $input;
48 | }
49 | } elseif (null === $input) {
50 | $input = [];
51 | }
52 | $_SESSION = $input;
53 | $this->setRequestAccessTime(microtime(true));
54 | }
55 |
56 | /**
57 | * Get Offset
58 | *
59 | * @param mixed $key
60 | * @return mixed
61 | */
62 | public function __get($key)
63 | {
64 | return $this->offsetGet($key);
65 | }
66 |
67 | /**
68 | * Set Offset
69 | *
70 | * @param mixed $key
71 | * @param mixed $value
72 | * @return void
73 | */
74 | public function __set($key, $value)
75 | {
76 | return $this->offsetSet($key, $value);
77 | }
78 |
79 | /**
80 | * Isset Offset
81 | *
82 | * @param mixed $key
83 | * @return bool
84 | */
85 | public function __isset($key)
86 | {
87 | return $this->offsetExists($key);
88 | }
89 |
90 | /**
91 | * Unset Offset
92 | *
93 | * @param mixed $key
94 | * @return void
95 | */
96 | public function __unset($key)
97 | {
98 | return $this->offsetUnset($key);
99 | }
100 |
101 | /**
102 | * Destructor
103 | *
104 | * @return void
105 | */
106 | public function __destruct()
107 | {
108 | return ;
109 | }
110 |
111 | /**
112 | * Offset Exists
113 | *
114 | * @param mixed $key
115 | * @return bool
116 | */
117 | public function offsetExists($key)
118 | {
119 | return isset($_SESSION[$key]);
120 | }
121 |
122 | /**
123 | * Offset Get
124 | *
125 | * @param mixed $key
126 | * @return mixed
127 | */
128 | public function offsetGet($key)
129 | {
130 | if (isset($_SESSION[$key])) {
131 | return $_SESSION[$key];
132 | }
133 |
134 | return;
135 | }
136 |
137 | /**
138 | * Offset Set
139 | *
140 | * @param mixed $key
141 | * @param mixed $value
142 | * @return void
143 | */
144 | public function offsetSet($key, $value)
145 | {
146 | $_SESSION[$key] = $value;
147 | }
148 |
149 | /**
150 | * Offset Unset
151 | *
152 | * @param mixed $key
153 | * @return void
154 | */
155 | public function offsetUnset($key)
156 | {
157 | unset($_SESSION[$key]);
158 | }
159 |
160 | /**
161 | * Count
162 | *
163 | * @return int
164 | */
165 | public function count()
166 | {
167 | return count($_SESSION);
168 | }
169 |
170 | /**
171 | * Seralize
172 | *
173 | * @return string
174 | */
175 | public function serialize()
176 | {
177 | return serialize($_SESSION);
178 | }
179 |
180 | /**
181 | * Unserialize
182 | *
183 | * @param string $session
184 | * @return mixed
185 | */
186 | public function unserialize($session)
187 | {
188 | return unserialize($session);
189 | }
190 |
191 | /**
192 | * Get Iterator
193 | *
194 | * @return ArrayIterator
195 | */
196 | public function getIterator()
197 | {
198 | return new ArrayIterator($_SESSION);
199 | }
200 |
201 | /**
202 | * Load session object from an existing array
203 | *
204 | * Ensures $_SESSION is set to an instance of the object when complete.
205 | *
206 | * @param array $array
207 | * @return SessionStorage
208 | */
209 | public function fromArray(array $array)
210 | {
211 | $ts = $this->getRequestAccessTime();
212 | $_SESSION = $array;
213 | $this->setRequestAccessTime($ts);
214 |
215 | return $this;
216 | }
217 |
218 | /**
219 | * Mark object as isImmutable
220 | *
221 | * @return SessionStorage
222 | */
223 | public function markImmutable()
224 | {
225 | $_SESSION['_IMMUTABLE'] = true;
226 |
227 | return $this;
228 | }
229 |
230 | /**
231 | * Determine if this object is isImmutable
232 | *
233 | * @return bool
234 | */
235 | public function isImmutable()
236 | {
237 | return (isset($_SESSION['_IMMUTABLE']) && $_SESSION['_IMMUTABLE']);
238 | }
239 |
240 | /**
241 | * Lock this storage instance, or a key within it
242 | *
243 | * @param null|int|string $key
244 | * @return ArrayStorage
245 | */
246 | public function lock($key = null)
247 | {
248 | if (null === $key) {
249 | $this->setMetadata('_READONLY', true);
250 |
251 | return $this;
252 | }
253 | if (isset($_SESSION[$key])) {
254 | $this->setMetadata('_LOCKS', [$key => true]);
255 | }
256 |
257 | return $this;
258 | }
259 |
260 | /**
261 | * Is the object or key marked as locked?
262 | *
263 | * @param null|int|string $key
264 | * @return bool
265 | */
266 | public function isLocked($key = null)
267 | {
268 | if ($this->isImmutable()) {
269 | // isImmutable trumps all
270 | return true;
271 | }
272 |
273 | if (null === $key) {
274 | // testing for global lock
275 | return $this->getMetadata('_READONLY');
276 | }
277 |
278 | $locks = $this->getMetadata('_LOCKS');
279 | $readOnly = $this->getMetadata('_READONLY');
280 |
281 | if ($readOnly && ! $locks) {
282 | // global lock in play; all keys are locked
283 | return true;
284 | }
285 | if ($readOnly && $locks) {
286 | return array_key_exists($key, $locks);
287 | }
288 |
289 | // test for individual locks
290 | if (! $locks) {
291 | return false;
292 | }
293 |
294 | return array_key_exists($key, $locks);
295 | }
296 |
297 | /**
298 | * Unlock an object or key marked as locked
299 | *
300 | * @param null|int|string $key
301 | * @return ArrayStorage
302 | */
303 | public function unlock($key = null)
304 | {
305 | if (null === $key) {
306 | // Unlock everything
307 | $this->setMetadata('_READONLY', false);
308 | $this->setMetadata('_LOCKS', false);
309 |
310 | return $this;
311 | }
312 |
313 | $locks = $this->getMetadata('_LOCKS');
314 | if (! $locks) {
315 | if (! $this->getMetadata('_READONLY')) {
316 | return $this;
317 | }
318 | $array = $this->toArray();
319 | $keys = array_keys($array);
320 | $locks = array_flip($keys);
321 | unset($array, $keys);
322 | }
323 |
324 | if (array_key_exists($key, $locks)) {
325 | unset($locks[$key]);
326 | $this->setMetadata('_LOCKS', $locks, true);
327 | }
328 |
329 | return $this;
330 | }
331 |
332 | /**
333 | * Set storage metadata
334 | *
335 | * Metadata is used to store information about the data being stored in the
336 | * object. Some example use cases include:
337 | * - Setting expiry data
338 | * - Maintaining access counts
339 | * - localizing session storage
340 | * - etc.
341 | *
342 | * @param string $key
343 | * @param mixed $value
344 | * @param bool $overwriteArray Whether to overwrite or merge array values; by default, merges
345 | * @return ArrayStorage
346 | * @throws Exception\RuntimeException
347 | */
348 | public function setMetadata($key, $value, $overwriteArray = false)
349 | {
350 | if ($this->isImmutable()) {
351 | throw new Exception\RuntimeException(
352 | sprintf('Cannot set key "%s" as storage is marked isImmutable', $key)
353 | );
354 | }
355 |
356 | if (! isset($_SESSION['__ZF']) || ! is_array($_SESSION['__ZF'])) {
357 | $_SESSION['__ZF'] = [];
358 | }
359 | if (isset($_SESSION['__ZF'][$key]) && is_array($value)) {
360 | if ($overwriteArray) {
361 | $_SESSION['__ZF'][$key] = $value;
362 | } else {
363 | $_SESSION['__ZF'][$key] = array_replace_recursive($_SESSION['__ZF'][$key], $value);
364 | }
365 | } else {
366 | if ((null === $value) && isset($_SESSION['__ZF'][$key])) {
367 | $array = $_SESSION['__ZF'];
368 | unset($array[$key]);
369 | $_SESSION['__ZF'] = $array;
370 | unset($array);
371 | } elseif (null !== $value) {
372 | $_SESSION['__ZF'][$key] = $value;
373 | }
374 | }
375 |
376 | return $this;
377 | }
378 |
379 | /**
380 | * Retrieve metadata for the storage object or a specific metadata key
381 | *
382 | * Returns false if no metadata stored, or no metadata exists for the given
383 | * key.
384 | *
385 | * @param null|int|string $key
386 | * @return mixed
387 | */
388 | public function getMetadata($key = null)
389 | {
390 | if (! isset($_SESSION['__ZF'])) {
391 | return false;
392 | }
393 |
394 | if (null === $key) {
395 | return $_SESSION['__ZF'];
396 | }
397 |
398 | if (! array_key_exists($key, $_SESSION['__ZF'])) {
399 | return false;
400 | }
401 |
402 | return $_SESSION['__ZF'][$key];
403 | }
404 |
405 | /**
406 | * Clear the storage object or a subkey of the object
407 | *
408 | * @param null|int|string $key
409 | * @return ArrayStorage
410 | * @throws Exception\RuntimeException
411 | */
412 | public function clear($key = null)
413 | {
414 | if ($this->isImmutable()) {
415 | throw new Exception\RuntimeException('Cannot clear storage as it is marked immutable');
416 | }
417 | if (null === $key) {
418 | $this->fromArray([]);
419 |
420 | return $this;
421 | }
422 |
423 | if (! isset($_SESSION[$key])) {
424 | return $this;
425 | }
426 |
427 | // Clear key data
428 | unset($_SESSION[$key]);
429 |
430 | // Clear key metadata
431 | $this->setMetadata($key, null)
432 | ->unlock($key);
433 |
434 | return $this;
435 | }
436 |
437 | /**
438 | * Retrieve the request access time
439 | *
440 | * @return float
441 | */
442 | public function getRequestAccessTime()
443 | {
444 | return $this->getMetadata('_REQUEST_ACCESS_TIME');
445 | }
446 |
447 | /**
448 | * Set the request access time
449 | *
450 | * @param float $time
451 | * @return ArrayStorage
452 | */
453 | protected function setRequestAccessTime($time)
454 | {
455 | $this->setMetadata('_REQUEST_ACCESS_TIME', $time);
456 |
457 | return $this;
458 | }
459 |
460 | /**
461 | * Cast the object to an array
462 | *
463 | * @param bool $metaData Whether to include metadata
464 | * @return array
465 | */
466 | public function toArray($metaData = false)
467 | {
468 | if (isset($_SESSION)) {
469 | $values = $_SESSION;
470 | } else {
471 | $values = [];
472 | }
473 |
474 | if ($metaData) {
475 | return $values;
476 | }
477 |
478 | if (isset($values['__ZF'])) {
479 | unset($values['__ZF']);
480 | }
481 |
482 | return $values;
483 | }
484 | }
485 |
--------------------------------------------------------------------------------
/src/Storage/ArrayStorage.php:
--------------------------------------------------------------------------------
1 | setRequestAccessTime(microtime(true));
44 | }
45 |
46 | /**
47 | * Set the request access time
48 | *
49 | * @param float $time
50 | * @return ArrayStorage
51 | */
52 | protected function setRequestAccessTime($time)
53 | {
54 | $this->setMetadata('_REQUEST_ACCESS_TIME', $time);
55 |
56 | return $this;
57 | }
58 |
59 | /**
60 | * Retrieve the request access time
61 | *
62 | * @return float
63 | */
64 | public function getRequestAccessTime()
65 | {
66 | return $this->getMetadata('_REQUEST_ACCESS_TIME');
67 | }
68 |
69 | /**
70 | * Set a value in the storage object
71 | *
72 | * If the object is marked as isImmutable, or the object or key is marked as
73 | * locked, raises an exception.
74 | *
75 | * @param string $key
76 | * @param mixed $value
77 | * @return void
78 | */
79 |
80 | /**
81 | * @param mixed $key
82 | * @param mixed $value
83 | * @throws Exception\RuntimeException
84 | */
85 | public function offsetSet($key, $value)
86 | {
87 | if ($this->isImmutable()) {
88 | throw new Exception\RuntimeException(
89 | sprintf('Cannot set key "%s" as storage is marked isImmutable', $key)
90 | );
91 | }
92 | if ($this->isLocked($key)) {
93 | throw new Exception\RuntimeException(
94 | sprintf('Cannot set key "%s" due to locking', $key)
95 | );
96 | }
97 |
98 | parent::offsetSet($key, $value);
99 | }
100 |
101 | /**
102 | * Lock this storage instance, or a key within it
103 | *
104 | * @param null|int|string $key
105 | * @return ArrayStorage
106 | */
107 | public function lock($key = null)
108 | {
109 | if (null === $key) {
110 | $this->setMetadata('_READONLY', true);
111 |
112 | return $this;
113 | }
114 | if (isset($this[$key])) {
115 | $this->setMetadata('_LOCKS', [$key => true]);
116 | }
117 |
118 | return $this;
119 | }
120 |
121 | /**
122 | * Is the object or key marked as locked?
123 | *
124 | * @param null|int|string $key
125 | * @return bool
126 | */
127 | public function isLocked($key = null)
128 | {
129 | if ($this->isImmutable()) {
130 | // isImmutable trumps all
131 | return true;
132 | }
133 |
134 | if (null === $key) {
135 | // testing for global lock
136 | return $this->getMetadata('_READONLY');
137 | }
138 |
139 | $locks = $this->getMetadata('_LOCKS');
140 | $readOnly = $this->getMetadata('_READONLY');
141 |
142 | if ($readOnly && ! $locks) {
143 | // global lock in play; all keys are locked
144 | return true;
145 | } elseif ($readOnly && $locks) {
146 | return array_key_exists($key, $locks);
147 | }
148 |
149 | // test for individual locks
150 | if (! $locks) {
151 | return false;
152 | }
153 |
154 | return array_key_exists($key, $locks);
155 | }
156 |
157 | /**
158 | * Unlock an object or key marked as locked
159 | *
160 | * @param null|int|string $key
161 | * @return ArrayStorage
162 | */
163 | public function unlock($key = null)
164 | {
165 | if (null === $key) {
166 | // Unlock everything
167 | $this->setMetadata('_READONLY', false);
168 | $this->setMetadata('_LOCKS', false);
169 |
170 | return $this;
171 | }
172 |
173 | $locks = $this->getMetadata('_LOCKS');
174 | if (! $locks) {
175 | if (! $this->getMetadata('_READONLY')) {
176 | return $this;
177 | }
178 | $array = $this->toArray();
179 | $keys = array_keys($array);
180 | $locks = array_flip($keys);
181 | unset($array, $keys);
182 | }
183 |
184 | if (array_key_exists($key, $locks)) {
185 | unset($locks[$key]);
186 | $this->setMetadata('_LOCKS', $locks, true);
187 | }
188 |
189 | return $this;
190 | }
191 |
192 | /**
193 | * Mark the storage container as isImmutable
194 | *
195 | * @return ArrayStorage
196 | */
197 | public function markImmutable()
198 | {
199 | $this->isImmutable = true;
200 |
201 | return $this;
202 | }
203 |
204 | /**
205 | * Is the storage container marked as isImmutable?
206 | *
207 | * @return bool
208 | */
209 | public function isImmutable()
210 | {
211 | return $this->isImmutable;
212 | }
213 |
214 | /**
215 | * Set storage metadata
216 | *
217 | * Metadata is used to store information about the data being stored in the
218 | * object. Some example use cases include:
219 | * - Setting expiry data
220 | * - Maintaining access counts
221 | * - localizing session storage
222 | * - etc.
223 | *
224 | * @param string $key
225 | * @param mixed $value
226 | * @param bool $overwriteArray Whether to overwrite or merge array values; by default, merges
227 | * @return ArrayStorage
228 | * @throws Exception\RuntimeException
229 | */
230 | public function setMetadata($key, $value, $overwriteArray = false)
231 | {
232 | if ($this->isImmutable) {
233 | throw new Exception\RuntimeException(
234 | sprintf('Cannot set key "%s" as storage is marked isImmutable', $key)
235 | );
236 | }
237 |
238 | if (! isset($this['__ZF'])) {
239 | $this['__ZF'] = [];
240 | }
241 |
242 | if (isset($this['__ZF'][$key]) && is_array($value)) {
243 | if ($overwriteArray) {
244 | $this['__ZF'][$key] = $value;
245 | } else {
246 | $this['__ZF'][$key] = array_replace_recursive($this['__ZF'][$key], $value);
247 | }
248 | } else {
249 | if ((null === $value) && isset($this['__ZF'][$key])) {
250 | // unset($this['__ZF'][$key]) led to "indirect modification...
251 | // has no effect" errors, so explicitly pulling array and
252 | // unsetting key.
253 | $array = $this['__ZF'];
254 | unset($array[$key]);
255 | $this['__ZF'] = $array;
256 | unset($array);
257 | } elseif (null !== $value) {
258 | $this['__ZF'][$key] = $value;
259 | }
260 | }
261 |
262 | return $this;
263 | }
264 |
265 | /**
266 | * Retrieve metadata for the storage object or a specific metadata key
267 | *
268 | * Returns false if no metadata stored, or no metadata exists for the given
269 | * key.
270 | *
271 | * @param null|int|string $key
272 | * @return mixed
273 | */
274 | public function getMetadata($key = null)
275 | {
276 | if (! isset($this['__ZF'])) {
277 | return false;
278 | }
279 |
280 | if (null === $key) {
281 | return $this['__ZF'];
282 | }
283 |
284 | if (! array_key_exists($key, $this['__ZF'])) {
285 | return false;
286 | }
287 |
288 | return $this['__ZF'][$key];
289 | }
290 |
291 | /**
292 | * Clear the storage object or a subkey of the object
293 | *
294 | * @param null|int|string $key
295 | * @return ArrayStorage
296 | * @throws Exception\RuntimeException
297 | */
298 | public function clear($key = null)
299 | {
300 | if ($this->isImmutable()) {
301 | throw new Exception\RuntimeException('Cannot clear storage as it is marked immutable');
302 | }
303 | if (null === $key) {
304 | $this->fromArray([]);
305 |
306 | return $this;
307 | }
308 |
309 | if (! isset($this[$key])) {
310 | return $this;
311 | }
312 |
313 | // Clear key data
314 | unset($this[$key]);
315 |
316 | // Clear key metadata
317 | $this->setMetadata($key, null)
318 | ->unlock($key);
319 |
320 | return $this;
321 | }
322 |
323 | /**
324 | * Load the storage from another array
325 | *
326 | * Overwrites any data that was previously set.
327 | *
328 | * @param array $array
329 | * @return ArrayStorage
330 | */
331 | public function fromArray(array $array)
332 | {
333 | $ts = $this->getRequestAccessTime();
334 | $this->exchangeArray($array);
335 | $this->setRequestAccessTime($ts);
336 |
337 | return $this;
338 | }
339 |
340 | /**
341 | * Cast the object to an array
342 | *
343 | * @param bool $metaData Whether to include metadata
344 | * @return array
345 | */
346 | public function toArray($metaData = false)
347 | {
348 | $values = $this->getArrayCopy();
349 | if ($metaData) {
350 | return $values;
351 | }
352 | if (isset($values['__ZF'])) {
353 | unset($values['__ZF']);
354 | }
355 |
356 | return $values;
357 | }
358 | }
359 |
--------------------------------------------------------------------------------
/src/Storage/Factory.php:
--------------------------------------------------------------------------------
1 | getArrayCopy();
61 | }
62 |
63 | /**
64 | * Load session object from an existing array
65 | *
66 | * Ensures $_SESSION is set to an instance of the object when complete.
67 | *
68 | * @param array $array
69 | * @return SessionStorage
70 | */
71 | public function fromArray(array $array)
72 | {
73 | parent::fromArray($array);
74 | if ($_SESSION !== $this) {
75 | $_SESSION = $this;
76 | }
77 |
78 | return $this;
79 | }
80 |
81 | /**
82 | * Mark object as isImmutable
83 | *
84 | * @return SessionStorage
85 | */
86 | public function markImmutable()
87 | {
88 | $this['_IMMUTABLE'] = true;
89 |
90 | return $this;
91 | }
92 |
93 | /**
94 | * Determine if this object is isImmutable
95 | *
96 | * @return bool
97 | */
98 | public function isImmutable()
99 | {
100 | return (isset($this['_IMMUTABLE']) && $this['_IMMUTABLE']);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Storage/StorageInitializationInterface.php:
--------------------------------------------------------------------------------
1 | storage = $storage;
34 | $validators = $storage->getMetadata('_VALID');
35 | if ($validators) {
36 | foreach ($validators as $validator => $data) {
37 | $this->attachValidator('session.validate', [new $validator($data), 'isValid'], 1);
38 | }
39 | }
40 | }
41 |
42 | /**
43 | * Attach a listener to the session validator chain.
44 | *
45 | * @param string $event
46 | * @param null|callable $callback
47 | * @param int $priority
48 | * @return \Zend\Stdlib\CallbackHandler
49 | */
50 | public function attach($event, $callback = null, $priority = 1)
51 | {
52 | return $this->attachValidator($event, $callback, $priority);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Validator/AbstractValidatorChainEM3.php:
--------------------------------------------------------------------------------
1 | storage = $storage;
34 | $validators = $storage->getMetadata('_VALID');
35 | if ($validators) {
36 | foreach ($validators as $validator => $data) {
37 | $this->attachValidator('session.validate', [new $validator($data), 'isValid'], 1);
38 | }
39 | }
40 | }
41 |
42 | /**
43 | * Attach a listener to the session validator chain.
44 | *
45 | * @param string $eventName
46 | * @param callable $callback
47 | * @param int $priority
48 | * @return \Zend\Stdlib\CallbackHandler
49 | */
50 | public function attach($eventName, callable $callback, $priority = 1)
51 | {
52 | return $this->attachValidator($eventName, $callback, $priority);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Validator/HttpUserAgent.php:
--------------------------------------------------------------------------------
1 | data = $data;
33 | }
34 |
35 | /**
36 | * isValid() - this method will determine if the current user agent matches the
37 | * user agent we stored when we initialized this variable.
38 | *
39 | * @return bool
40 | */
41 | public function isValid()
42 | {
43 | $userAgent = isset($_SERVER['HTTP_USER_AGENT'])
44 | ? $_SERVER['HTTP_USER_AGENT']
45 | : null;
46 |
47 | return ($userAgent === $this->getData());
48 | }
49 |
50 | /**
51 | * Retrieve token for validating call
52 | *
53 | * @return string
54 | */
55 | public function getData()
56 | {
57 | return $this->data;
58 | }
59 |
60 | /**
61 | * Return validator name
62 | *
63 | * @return string
64 | */
65 | public function getName()
66 | {
67 | return __CLASS__;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Validator/Id.php:
--------------------------------------------------------------------------------
1 | id = $id;
37 | }
38 |
39 | /**
40 | * Is the current session identifier valid?
41 | *
42 | * Tests that the identifier does not contain invalid characters.
43 | *
44 | * @return bool
45 | */
46 | public function isValid()
47 | {
48 | $id = $this->id;
49 | $saveHandler = ini_get('session.save_handler');
50 | if ($saveHandler == 'cluster') { // Zend Server SC, validate only after last dash
51 | $dashPos = strrpos($id, '-');
52 | if ($dashPos) {
53 | $id = substr($id, $dashPos + 1);
54 | }
55 | }
56 |
57 | // Get the session id bits per character INI setting, using 5 if unavailable
58 | $bitsPerCharacter = PHP_VERSION_ID >= 70100
59 | ? 'session.sid_bits_per_character'
60 | : 'session.hash_bits_per_character';
61 | $hashBitsPerChar = ini_get($bitsPerCharacter) ?: 5;
62 |
63 | switch ($hashBitsPerChar) {
64 | case 4:
65 | $pattern = '#^[0-9a-f]*$#';
66 | break;
67 | case 6:
68 | $pattern = '#^[0-9a-zA-Z-,]*$#';
69 | break;
70 | case 5:
71 | // intentionally fall-through
72 | default:
73 | $pattern = '#^[0-9a-v]*$#';
74 | break;
75 | }
76 |
77 | return (bool) preg_match($pattern, $id);
78 | }
79 |
80 | /**
81 | * Retrieve token for validating call (session_id)
82 | *
83 | * @return string
84 | */
85 | public function getData()
86 | {
87 | return $this->id;
88 | }
89 |
90 | /**
91 | * Return validator name
92 | *
93 | * @return string
94 | */
95 | public function getName()
96 | {
97 | return __CLASS__;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Validator/RemoteAddr.php:
--------------------------------------------------------------------------------
1 | getIpAddress();
58 | }
59 | $this->data = $data;
60 | }
61 |
62 | /**
63 | * isValid() - this method will determine if the current user IP matches the
64 | * IP we stored when we initialized this variable.
65 | *
66 | * @return bool
67 | */
68 | public function isValid()
69 | {
70 | return ($this->getIpAddress() === $this->getData());
71 | }
72 |
73 | /**
74 | * Changes proxy handling setting.
75 | *
76 | * This must be static method, since validators are recovered automatically
77 | * at session read, so this is the only way to switch setting.
78 | *
79 | * @param bool $useProxy Whether to check also proxied IP addresses.
80 | * @return void
81 | */
82 | public static function setUseProxy($useProxy = true)
83 | {
84 | static::$useProxy = $useProxy;
85 | }
86 |
87 | /**
88 | * Checks proxy handling setting.
89 | *
90 | * @return bool Current setting value.
91 | */
92 | public static function getUseProxy()
93 | {
94 | return static::$useProxy;
95 | }
96 |
97 | /**
98 | * Set list of trusted proxy addresses
99 | *
100 | * @param array $trustedProxies
101 | * @return void
102 | */
103 | public static function setTrustedProxies(array $trustedProxies)
104 | {
105 | static::$trustedProxies = $trustedProxies;
106 | }
107 |
108 | /**
109 | * Set the header to introspect for proxy IPs
110 | *
111 | * @param string $header
112 | * @return void
113 | */
114 | public static function setProxyHeader($header = 'X-Forwarded-For')
115 | {
116 | static::$proxyHeader = $header;
117 | }
118 |
119 | /**
120 | * Returns client IP address.
121 | *
122 | * @return string IP address.
123 | */
124 | protected function getIpAddress()
125 | {
126 | $remoteAddress = new RemoteAddress();
127 | $remoteAddress->setUseProxy(static::$useProxy);
128 | $remoteAddress->setTrustedProxies(static::$trustedProxies);
129 | $remoteAddress->setProxyHeader(static::$proxyHeader);
130 | return $remoteAddress->getIpAddress();
131 | }
132 |
133 | /**
134 | * Retrieve token for validating call
135 | *
136 | * @return string
137 | */
138 | public function getData()
139 | {
140 | return $this->data;
141 | }
142 |
143 | /**
144 | * Return validator name
145 | *
146 | * @return string
147 | */
148 | public function getName()
149 | {
150 | return __CLASS__;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/Validator/ValidatorChainTrait.php:
--------------------------------------------------------------------------------
1 | storage;
30 | }
31 |
32 | /**
33 | * Internal implementation for attaching a listener to the
34 | * session validator chain.
35 | *
36 | * @param string $event
37 | * @param callable $callback
38 | * @param int $priority
39 | * @return \Zend\Stdlib\CallbackHandler|callable
40 | */
41 | private function attachValidator($event, $callback, $priority)
42 | {
43 | $context = null;
44 | if ($callback instanceof ValidatorInterface) {
45 | $context = $callback;
46 | } elseif (is_array($callback)) {
47 | $test = array_shift($callback);
48 | if ($test instanceof ValidatorInterface) {
49 | $context = $test;
50 | }
51 | array_unshift($callback, $test);
52 | }
53 | if ($context instanceof ValidatorInterface) {
54 | $data = $context->getData();
55 | $name = $context->getName();
56 | $this->getStorage()->setMetadata('_VALID', [$name => $data]);
57 | }
58 |
59 | $listener = parent::attach($event, $callback, $priority);
60 | return $listener;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Validator/ValidatorInterface.php:
--------------------------------------------------------------------------------
1 |