├── .github └── workflows │ └── main.yaml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── phpcs.xml ├── phpunit.xml └── src └── PhpSession.php /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: "testing" 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | qa: 11 | name: Quality assurance 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Validate composer.json and composer.lock 19 | run: composer validate 20 | 21 | - name: Cache Composer packages 22 | id: composer-cache 23 | uses: actions/cache@v4 24 | with: 25 | path: vendor 26 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 27 | restore-keys: | 28 | ${{ runner.os }}-php- 29 | 30 | - name: Install dependencies 31 | if: steps.composer-cache.outputs.cache-hit != 'true' 32 | run: composer install --prefer-dist --no-progress 33 | 34 | - name: Coding Standard 35 | run: composer run-script cs 36 | 37 | tests: 38 | name: Tests 39 | runs-on: ubuntu-latest 40 | 41 | strategy: 42 | matrix: 43 | php: 44 | - 7.2 45 | - 7.3 46 | - 7.4 47 | - 8.0 48 | - 8.1 49 | - 8.2 50 | - 8.3 51 | - 8.4 52 | 53 | steps: 54 | - name: Checkout 55 | uses: actions/checkout@v4 56 | 57 | - name: Install PHP 58 | uses: shivammathur/setup-php@v2 59 | with: 60 | php-version: ${{ matrix.php }} 61 | 62 | - name: Cache PHP dependencies 63 | uses: actions/cache@v4 64 | with: 65 | path: vendor 66 | key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} 67 | restore-keys: ${{ runner.os }}-php-${{ matrix.php }}-composer- 68 | 69 | - name: Install dependencies 70 | run: composer install --prefer-dist --no-progress 71 | 72 | - name: Tests 73 | run: composer test 74 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [3.3.0] - 2025-04-05 9 | ### Added 10 | 11 | Extended `options()` functionality so that cookie parameters can be passed from now on. Before that you could only set them with `session_set_cookie_params()`. 12 | 13 | Its important to mention that `options()` will have preference over `session_set_cookie_params()`. 14 | 15 | Example: 16 | 17 | ```php 18 | Dispatcher::run([ 19 | (new PhpSession())->options([ 20 | // session params, as usual 21 | 'use_cookies' => false, 22 | 'cache_limiter' => '', 23 | 24 | // new cookie params functionality 25 | 'lifetime' => 10, 26 | 'path' => '/middlewares', 27 | 'domain' => 'middlewares.dev', 28 | 'secure' => true, 29 | 'httponly' => true, 30 | 'samesite' => 'Strict', 31 | ]), 32 | ] 33 | ); 34 | ``` 35 | 36 | ## [3.2.0] - 2025-03-21 37 | ### Added 38 | - Support for PHP 8.4 39 | 40 | ## [3.1.1] - 2022-03-13 41 | ### Fixed 42 | - Get the session name from options if available and not explicity set [#14] 43 | 44 | ## [3.1.0] - 2021-11-05 45 | ### Added 46 | - Support for `SameSite` [#9]. 47 | 48 | ### Fixed 49 | - Code improvements [#7], [#8]. 50 | 51 | ## [3.0.1] - 2021-11-03 52 | ### Fixed 53 | - Replace `isset` with `!empty` [#6] 54 | 55 | ## [3.0.0] - 2021-02-22 56 | ### Changed 57 | - This middleware handles the output session id cookie in the Psr7 response, instead send the cookie automaticaly by PHP. This requires the configuration `use_trans_sid`, `use_cookies` must be `false` and `use_only_cookies` as `true` [#4] [#5]. 58 | 59 | ## [2.0.0] - 2020-12-03 60 | ### Added 61 | - Support for PHP 8 62 | 63 | ### Removed 64 | - Support for PHP 7.0 and 7.1 65 | 66 | ## [1.2.0] - 2018-08-04 67 | ### Added 68 | - PSR-17 support 69 | 70 | ## [1.1.0] - 2018-05-12 71 | ### Added 72 | - New function `regenerateId` to regenerate session IDs after a given interval [#2] 73 | 74 | ## [1.0.0] - 2018-01-27 75 | ### Added 76 | - Improved testing and added code coverage reporting 77 | - Added tests for PHP 7.2 78 | 79 | ### Changed 80 | - Upgraded to the final version of PSR-15 `psr/http-server-middleware` 81 | 82 | ### Fixed 83 | - Updated license year 84 | 85 | ## [0.6.0] - 2017-11-13 86 | ### Changed 87 | - Replaced `http-interop/http-middleware` with `http-interop/http-server-middleware`. 88 | 89 | ### Removed 90 | - Removed support for PHP 5.x. 91 | 92 | ## [0.5.0] - 2017-09-21 93 | ### Changed 94 | - Append `.dist` suffix to phpcs.xml and phpunit.xml files 95 | - Changed the configuration of phpcs and php_cs 96 | - Upgraded phpunit to the latest version and improved its config file 97 | - Updated to `http-interop/http-middleware#0.5` 98 | 99 | ## [0.4.0] - 2017-04-18 100 | ### Added 101 | - New option `options()` to configure `session_start()` 102 | 103 | ## [0.3.0] - 2016-12-26 104 | ### Changed 105 | - Updated tests 106 | - Updated to `http-interop/http-middleware#0.4` 107 | - Updated `friendsofphp/php-cs-fixer#2.0` 108 | 109 | ## [0.2.0] - 2016-11-27 110 | ### Changed 111 | - Updated to `http-interop/http-middleware#0.3` 112 | 113 | ## [0.1.0] - 2016-10-08 114 | First version 115 | 116 | [#2]: https://github.com/middlewares/php-session/issues/2 117 | [#4]: https://github.com/middlewares/php-session/issues/4 118 | [#5]: https://github.com/middlewares/php-session/issues/5 119 | [#6]: https://github.com/middlewares/php-session/issues/6 120 | [#7]: https://github.com/middlewares/php-session/issues/7 121 | [#8]: https://github.com/middlewares/php-session/issues/8 122 | [#9]: https://github.com/middlewares/php-session/issues/9 123 | [#14]: https://github.com/middlewares/php-session/issues/14 124 | 125 | [3.3.0]: https://github.com/middlewares/php-session/compare/v3.2.0...v3.3.0 126 | [3.2.0]: https://github.com/middlewares/php-session/compare/v3.1.1...v3.2.0 127 | [3.1.1]: https://github.com/middlewares/php-session/compare/v3.1.0...v3.1.1 128 | [3.1.0]: https://github.com/middlewares/php-session/compare/v3.0.1...v3.1.0 129 | [3.0.1]: https://github.com/middlewares/php-session/compare/v3.0.0...v3.0.1 130 | [3.0.0]: https://github.com/middlewares/php-session/compare/v2.0.0...v3.0.0 131 | [2.0.0]: https://github.com/middlewares/php-session/compare/v1.2.0...v2.0.0 132 | [1.2.0]: https://github.com/middlewares/php-session/compare/v1.1.0...v1.2.0 133 | [1.1.0]: https://github.com/middlewares/php-session/compare/v1.0.0...v1.1.0 134 | [1.0.0]: https://github.com/middlewares/php-session/compare/v0.6.0...v1.0.0 135 | [0.6.0]: https://github.com/middlewares/php-session/compare/v0.5.0...v0.6.0 136 | [0.5.0]: https://github.com/middlewares/php-session/compare/v0.4.0...v0.5.0 137 | [0.4.0]: https://github.com/middlewares/php-session/compare/v0.3.0...v0.4.0 138 | [0.3.0]: https://github.com/middlewares/php-session/compare/v0.2.0...v0.3.0 139 | [0.2.0]: https://github.com/middlewares/php-session/compare/v0.1.0...v0.2.0 140 | [0.1.0]: https://github.com/middlewares/php-session/releases/tag/v0.1.0 141 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | This project adheres to [The Code Manifesto](http://codemanifesto.com) as its guidelines for contributor interactions. 4 | 5 | ## The Code Manifesto 6 | 7 | We want to work in an ecosystem that empowers developers to reach their potential--one that encourages growth and effective collaboration. A space that is safe for all. 8 | 9 | A space such as this benefits everyone that participates in it. It encourages new developers to enter our field. It is through discussion and collaboration that we grow, and through growth that we improve. 10 | 11 | In the effort to create such a place, we hold to these values: 12 | 13 | 1. **Discrimination limits us.** This includes discrimination on the basis of race, gender, sexual orientation, gender identity, age, nationality, technology and any other arbitrary exclusion of a group of people. 14 | 2. **Boundaries honor us.** Your comfort levels are not everyone’s comfort levels. Remember that, and if brought to your attention, heed it. 15 | 3. **We are our biggest assets.** None of us were born masters of our trade. Each of us has been helped along the way. Return that favor, when and where you can. 16 | 4. **We are resources for the future.** As an extension of #3, share what you know. Make yourself a resource to help those that come after you. 17 | 5. **Respect defines us.** Treat others as you wish to be treated. Make your discussions, criticisms and debates from a position of respectfulness. Ask yourself, is it true? Is it necessary? Is it constructive? Anything less is unacceptable. 18 | 6. **Reactions require grace.** Angry responses are valid, but abusive language and vindictive actions are toxic. When something happens that offends you, handle it assertively, but be respectful. Escalate reasonably, and try to allow the offender an opportunity to explain themselves, and possibly correct the issue. 19 | 7. **Opinions are just that: opinions.** Each and every one of us, due to our background and upbringing, have varying opinions. That is perfectly acceptable. Remember this: if you respect your own opinions, you should respect the opinions of others. 20 | 8. **To err is human.** You might not intend it, but mistakes do happen and contribute to build experience. Tolerate honest mistakes, and don't hesitate to apologize if you make one yourself. 21 | 22 | ## How to contribute 23 | 24 | This is a collaborative effort. We welcome all contributions submitted as pull requests. 25 | 26 | (Contributions on wording & style are also welcome.) 27 | 28 | ### Bugs 29 | 30 | A bug is a demonstrable problem that is caused by the code in the repository. Good bug reports are extremely helpful – thank you! 31 | 32 | Please try to be as detailed as possible in your report. Include specific information about the environment – version of PHP, etc, and steps required to reproduce the issue. 33 | 34 | ### Pull Requests 35 | 36 | Good pull requests – patches, improvements, new features – are a fantastic help. Before create a pull request, please follow these instructions: 37 | 38 | * The code must follow the [PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). Run `composer cs-fix` to fix your code before commit. 39 | * Write tests 40 | * Document any change in `README.md` and `CHANGELOG.md` 41 | * One pull request per feature. If you want to do more than one thing, send multiple pull request 42 | 43 | ### Runing tests 44 | 45 | ```sh 46 | composer test 47 | ``` 48 | 49 | To get code coverage information execute the following comand: 50 | 51 | ```sh 52 | composer coverage 53 | ``` 54 | 55 | Then, open the `./coverage/index.html` file in your browser. 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-2025 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # middlewares/php-session 2 | 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Software License][ico-license]](LICENSE) 5 | ![Testing][ico-ga] 6 | [![Total Downloads][ico-downloads]][link-downloads] 7 | 8 | Middleware to start a [php session](http://php.net/manual/en/book.session.php) using the request data and close it after returning the response. Reads and writes session cookies in the PSR-7 request/response. 9 | 10 | ## Requirements 11 | 12 | * PHP >= 7.2 13 | * A [PSR-7 http message implementation](https://github.com/middlewares/awesome-psr15-middlewares#psr-7-implementations) 14 | * A [PSR-15 middleware dispatcher](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) 15 | 16 | ## Installation 17 | 18 | This package is installable and autoloadable via Composer as [middlewares/php-session](https://packagist.org/packages/middlewares/php-session). 19 | 20 | ```sh 21 | composer require middlewares/php-session 22 | ``` 23 | 24 | ## Example 25 | 26 | ```php 27 | Dispatcher::run([ 28 | new Middlewares\PhpSession(), 29 | 30 | function () { 31 | //Use the global $_SESSION variable to get/set data 32 | $_SESSION['name'] = 'John'; 33 | } 34 | ]); 35 | ``` 36 | 37 | ## Usage 38 | 39 | This is a middleware to start the native PHP session using the cookies of the server request. 40 | 41 | ### name 42 | 43 | The session name. If it's not provided, use the php's default name (PHPSESSID). More info [session_name](https://www.php.net/manual/en/function.session-name.php) 44 | 45 | ```php 46 | // Start the session with other name 47 | $session = (new Middlewares\PhpSession())->name('user_session'); 48 | ``` 49 | 50 | ### id 51 | 52 | This option set a session id. If it's not provided, use the request's cookies to get it. 53 | 54 | ```php 55 | // Start the session with a specific session id 56 | $session = (new Middlewares\PhpSession())->id('foo'); 57 | ``` 58 | 59 | ### options 60 | 61 | This allows to set an of options passed to [`session_start()`](http://php.net/session_start) 62 | 63 | ```php 64 | // Start the session with a specific session id 65 | $session = (new Middlewares\PhpSession())->options([ 66 | 'cookie_lifetime' => 86400 67 | ]); 68 | ``` 69 | 70 | ### regenerateId 71 | 72 | This option regenerates the id after a specific time interval. The latest regeneration time is saved in the key `session-id-expires` but you can change it in the second argument: 73 | 74 | ```php 75 | // Regenerate the session id after 60 seconds 76 | $session = (new Middlewares\PhpSession())->regenerateId(60); 77 | 78 | // Regenerate the session id after 60 seconds, storing the expires date in the key 'expiresAt' 79 | $session = (new Middlewares\PhpSession())->regenerateId(60, 'expiresAt'); 80 | ``` 81 | 82 | --- 83 | 84 | Please see [CHANGELOG](CHANGELOG.md) for more information about recent changes and [CONTRIBUTING](CONTRIBUTING.md) for contributing details. 85 | 86 | The MIT License (MIT). Please see [LICENSE](LICENSE) for more information. 87 | 88 | [ico-version]: https://img.shields.io/packagist/v/middlewares/php-session.svg?style=flat-square 89 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 90 | [ico-ga]: https://github.com/middlewares/php-session/workflows/testing/badge.svg 91 | [ico-downloads]: https://img.shields.io/packagist/dt/middlewares/php-session.svg?style=flat-square 92 | 93 | [link-packagist]: https://packagist.org/packages/middlewares/php-session 94 | [link-downloads]: https://packagist.org/packages/middlewares/php-session 95 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "middlewares/php-session", 3 | "type": "library", 4 | "description": "Middleware to start php sessions using the request data", 5 | "license": "MIT", 6 | "keywords": [ 7 | "psr-7", 8 | "psr-15", 9 | "middleware", 10 | "server", 11 | "http", 12 | "session" 13 | ], 14 | "homepage": "https://github.com/middlewares/php-session", 15 | "support": { 16 | "issues": "https://github.com/middlewares/php-session/issues" 17 | }, 18 | "require": { 19 | "php": "^7.2 || ^8.0", 20 | "psr/http-server-middleware": "^1" 21 | }, 22 | "require-dev": { 23 | "middlewares/utils": "^2 || ^3 || ^4", 24 | "phpunit/phpunit": "^8 || ^9", 25 | "friendsofphp/php-cs-fixer": "^3", 26 | "squizlabs/php_codesniffer": "^3", 27 | "oscarotero/php-cs-fixer-config": "^2", 28 | "phpstan/phpstan": "^1 || ^2", 29 | "laminas/laminas-diactoros": "^2 || ^3" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Middlewares\\": "src/" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Middlewares\\Tests\\": "tests/" 39 | } 40 | }, 41 | "scripts": { 42 | "cs": "phpcs", 43 | "cs-fix": "php-cs-fixer fix", 44 | "phpstan": "phpstan analyse", 45 | "test": "phpunit", 46 | "coverage": "phpunit --coverage-text", 47 | "coverage-html": "phpunit --coverage-html=coverage" 48 | } 49 | } -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Middlewares coding standard 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | src 15 | tests 16 | 17 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | tests 21 | 22 | 23 | 24 | 25 | 26 | ./src 27 | 28 | ./tests 29 | ./vendor 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/PhpSession.php: -------------------------------------------------------------------------------- 1 | |null 26 | */ 27 | private $sessionOptions; 28 | 29 | /** 30 | * @var array|null 31 | */ 32 | private $cookieOptions; 33 | 34 | /** 35 | * @var int|null 36 | */ 37 | private $regenerateIdInterval; 38 | 39 | /** 40 | * @var string|null 41 | */ 42 | private $sessionIdExpiryKey; 43 | 44 | /** 45 | * Configure the session name. 46 | */ 47 | public function name(string $name): self 48 | { 49 | $this->name = $name; 50 | 51 | return $this; 52 | } 53 | 54 | /** 55 | * Configure the session id. 56 | */ 57 | public function id(string $id): self 58 | { 59 | $this->id = $id; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Set the session and cookie options. 66 | * 67 | * @param array $options 68 | * @throws RuntimeException 69 | */ 70 | public function options(array $options): self 71 | { 72 | self::checkSessionSettings($options); 73 | 74 | $this->sessionOptions = $options; 75 | $this->cookieOptions = []; 76 | 77 | static::moveKeys( 78 | ['lifetime', 'path', 'domain', 'secure', 'httponly', 'samesite'], 79 | $this->sessionOptions, 80 | $this->cookieOptions 81 | ); 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * @param string[] $keysToMove 88 | * @param array $source 89 | * @param array $target 90 | */ 91 | private static function moveKeys(array $keysToMove, array &$source, array &$target): void 92 | { 93 | foreach ($keysToMove as $key) { 94 | if (array_key_exists($key, $source)) { 95 | $target[$key] = $source[$key]; 96 | unset($source[$key]); 97 | } 98 | } 99 | } 100 | 101 | /** 102 | * Set the session id regenerate interval and id expiry key name. 103 | */ 104 | public function regenerateId(int $interval, string $key = 'session-id-expires'): self 105 | { 106 | $this->regenerateIdInterval = $interval; 107 | 108 | $this->sessionIdExpiryKey = $key; 109 | 110 | return $this; 111 | } 112 | 113 | /** 114 | * Process a server request and return a response. 115 | */ 116 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface 117 | { 118 | self::checkSessionSettings($this->sessionOptions ?? []); 119 | self::checkSessionCanStart(); 120 | 121 | // Session name 122 | $name = $this->name ?? $this->sessionOptions['name'] ?? session_name(); 123 | session_name((string) $name); 124 | 125 | // Session ID 126 | $id = $this->id ?: self::readSessionCookie($request, (string) $name); 127 | if (!empty($id)) { 128 | session_id($id); 129 | } 130 | 131 | if ($this->sessionOptions === null) { 132 | session_start(); 133 | } else { 134 | session_start($this->sessionOptions); 135 | } 136 | 137 | // Session ID regeneration 138 | self::runIdRegeneration($this->regenerateIdInterval, $this->sessionIdExpiryKey); 139 | 140 | $response = $handler->handle($request); 141 | 142 | if (session_status() === PHP_SESSION_ACTIVE && session_name() === $name) { 143 | session_write_close(); 144 | } 145 | 146 | // If the session ID changed, write the session cookie 147 | if (session_id() !== $id) { 148 | $response = self::writeSessionCookie( 149 | $response, 150 | (string) session_name(), 151 | (string) session_id(), 152 | time(), 153 | array_merge(session_get_cookie_params(), $this->cookieOptions) 154 | ); 155 | } 156 | 157 | return $response; 158 | } 159 | 160 | /** 161 | * Check PHP session settings for compatibility with PSR-7. 162 | * 163 | * @param array $options 164 | * @throws RuntimeException 165 | */ 166 | private static function checkSessionSettings(array $options): void 167 | { 168 | // See https://paul-m-jones.com/post/2016/04/12/psr-7-and-session-cookies 169 | $use_trans_sid = $options['use_trans_sid'] ?? ini_get('session.use_trans_sid'); 170 | $use_cookies = $options['use_cookies'] ?? ini_get('session.use_cookies'); 171 | $use_only_cookies = $options['use_only_cookies'] ?? ini_get('session.use_only_cookies'); 172 | $cache_limiter = $options['cache_limiter'] ?? ini_get('session.cache_limiter'); 173 | 174 | if ($use_trans_sid != false) { 175 | throw new RuntimeException('session.use_trans_sid must be false'); 176 | } 177 | 178 | if ($use_cookies != false) { 179 | throw new RuntimeException('session.use_cookies must be false'); 180 | } 181 | 182 | if ($use_only_cookies != true) { 183 | throw new RuntimeException('session.use_only_cookies must be true'); 184 | } 185 | 186 | if ($cache_limiter !== '') { 187 | throw new RuntimeException('session.cache_limiter must be set to an empty string'); 188 | } 189 | } 190 | 191 | /** 192 | * Checks whether the session can be started. 193 | * 194 | * @throws RuntimeException 195 | */ 196 | private static function checkSessionCanStart(): void 197 | { 198 | if (session_status() === PHP_SESSION_DISABLED) { 199 | throw new RuntimeException('PHP sessions are disabled'); 200 | } 201 | 202 | if (session_status() === PHP_SESSION_ACTIVE) { 203 | throw new RuntimeException('Failed to start the session: already started by PHP.'); 204 | } 205 | } 206 | 207 | /** 208 | * Regenerate the session ID if it's needed. 209 | */ 210 | private static function runIdRegeneration(?int $interval = null, ?string $key = null): void 211 | { 212 | if (empty($interval)) { 213 | return; 214 | } 215 | 216 | $expiry = time() + $interval; 217 | 218 | if (!isset($_SESSION[$key])) { 219 | $_SESSION[$key] = $expiry; 220 | } 221 | 222 | if ($_SESSION[$key] < time() || $_SESSION[$key] > $expiry) { 223 | session_regenerate_id(true); 224 | 225 | $_SESSION[$key] = $expiry; 226 | } 227 | } 228 | 229 | /** 230 | * Attempt to read the session ID from the session cookie in a PSR-7 request. 231 | */ 232 | private static function readSessionCookie(ServerRequestInterface $request, string $name): string 233 | { 234 | $cookies = $request->getCookieParams(); 235 | 236 | return $cookies[$name] ?? ''; 237 | } 238 | 239 | /** 240 | * Write a session cookie to the PSR-7 response. 241 | * 242 | * @param array $params 243 | */ 244 | private static function writeSessionCookie( 245 | ResponseInterface $response, 246 | string $name, 247 | string $id, 248 | int $now, 249 | array $params 250 | ): ResponseInterface { 251 | $cookie = urlencode($name) . '=' . urlencode($id); 252 | 253 | // if omitted, the cookie will expire at end of the session (ie when the browser closes) 254 | if (!empty($params['lifetime'])) { 255 | // @phpstan-ignore-next-line 256 | $expires = gmdate('D, d M Y H:i:s T', $now + $params['lifetime']); 257 | $cookie .= "; Expires={$expires}; Max-Age={$params['lifetime']}"; 258 | } 259 | 260 | if (!empty($params['domain'])) { 261 | $cookie .= "; Domain={$params['domain']}"; 262 | } 263 | 264 | if (!empty($params['path'])) { 265 | $cookie .= "; Path={$params['path']}"; 266 | } 267 | 268 | if (!empty($params['samesite']) && in_array($params['samesite'], ['None', 'Lax', 'Strict'])) { 269 | $cookie .= '; SameSite=' . $params['samesite']; 270 | } 271 | 272 | if (!empty($params['secure'])) { 273 | $cookie .= '; Secure'; 274 | } 275 | 276 | if (!empty($params['httponly'])) { 277 | $cookie .= '; HttpOnly'; 278 | } 279 | 280 | return $response->withAddedHeader('Set-Cookie', $cookie); 281 | } 282 | } 283 | --------------------------------------------------------------------------------