diff --git a/.gitattributes b/.gitattributes index c540470..b67c7ca 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,10 +1,10 @@ * text=auto eol=lf -/tests export-ignore -.editorconfig export-ignore -.gitattributes export-ignore -.gitignore export-ignore -.php_cs export-ignore -.travis.yml export-ignore -phpcs.xml.dist export-ignore -phpunit.xml.dist export-ignore +/tests export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.php-cs-fixer.php export-ignore +phpcs.xml.dist export-ignore +phpunit.xml.dist export-ignore +.phpstan.neon export-ignore diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 494d950..5d14a59 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -1,10 +1,10 @@ name: "testing" on: - push: - branches: [ master ] - pull_request: - branches: [ master ] + push: + branches: [ master ] + pull_request: + branches: [ master ] jobs: qa: @@ -13,19 +13,19 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Validate composer.json and composer.lock run: composer validate - name: Cache Composer packages id: composer-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- - name: Install dependencies if: steps.composer-cache.outputs.cache-hit != 'true' @@ -40,19 +40,19 @@ jobs: strategy: matrix: - php: - - 7.2 - - 7.3 - - 7.4 - composer-args: [ "" ] - include: - - php: 8.0 - composer-args: --ignore-platform-reqs - fail-fast: false + php: + - 7.2 + - 7.3 + - 7.4 + - 8.0 + - 8.1 + - 8.2 + - 8.3 + - 8.4 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install PHP uses: shivammathur/setup-php@v2 @@ -60,43 +60,14 @@ jobs: php-version: ${{ matrix.php }} - name: Cache PHP dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: vendor key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-php-${{ matrix.php }}-composer- - name: Install dependencies - run: composer install --prefer-dist --no-progress ${{ matrix.composer-args }} + run: composer install --prefer-dist --no-progress - name: Tests run: composer test - - - name: Tests coverage - run: composer coverage - - static-analysis: - name: Static Analysis - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 7.4 - - - name: Cache PHP dependencies - uses: actions/cache@v2 - with: - path: vendor - key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-php-${{ matrix.php }}-composer- - - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - - name: Static Analysis - run: composer run-script phpstan -- --level=5 src/ tests/ diff --git a/.gitignore b/.gitignore index 364d1a4..5ae8693 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ vendor composer.lock coverage *.cache +.idea +kit diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..90b12a3 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,10 @@ +setFinder( + PhpCsFixer\Finder::create() + ->files() + ->name('*.php') + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') + ); \ No newline at end of file diff --git a/.phpstan.neon b/.phpstan.neon new file mode 100644 index 0000000..7f33c04 --- /dev/null +++ b/.phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 8 + paths: + - src + - tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 31b7020..9851485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [3.2.0] - 2025-03-21 +### Added +- Support for PHP 8.4 + ## [3.1.1] - 2022-03-13 ### Fixed - Get the session name from options if available and not explicity set [#14] @@ -90,6 +94,7 @@ First version [#9]: https://github.com/middlewares/php-session/issues/9 [#14]: https://github.com/middlewares/php-session/issues/14 +[3.2.0]: https://github.com/middlewares/php-session/compare/v3.1.1...v3.2.0 [3.1.1]: https://github.com/middlewares/php-session/compare/v3.1.0...v3.1.1 [3.1.0]: https://github.com/middlewares/php-session/compare/v3.0.1...v3.1.0 [3.0.1]: https://github.com/middlewares/php-session/compare/v3.0.0...v3.0.1 diff --git a/LICENSE b/LICENSE index 017c0cd..374fb13 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2019 +Copyright (c) 2019-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/composer.json b/composer.json index f350d54..512ebcb 100644 --- a/composer.json +++ b/composer.json @@ -17,16 +17,16 @@ }, "require": { "php": "^7.2 || ^8.0", - "psr/http-server-middleware": "^1.0" + "psr/http-server-middleware": "^1" }, "require-dev": { - "middlewares/utils": "^3.0", - "phpunit/phpunit": "^8|^9", - "friendsofphp/php-cs-fixer": "^2.0", - "squizlabs/php_codesniffer": "^3.0", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12", - "laminas/laminas-diactoros": "^2.3" + "middlewares/utils": "^2 || ^3 || ^4", + "phpunit/phpunit": "^8 || ^9", + "friendsofphp/php-cs-fixer": "^3", + "squizlabs/php_codesniffer": "^3", + "oscarotero/php-cs-fixer-config": "^2", + "phpstan/phpstan": "^1 || ^2", + "laminas/laminas-diactoros": "^2 || ^3" }, "autoload": { "psr-4": { @@ -46,4 +46,4 @@ "coverage": "phpunit --coverage-text", "coverage-html": "phpunit --coverage-html=coverage" } -} +} \ No newline at end of file diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..7814331 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,16 @@ + + + Middlewares coding standard + + + + + + + + + + + src + tests + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..c2d04d2 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,33 @@ + + + + + tests + + + + + + ./src + + ./tests + ./vendor + + + + diff --git a/src/PhpSession.php b/src/PhpSession.php index 2f995ca..99636e0 100644 --- a/src/PhpSession.php +++ b/src/PhpSession.php @@ -22,7 +22,7 @@ class PhpSession implements MiddlewareInterface private $id; /** - * @var array|null + * @var array|null */ private $options; @@ -59,6 +59,7 @@ public function id(string $id): self /** * Set the session options. * + * @param array $options * @throws RuntimeException */ public function options(array $options): self @@ -92,10 +93,10 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface // Session name $name = $this->name ?? $this->options['name'] ?? session_name(); - session_name($name); + session_name((string) $name); // Session ID - $id = $this->id ?: self::readSessionCookie($request, $name); + $id = $this->id ?: self::readSessionCookie($request, (string) $name); if (!empty($id)) { session_id($id); } @@ -119,8 +120,8 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface if (session_id() !== $id) { $response = self::writeSessionCookie( $response, - session_name(), - session_id(), + (string) session_name(), + (string) session_id(), time(), session_get_cookie_params() ); @@ -132,6 +133,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface /** * Check PHP session settings for compatibility with PSR-7. * + * @param array $options * @throws RuntimeException */ private static function checkSessionSettings(array $options): void @@ -178,7 +180,7 @@ private static function checkSessionCanStart(): void /** * Regenerate the session ID if it's needed. */ - private static function runIdRegeneration(int $interval = null, string $key = null): void + private static function runIdRegeneration(?int $interval = null, ?string $key = null): void { if (empty($interval)) { return; @@ -203,11 +205,14 @@ private static function runIdRegeneration(int $interval = null, string $key = nu private static function readSessionCookie(ServerRequestInterface $request, string $name): string { $cookies = $request->getCookieParams(); + return $cookies[$name] ?? ''; } /** * Write a session cookie to the PSR-7 response. + * + * @param array $params */ private static function writeSessionCookie( ResponseInterface $response, @@ -220,6 +225,7 @@ private static function writeSessionCookie( // if omitted, the cookie will expire at end of the session (ie when the browser closes) if (!empty($params['lifetime'])) { + // @phpstan-ignore-next-line $expires = gmdate('D, d M Y H:i:s T', $now + $params['lifetime']); $cookie .= "; Expires={$expires}; Max-Age={$params['lifetime']}"; } diff --git a/tests/PhpSessionTest.php b/tests/PhpSessionTest.php index 38982fe..d8c4986 100644 --- a/tests/PhpSessionTest.php +++ b/tests/PhpSessionTest.php @@ -10,6 +10,7 @@ class PhpSessionTest extends TestCase { + /** @var array */ private $sessionOptions = [ 'use_strict_mode' => false, 'use_trans_sid' => false, @@ -23,18 +24,21 @@ class PhpSessionTest extends TestCase 'cookie_httponly' => true, ]; - private function getCookieHeader(string $sessionName, string $sessionId): string - { - return sprintf( - '%s=%s; expires=%s; path=%s; domain=%s; secure; httponly', - urlencode($sessionName), - urlencode($sessionId), - $this->sessionOptions['cookie_path'], - $this->sessionOptions['cookie_domain'], - gmdate('D, d M Y H:i:s T', $this->sessionOptions['cookie_lifetime']) - ); - } + // private function getCookieHeader(string $sessionName, string $sessionId): string + // { + // return sprintf( + // '%s=%s; expires=%s; path=%s; domain=%s; secure; httponly', + // urlencode($sessionName), + // urlencode($sessionId), + // $this->sessionOptions['cookie_path'], + // $this->sessionOptions['cookie_domain'], + // gmdate('D, d M Y H:i:s T', $this->sessionOptions['cookie_lifetime']) + // ); + // } + /** + * @return array> + */ public function sessionDataProvider(): array { return [ @@ -50,7 +54,7 @@ public function sessionDataProvider(): array ]; } - public function testCheckUseTransSidSettingException() + public function testCheckUseTransSidSettingException(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('session.use_trans_sid must be false'); @@ -60,7 +64,7 @@ public function testCheckUseTransSidSettingException() ]); } - public function testCheckUseCookiesSettingException() + public function testCheckUseCookiesSettingException(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('session.use_cookies must be false'); @@ -71,7 +75,7 @@ public function testCheckUseCookiesSettingException() ]); } - public function testCheckUseOnlyCookiesSettingException() + public function testCheckUseOnlyCookiesSettingException(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('session.use_only_cookies must be true'); @@ -83,7 +87,7 @@ public function testCheckUseOnlyCookiesSettingException() ]); } - public function testCheckCacheLimiterSettingException() + public function testCheckCacheLimiterSettingException(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('session.cache_limiter must be set to an empty string'); @@ -99,7 +103,7 @@ public function testCheckCacheLimiterSettingException() /** * @runInSeparateProcess */ - public function testDefaultSettingCheck() + public function testDefaultSettingCheck(): void { ini_set('session.use_trans_sid', '0'); ini_set('session.use_cookies', '0'); @@ -112,7 +116,7 @@ public function testDefaultSettingCheck() /** * @runInSeparateProcess */ - public function testWriteSessionCookie() + public function testWriteSessionCookie(): void { $response = Dispatcher::run( [ @@ -132,7 +136,7 @@ function () { * @runInSeparateProcess * @dataProvider sessionDataProvider */ - public function testPhpSession(string $sessionName, string $sessionId, string $value) + public function testPhpSession(string $sessionName, string $sessionId, string $value): void { $response = Dispatcher::run( [ @@ -157,14 +161,14 @@ function ($request) use ($value) { /** * @runInSeparateProcess */ - public function testRegenerateId() + public function testRegenerateId(): void { $sessionId = session_create_id(); $response = Dispatcher::run( [ (new PhpSession()) - ->id($sessionId) + ->id((string) $sessionId) ->regenerateId(-10) ->options($this->sessionOptions), @@ -184,14 +188,14 @@ function () use ($sessionId) { /** * @runInSeparateProcess */ - public function testStrictMode() + public function testStrictMode(): void { $sessionId = session_create_id(); $response = Dispatcher::run( [ (new PhpSession()) - ->id($sessionId) + ->id((string) $sessionId) ->options(array_merge($this->sessionOptions, [ 'use_strict_mode' => true, ])),