From eb88000d3c1a4ab9747c446f31fbb0604c6edae2 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 20 Feb 2026 00:07:21 -0300 Subject: [PATCH 01/19] Require PHP 8.5 --- .github/workflows/tests.yml | 4 ++-- .gitlab-ci.yml | 2 +- composer.json | 2 +- docker-compose.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2c0f654..8d459f4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,7 +33,7 @@ jobs: ports: - 6379:6379 - name: PHP 8.3 + name: PHP 8.5 steps: - name: Checkout @@ -42,7 +42,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.3 + php-version: 8.5 tools: composer coverage: xdebug diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0ff2d72..3a637ee 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,5 @@ --- -image: registry.gitlab.com/aplus-framework/images/base:4 +image: registry.gitlab.com/aplus-framework/images/base:6 include: - template: Security/SAST.gitlab-ci.yml diff --git a/composer.json b/composer.json index 7ca4c65..9287b1f 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ } ], "require": { - "php": ">=8.3", + "php": ">=8.5", "ext-memcached": "*", "ext-redis": "*", "aplus/database": "^4.0", diff --git a/docker-compose.yml b/docker-compose.yml index 5557e35..9a57b06 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: "3" services: package: - image: registry.gitlab.com/aplus-framework/images/package:4 + image: registry.gitlab.com/aplus-framework/images/package:6 container_name: package-session working_dir: /package volumes: From 317e04b64b7a72ef0b6bd641653f8351a5bef614 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 20 Feb 2026 00:08:19 -0300 Subject: [PATCH 02/19] Run jobs with Ubuntu 24.04 --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8d459f4..9deb65a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ on: jobs: tests: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 10 services: @@ -83,7 +83,7 @@ jobs: php-coveralls --coverage_clover=build/logs/clover.xml -v tests-latest: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 10 services: From 06ba7284b41b880f60fb283e3c4e584e53c721a8 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 20 Feb 2026 00:15:07 -0300 Subject: [PATCH 03/19] Upgrade required packages --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 9287b1f..c3cbcd0 100644 --- a/composer.json +++ b/composer.json @@ -36,13 +36,13 @@ "php": ">=8.5", "ext-memcached": "*", "ext-redis": "*", - "aplus/database": "^4.0", - "aplus/debug": "^4.0", - "aplus/log": "^4.0" + "aplus/database": "^5.0", + "aplus/debug": "^5.0", + "aplus/log": "^5.0" }, "require-dev": { "ext-xdebug": "*", - "aplus/coding-standard": "^2.8", + "aplus/coding-standard": "^3.0", "ergebnis/composer-normalize": "^2.25", "jetbrains/phpstorm-attributes": "^1.0", "phpmd/phpmd": "^2.13", From e3493a898003f25aa64fb1def97de642cb28b4a8 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 20 Feb 2026 00:17:26 -0300 Subject: [PATCH 04/19] Remove unnecessary parentheses --- .php-cs-fixer.dist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 0100ce9..eb4aad8 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -10,7 +10,7 @@ use Framework\CodingStandard\Config; use Framework\CodingStandard\Finder; -return (new Config())->setDefaultHeaderComment( +return new Config()->setDefaultHeaderComment( 'Aplus Framework Session Library', 'Natan Felles ' )->setFinder( From 1ed2ad4ddcbe5905fa3d271c1d93f7aab0c47d87 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 20 Feb 2026 00:24:55 -0300 Subject: [PATCH 05/19] Update SaveHandler::validateId method --- src/SaveHandler.php | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/SaveHandler.php b/src/SaveHandler.php index ed6a762..247aad1 100644 --- a/src/SaveHandler.php +++ b/src/SaveHandler.php @@ -211,18 +211,7 @@ protected function getKeySuffix() : string */ public function validateId($id) : bool { - if (\PHP_VERSION_ID >= 80400) { - return (bool) \preg_match('#\A[0-9a-f]{32}\z#', $id); - } - $bits = \ini_get('session.sid_bits_per_character') ?: 5; - $length = \ini_get('session.sid_length') ?: 40; - $bitsRegex = [ - 4 => '[0-9a-f]', - 5 => '[0-9a-v]', - 6 => '[0-9a-zA-Z,-]', - ]; - return isset($bitsRegex[$bits]) - && \preg_match('#\A' . $bitsRegex[$bits] . '{' . $length . '}\z#', $id); + return (bool) \preg_match('#\A[0-9a-f]{32}\z#', $id); } /** From db150c24724d317d6c88f54f4898e23346dfe798 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 20 Feb 2026 00:27:07 -0300 Subject: [PATCH 06/19] Remove obsolete attribute `version` --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9a57b06..d6dfab9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3" services: package: image: registry.gitlab.com/aplus-framework/images/package:6 From c35ffd9817de3b0dcf12a72bc0ea2f9b86d27323 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 20 Feb 2026 00:36:36 -0300 Subject: [PATCH 07/19] Update tests --- tests/SaveHandlers/AbstractHandler.php | 29 +++----------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/tests/SaveHandlers/AbstractHandler.php b/tests/SaveHandlers/AbstractHandler.php index 58ee232..1e113f7 100644 --- a/tests/SaveHandlers/AbstractHandler.php +++ b/tests/SaveHandlers/AbstractHandler.php @@ -54,34 +54,11 @@ public function testValidateId() : void $id5 = 'iimuf8lvdectatt5jtkve15831funl8rg5cg6okp'; $id4 = '96aa2c863140e0e714a603cf44b0afc9a0632592'; $this->session->stop(); - /* - * Since PHP 8.4 session.sid_bits_per_character and session.sid_length - * are deprecated. - * - * - The default value for session.sid_bits_per_character is 4 (0-9, a-f). - * - The default value for session.sid_length is 32 character. - */ - if (\PHP_VERSION_ID >= 80400) { - self::assertFalse($this->handler->validateId($id6)); - self::assertFalse($this->handler->validateId($id5)); - self::assertFalse($this->handler->validateId($id4)); - $idPhp84 = '96aa2c863140e0e714a603cf44b0afc9'; - self::assertTrue($this->handler->validateId($idPhp84)); - return; - } - \ini_set('session.sid_bits_per_character', '6'); - \ini_set('session.sid_length', '40'); - self::assertTrue($this->handler->validateId($id6)); - self::assertTrue($this->handler->validateId($id5)); - self::assertTrue($this->handler->validateId($id4)); - \ini_set('session.sid_bits_per_character', '5'); - self::assertFalse($this->handler->validateId($id6)); - self::assertTrue($this->handler->validateId($id5)); - self::assertTrue($this->handler->validateId($id4)); - \ini_set('session.sid_bits_per_character', '4'); self::assertFalse($this->handler->validateId($id6)); self::assertFalse($this->handler->validateId($id5)); - self::assertTrue($this->handler->validateId($id4)); + self::assertFalse($this->handler->validateId($id4)); + $idPhp84 = '96aa2c863140e0e714a603cf44b0afc9'; + self::assertTrue($this->handler->validateId($idPhp84)); } public function testGC() : void From 294a237a33223ae2ff5da8a0b50f1043c25eb2b5 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 20 Feb 2026 00:43:18 -0300 Subject: [PATCH 08/19] Update notes about database column --- guide/index.rst | 2 +- src/SaveHandlers/DatabaseHandler.php | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/guide/index.rst b/guide/index.rst index 258402c..1be97a6 100644 --- a/guide/index.rst +++ b/guide/index.rst @@ -325,7 +325,7 @@ A basic example of a table for sessions is below: .. code-block:: sql CREATE TABLE `Sessions` ( - `id` varchar(128) NOT NULL, + `id` char(32) NOT NULL, `timestamp` timestamp NOT NULL, `data` blob NOT NULL, `ip` varchar(45) NOT NULL, -- optional diff --git a/src/SaveHandlers/DatabaseHandler.php b/src/SaveHandlers/DatabaseHandler.php index 2492370..b509395 100644 --- a/src/SaveHandlers/DatabaseHandler.php +++ b/src/SaveHandlers/DatabaseHandler.php @@ -23,7 +23,7 @@ * * ```sql * CREATE TABLE `Sessions` ( - * `id` varchar(128) NOT NULL, + * `id` char(32) NOT NULL, * `timestamp` timestamp NOT NULL, * `data` blob NOT NULL, * `ip` varchar(45) NOT NULL, -- optional @@ -35,8 +35,6 @@ * ); * ``` * - * NOTE: As of PHP 8.4 the id column can be `char(32)`. - * * @package session */ class DatabaseHandler extends SaveHandler From 678044aad41564a4a58e9e2e99e5ce664a61bd0a Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 20 Feb 2026 00:49:26 -0300 Subject: [PATCH 09/19] Allow set custom key to get the IP address --- src/SaveHandler.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SaveHandler.php b/src/SaveHandler.php index 247aad1..eddaf31 100644 --- a/src/SaveHandler.php +++ b/src/SaveHandler.php @@ -171,7 +171,8 @@ protected function getMaxlifetime() : int */ protected function getIP() : string { - return $_SERVER['REMOTE_ADDR'] ?? ''; + $key = $this->config['ip_key'] ?? 'REMOTE_ADDR'; + return $_SERVER[$key] ?? ''; } /** From 17c6b7a69968138d1a5c3eee5678d003ca5b47ba Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Sat, 21 Feb 2026 00:44:07 -0300 Subject: [PATCH 10/19] Allow get data with custom column name --- src/SaveHandlers/DatabaseHandler.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SaveHandlers/DatabaseHandler.php b/src/SaveHandlers/DatabaseHandler.php index b509395..4731f22 100644 --- a/src/SaveHandlers/DatabaseHandler.php +++ b/src/SaveHandlers/DatabaseHandler.php @@ -188,7 +188,8 @@ public function read($id) : string $this->addWhereMatchs($statement); $row = $statement->limit(1)->run()->fetch(); $this->sessionExists = (bool) $row; - $data = $row->data ?? ''; + $column = $this->getColumn('data'); + $data = $row->{$column} ?? ''; $this->setFingerprint($data); return $data; } From 73dd33522bd29cc3ee50dd89141b9aad9345713a Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Sat, 21 Feb 2026 01:20:55 -0300 Subject: [PATCH 11/19] Rename 'config' to 'configs' --- src/Debug/SessionCollector.php | 2 +- src/SaveHandler.php | 26 +++++++++--------- src/SaveHandlers/DatabaseHandler.php | 27 ++++++++++--------- src/SaveHandlers/FilesHandler.php | 34 ++++++++++++------------ src/SaveHandlers/MemcachedHandler.php | 26 +++++++++--------- src/SaveHandlers/RedisHandler.php | 38 +++++++++++++-------------- 6 files changed, 77 insertions(+), 76 deletions(-) diff --git a/src/Debug/SessionCollector.php b/src/Debug/SessionCollector.php index 1b10f50..072d1b0 100644 --- a/src/Debug/SessionCollector.php +++ b/src/Debug/SessionCollector.php @@ -358,7 +358,7 @@ protected function renderAutoRegenerateId() : string */ protected function getSaveHandlerConfigs() : array { - $config = $this->saveHandler->getConfig(); + $config = $this->saveHandler->getConfigs(); if ($this->saveHandler instanceof FilesHandler) { return [ 'Directory' => $config['directory'], diff --git a/src/SaveHandler.php b/src/SaveHandler.php index eddaf31..71d240d 100644 --- a/src/SaveHandler.php +++ b/src/SaveHandler.php @@ -29,7 +29,7 @@ abstract class SaveHandler implements \SessionHandlerInterface, \SessionUpdateTi * * @var array */ - protected array $config; + protected array $configs; /** * The current data fingerprint. * @@ -74,36 +74,36 @@ abstract class SaveHandler implements \SessionHandlerInterface, \SessionUpdateTi /** * SessionSaveHandler constructor. * - * @param array $config + * @param array $configs * @param Logger|null $logger */ public function __construct( #[SensitiveParameter] - array $config = [], + array $configs = [], ?Logger $logger = null ) { - $this->prepareConfig($config); + $this->prepareConfigs($configs); $this->logger = $logger; } /** * Prepare configurations to be used by the save handler. * - * @param array $config Custom configs + * @param array $configs Custom configs * * @codeCoverageIgnore */ - protected function prepareConfig(#[SensitiveParameter] array $config) : void + protected function prepareConfigs(#[SensitiveParameter] array $configs) : void { - $this->config = $config; + $this->configs = $configs; } /** * @return array */ - public function getConfig() : array + public function getConfigs() : array { - return $this->config; + return $this->configs; } /** @@ -161,7 +161,7 @@ protected function hasSameFingerprint(string $data) : bool */ protected function getMaxlifetime() : int { - return (int) ($this->config['maxlifetime'] ?? \ini_get('session.gc_maxlifetime')); + return (int) ($this->configs['maxlifetime'] ?? \ini_get('session.gc_maxlifetime')); } /** @@ -171,7 +171,7 @@ protected function getMaxlifetime() : int */ protected function getIP() : string { - $key = $this->config['ip_key'] ?? 'REMOTE_ADDR'; + $key = $this->configs['ip_key'] ?? 'REMOTE_ADDR'; return $_SERVER[$key] ?? ''; } @@ -188,10 +188,10 @@ protected function getUA() : string protected function getKeySuffix() : string { $suffix = ''; - if ($this->config['match_ip']) { + if ($this->configs['match_ip']) { $suffix .= ':' . $this->getIP(); } - if ($this->config['match_ua']) { + if ($this->configs['match_ua']) { $suffix .= ':' . $this->getUA(); } if ($suffix) { diff --git a/src/SaveHandlers/DatabaseHandler.php b/src/SaveHandlers/DatabaseHandler.php index 4731f22..bf2f59d 100644 --- a/src/SaveHandlers/DatabaseHandler.php +++ b/src/SaveHandlers/DatabaseHandler.php @@ -44,7 +44,7 @@ class DatabaseHandler extends SaveHandler /** * Prepare configurations to be used by the DatabaseHandler. * - * @param array $config Custom configs + * @param array $configs Custom configs * * The custom configs are: * @@ -75,9 +75,10 @@ class DatabaseHandler extends SaveHandler * * NOTE: The Database::connect configs was not shown. */ - protected function prepareConfig(#[SensitiveParameter] array $config) : void + protected function prepareConfigs(#[SensitiveParameter] array $configs) : void { - $this->config = \array_replace_recursive([ + $this->configs = \array_replace_recursive([ + 'handler' => [], 'table' => 'Sessions', 'maxlifetime' => null, 'columns' => [ @@ -93,7 +94,7 @@ protected function prepareConfig(#[SensitiveParameter] array $config) : void 'save_ip' => false, 'save_ua' => false, 'save_user_id' => false, - ], $config); + ], $configs); } public function setDatabase(Database $database) : static @@ -115,7 +116,7 @@ public function getDatabase() : ?Database */ protected function getTable() : string { - return $this->config['table']; + return $this->configs['table']; } /** @@ -127,7 +128,7 @@ protected function getTable() : string */ protected function getColumn(string $key) : string { - return $this->config['columns'][$key]; + return $this->configs['columns'][$key]; } /** @@ -137,10 +138,10 @@ protected function getColumn(string $key) : string */ protected function addWhereMatchs(Delete | Select | Update $statement) : void { - if ($this->config['match_ip']) { + if ($this->configs['match_ip']) { $statement->whereEqual($this->getColumn('ip'), $this->getIP()); } - if ($this->config['match_ua']) { + if ($this->configs['match_ua']) { $statement->whereEqual($this->getColumn('ua'), $this->getUA()); } } @@ -152,7 +153,7 @@ protected function addWhereMatchs(Delete | Select | Update $statement) : void */ protected function addUserIdColumn(array &$columns) : void { - if ($this->config['save_user_id']) { + if ($this->configs['save_user_id']) { $key = $this->getColumn('user_id'); $columns[$key] = $_SESSION[$key] ?? null; } @@ -161,7 +162,7 @@ protected function addUserIdColumn(array &$columns) : void public function open($path, $name) : bool { try { - $this->database ??= new Database($this->config); + $this->database ??= new Database($this->configs); return true; } catch (\Exception $exception) { $this->log( @@ -221,10 +222,10 @@ protected function writeInsert(string $id, string $data) : bool }, $this->getColumn('data') => $data, ]; - if ($this->config['match_ip'] || $this->config['save_ip']) { + if ($this->configs['match_ip'] || $this->configs['save_ip']) { $columns[$this->getColumn('ip')] = $this->getIP(); } - if ($this->config['match_ua'] || $this->config['save_ua']) { + if ($this->configs['match_ua'] || $this->configs['save_ua']) { $columns[$this->getColumn('ua')] = $this->getUA(); } $this->addUserIdColumn($columns); @@ -306,7 +307,7 @@ public function destroy($id) : bool public function gc($max_lifetime) : false | int { try { - $this->database ??= new Database($this->config); + $this->database ??= new Database($this->configs); } catch (\Exception $exception) { $this->log( 'Session (database): Thrown a ' . \get_class($exception) diff --git a/src/SaveHandlers/FilesHandler.php b/src/SaveHandlers/FilesHandler.php index f9fe8c5..5f29746 100644 --- a/src/SaveHandlers/FilesHandler.php +++ b/src/SaveHandlers/FilesHandler.php @@ -30,7 +30,7 @@ class FilesHandler extends SaveHandler /** * Prepare configurations to be used by the FilesHandler. * - * @param array $config Custom configs + * @param array $configs Custom configs * * The custom configs are: * @@ -47,34 +47,34 @@ class FilesHandler extends SaveHandler * ]; * ``` */ - protected function prepareConfig(#[SensitiveParameter] array $config) : void + protected function prepareConfigs(#[SensitiveParameter] array $configs) : void { - $this->config = \array_replace([ + $this->configs = \array_replace([ 'prefix' => '', 'directory' => '', 'match_ip' => false, 'match_ua' => false, - ], $config); - if (empty($this->config['directory'])) { + ], $configs); + if (empty($this->configs['directory'])) { throw new LogicException('Session config has not a directory'); } - $this->config['directory'] = \rtrim( - $this->config['directory'], + $this->configs['directory'] = \rtrim( + $this->configs['directory'], \DIRECTORY_SEPARATOR ) . \DIRECTORY_SEPARATOR; - if (!\is_dir($this->config['directory'])) { + if (!\is_dir($this->configs['directory'])) { throw new LogicException( - 'Session config directory does not exist: ' . $this->config['directory'] + 'Session config directory does not exist: ' . $this->configs['directory'] ); } - if ($this->config['prefix']) { - $dirname = $this->config['directory'] . $this->config['prefix'] . \DIRECTORY_SEPARATOR; + if ($this->configs['prefix']) { + $dirname = $this->configs['directory'] . $this->configs['prefix'] . \DIRECTORY_SEPARATOR; if (!\is_dir($dirname) && !\mkdir($dirname, 0700) && !\is_dir($dirname)) { throw new RuntimeException( "Session prefix directory '{$dirname}' was not created", ); } - $this->config['directory'] = $dirname; + $this->configs['directory'] = $dirname; } } @@ -88,7 +88,7 @@ protected function prepareConfig(#[SensitiveParameter] array $config) : void */ protected function getFilename(string $id) : string { - $filename = $this->config['directory'] . $id[0] . $id[1] . \DIRECTORY_SEPARATOR . $id; + $filename = $this->configs['directory'] . $id[0] . $id[1] . \DIRECTORY_SEPARATOR . $id; return $filename . $this->getKeySuffix(); } @@ -189,10 +189,10 @@ public function destroy($id) : bool public function gc($max_lifetime) : false | int { - $dirHandle = \opendir($this->config['directory']); + $dirHandle = \opendir($this->configs['directory']); if ($dirHandle === false) { $this->log( - "Session (files): Garbage Collector could not open directory '{$this->config['directory']}'", + "Session (files): Garbage Collector could not open directory '{$this->configs['directory']}'", LogLevel::DEBUG ); return false; @@ -202,10 +202,10 @@ public function gc($max_lifetime) : false | int while (($filename = \readdir($dirHandle)) !== false) { if ($filename !== '.' && $filename !== '..' - && \is_dir($this->config['directory'] . $filename) + && \is_dir($this->configs['directory'] . $filename) ) { $gcCount += $this->gcSubdir( - $this->config['directory'] . $filename, + $this->configs['directory'] . $filename, $max_lifetime ); } diff --git a/src/SaveHandlers/MemcachedHandler.php b/src/SaveHandlers/MemcachedHandler.php index 08444b1..8ce97eb 100644 --- a/src/SaveHandlers/MemcachedHandler.php +++ b/src/SaveHandlers/MemcachedHandler.php @@ -27,7 +27,7 @@ class MemcachedHandler extends SaveHandler /** * Prepare configurations to be used by the MemcachedHandler. * - * @param array $config Custom configs + * @param array $configs Custom configs * * The custom configs are: * @@ -62,9 +62,9 @@ class MemcachedHandler extends SaveHandler * ]; * ``` */ - protected function prepareConfig(#[SensitiveParameter] array $config) : void + protected function prepareConfigs(#[SensitiveParameter] array $configs) : void { - $this->config = \array_replace_recursive([ + $this->configs = \array_replace_recursive([ 'prefix' => '', 'servers' => [ [ @@ -82,8 +82,8 @@ protected function prepareConfig(#[SensitiveParameter] array $config) : void 'maxlifetime' => null, 'match_ip' => false, 'match_ua' => false, - ], $config); - foreach ($this->config['servers'] as $index => $server) { + ], $configs); + foreach ($this->configs['servers'] as $index => $server) { if (!isset($server['host'])) { throw new OutOfBoundsException( "Memcached host not set on server config '{$index}'" @@ -132,7 +132,7 @@ protected function getExpiration(int $seconds) : int */ protected function getKey(string $id) : string { - return $this->config['prefix'] . $id . $this->getKeySuffix(); + return $this->configs['prefix'] . $id . $this->getKeySuffix(); } public function open($path, $name) : bool @@ -142,7 +142,7 @@ public function open($path, $name) : bool } $this->memcached = new Memcached(); $pool = []; - foreach ($this->config['servers'] as $server) { + foreach ($this->configs['servers'] as $server) { $host = $server['host'] . ':' . ($server['port'] ?? 11211); if (\in_array($host, $pool, true)) { $this->log( @@ -162,7 +162,7 @@ public function open($path, $name) : bool } $pool[] = $host; } - $result = $this->memcached->setOptions($this->config['options']); + $result = $this->memcached->setOptions($this->configs['options']); if ($result === false) { $this->log('Session (memcached): ' . $this->memcached->getLastErrorMessage()); } @@ -204,7 +204,7 @@ public function write($id, $data) : bool $this->memcached->replace( $this->lockId, \time(), - $this->getExpiration($this->config['lock_ttl']) + $this->getExpiration($this->configs['lock_ttl']) ); $maxlifetime = $this->getExpiration($this->getMaxlifetime()); if ($this->hasSameFingerprint($data)) { @@ -256,16 +256,16 @@ public function gc($max_lifetime) : false | int protected function lock(string $id) : bool { - $expiration = $this->getExpiration($this->config['lock_ttl']); + $expiration = $this->getExpiration($this->configs['lock_ttl']); if ($this->lockId && $this->memcached->get($this->lockId)) { return $this->memcached->replace($this->lockId, \time(), $expiration); } $lockId = $this->getKey($id) . ':lock'; $attempt = 0; - while ($attempt < $this->config['lock_attempts']) { + while ($attempt < $this->configs['lock_attempts']) { $attempt++; if ($this->memcached->get($lockId)) { - \usleep($this->config['lock_sleep']); + \usleep($this->configs['lock_sleep']); continue; } if (!$this->memcached->set($lockId, \time(), $expiration)) { @@ -275,7 +275,7 @@ protected function lock(string $id) : bool $this->lockId = $lockId; break; } - if ($attempt === $this->config['lock_attempts']) { + if ($attempt === $this->configs['lock_attempts']) { $this->log( "Session (memcached): Unable to lock {$lockId} after {$attempt} attempts" ); diff --git a/src/SaveHandlers/RedisHandler.php b/src/SaveHandlers/RedisHandler.php index 8ac9beb..e0bc825 100644 --- a/src/SaveHandlers/RedisHandler.php +++ b/src/SaveHandlers/RedisHandler.php @@ -27,7 +27,7 @@ class RedisHandler extends SaveHandler /** * Prepare configurations to be used by the RedisHandler. * - * @param array $config Custom configs + * @param array $configs Custom configs * * The custom configs are: * @@ -60,9 +60,9 @@ class RedisHandler extends SaveHandler * ]; * ``` */ - protected function prepareConfig(#[SensitiveParameter] array $config) : void + protected function prepareConfigs(#[SensitiveParameter] array $configs) : void { - $this->config = \array_replace([ + $this->configs = \array_replace([ 'prefix' => '', 'host' => '127.0.0.1', 'port' => 6379, @@ -75,7 +75,7 @@ protected function prepareConfig(#[SensitiveParameter] array $config) : void 'maxlifetime' => null, 'match_ip' => false, 'match_ua' => false, - ], $config); + ], $configs); } public function setRedis(Redis $redis) : static @@ -100,7 +100,7 @@ public function getRedis() : ?Redis */ protected function getKey(string $id) : string { - return $this->config['prefix'] . $id . $this->getKeySuffix(); + return $this->configs['prefix'] . $id . $this->getKeySuffix(); } public function open($path, $name) : bool @@ -111,30 +111,30 @@ public function open($path, $name) : bool $this->redis = new Redis(); try { @$this->redis->connect( - $this->config['host'], - $this->config['port'], - $this->config['timeout'] + $this->configs['host'], + $this->configs['port'], + $this->configs['timeout'] ); } catch (RedisException) { $this->log( 'Session (redis): Could not connect to server ' - . $this->config['host'] . ':' . $this->config['port'] + . $this->configs['host'] . ':' . $this->configs['port'] ); return false; } - if (isset($this->config['password'])) { + if (isset($this->configs['password'])) { try { - $this->redis->auth($this->config['password']); + $this->redis->auth($this->configs['password']); } catch (RedisException) { $this->log('Session (redis): Authentication failed'); return false; } } - if (isset($this->config['database']) - && !$this->redis->select($this->config['database']) + if (isset($this->configs['database']) + && !$this->redis->select($this->configs['database']) ) { $this->log( - "Session (redis): Could not select the database '{$this->config['database']}'" + "Session (redis): Could not select the database '{$this->configs['database']}'" ); return false; } @@ -171,7 +171,7 @@ public function write($id, $data) : bool return false; } $maxlifetime = $this->getMaxlifetime(); - $this->redis->expire($this->lockId, $this->config['lock_ttl']); + $this->redis->expire($this->lockId, $this->configs['lock_ttl']); if ($this->sessionExists === false || !$this->hasSameFingerprint($data)) { if ($this->redis->set($this->getKey($id), $data, $maxlifetime)) { $this->setFingerprint($data); @@ -233,17 +233,17 @@ public function gc($max_lifetime) : false | int protected function lock(string $id) : bool { - $ttl = $this->config['lock_ttl']; + $ttl = $this->configs['lock_ttl']; if ($this->lockId && $this->redis->get($this->lockId)) { return $this->redis->expire($this->lockId, $ttl); } $lockId = $this->getKey($id) . ':lock'; $attempt = 0; - while ($attempt < $this->config['lock_attempts']) { + while ($attempt < $this->configs['lock_attempts']) { $attempt++; $oldTtl = $this->redis->ttl($lockId); if (\is_int($oldTtl) && $oldTtl > 0) { - \usleep($this->config['lock_sleep']); + \usleep($this->configs['lock_sleep']); continue; } if (!$this->redis->setex($lockId, $ttl, (string) \time())) { @@ -253,7 +253,7 @@ protected function lock(string $id) : bool $this->lockId = $lockId; break; } - if ($attempt === $this->config['lock_attempts']) { + if ($attempt === $this->configs['lock_attempts']) { $this->log( "Session (redis): Unable to lock {$lockId} after {$attempt} attempts" ); From 32d00411024b807c9a942d1fcd447061fed6938f Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 22 May 2026 18:53:13 -0300 Subject: [PATCH 12/19] Update coding standard --- tests/Debug/SessionCollectorTest.php | 3 ++- tests/SaveHandlers/DatabaseHandlerTest.php | 18 ++++++++++++------ tests/SaveHandlers/FilesHandlerTest.php | 9 ++++++--- tests/SaveHandlers/MemcachedHandlerTest.php | 15 ++++++++++----- tests/SaveHandlers/RedisHandlerTest.php | 12 ++++++++---- 5 files changed, 38 insertions(+), 19 deletions(-) diff --git a/tests/Debug/SessionCollectorTest.php b/tests/Debug/SessionCollectorTest.php index 486cada..169d434 100644 --- a/tests/Debug/SessionCollectorTest.php +++ b/tests/Debug/SessionCollectorTest.php @@ -177,7 +177,8 @@ public function testSaveHandlers(SaveHandler $handler) : void public function testCustomSaveHandlers() : void { - $handler = new class() extends SaveHandler { + $handler = new class() extends SaveHandler + { public function open($path, $name) : bool { return true; diff --git a/tests/SaveHandlers/DatabaseHandlerTest.php b/tests/SaveHandlers/DatabaseHandlerTest.php index 9deaba9..0788a83 100644 --- a/tests/SaveHandlers/DatabaseHandlerTest.php +++ b/tests/SaveHandlers/DatabaseHandlerTest.php @@ -62,7 +62,8 @@ public function testUserId() : void $this->replaceConfig([ 'save_user_id' => true, ]); - $handler = new class($this->config, $this->logger) extends DatabaseHandler { + $handler = new class($this->config, $this->logger) extends DatabaseHandler + { public ?Database $database; }; $session = new Session(handler: $handler); @@ -104,7 +105,8 @@ public function testOpenError() : void public function testFailToRead() : void { - $handler = new class($this->config) extends DatabaseHandler { + $handler = new class($this->config) extends DatabaseHandler + { public ?Database $database; }; $handler->database = null; @@ -113,7 +115,8 @@ public function testFailToRead() : void public function testFailToWrite() : void { - $handler = new class($this->config) extends DatabaseHandler { + $handler = new class($this->config) extends DatabaseHandler + { public ?Database $database; public false | string $lockId; }; @@ -136,7 +139,8 @@ public function testFailToGC() : void public function testUnlockWithoutLockId() : void { - $handler = new class() extends DatabaseHandler { + $handler = new class() extends DatabaseHandler + { public function unlock() : bool { return parent::unlock(); @@ -147,7 +151,8 @@ public function unlock() : bool public function testFailToUnlock() : void { - $handler = new class($this->config) extends DatabaseHandler { + $handler = new class($this->config) extends DatabaseHandler + { public false | string $lockId; public function unlock() : bool @@ -162,7 +167,8 @@ public function unlock() : bool public function testFailToLock() : void { - $handler = new class($this->config, $this->logger) extends DatabaseHandler { + $handler = new class($this->config, $this->logger) extends DatabaseHandler + { public function lock(string $id) : bool { return parent::lock($id); diff --git a/tests/SaveHandlers/FilesHandlerTest.php b/tests/SaveHandlers/FilesHandlerTest.php index ebbced2..fbbe438 100644 --- a/tests/SaveHandlers/FilesHandlerTest.php +++ b/tests/SaveHandlers/FilesHandlerTest.php @@ -54,7 +54,8 @@ public function testPrefix() : void 'prefix' => 'foo', 'directory' => \getenv('FILES_DIR'), ]; - $handler = new class($config) extends FilesHandler { + $handler = new class($config) extends FilesHandler + { public function getFilename(string $id) : string { return parent::getFilename($id); @@ -68,7 +69,8 @@ public function getFilename(string $id) : string public function testFailToWrite() : void { - $handler = new class($this->config) extends FilesHandler { + $handler = new class($this->config) extends FilesHandler + { public $stream; }; $handler->stream = null; @@ -77,7 +79,8 @@ public function testFailToWrite() : void public function testUnlockWithoutStream() : void { - $handler = new class($this->config) extends FilesHandler { + $handler = new class($this->config) extends FilesHandler + { public $stream; public function unlock() : bool diff --git a/tests/SaveHandlers/MemcachedHandlerTest.php b/tests/SaveHandlers/MemcachedHandlerTest.php index 7669821..deab99c 100644 --- a/tests/SaveHandlers/MemcachedHandlerTest.php +++ b/tests/SaveHandlers/MemcachedHandlerTest.php @@ -116,7 +116,8 @@ public function testInvalidOption() : void public function testFailToRead() : void { - $handler = new class($this->config) extends MemcachedHandler { + $handler = new class($this->config) extends MemcachedHandler + { public ?Memcached $memcached; }; $handler->memcached = null; @@ -125,7 +126,8 @@ public function testFailToRead() : void public function testFailToWrite() : void { - $handler = new class($this->config) extends MemcachedHandler { + $handler = new class($this->config) extends MemcachedHandler + { public ?Memcached $memcached; public false | string $lockId; public ?string $sessionId; @@ -143,7 +145,8 @@ public function testFailToWrite() : void public function testUnlocked() : void { - $handler = new class($this->config) extends MemcachedHandler { + $handler = new class($this->config) extends MemcachedHandler + { public false | string $lockId; public function unlock() : bool @@ -158,7 +161,8 @@ public function unlock() : bool public function testReplaceLock() : void { - $handler = new class($this->config, $this->logger) extends MemcachedHandler { + $handler = new class($this->config, $this->logger) extends MemcachedHandler + { public ?Memcached $memcached; public false | string $lockId; @@ -175,7 +179,8 @@ public function lock(string $id) : bool public function testFailToDestroy() : void { - $handler = new class($this->config) extends MemcachedHandler { + $handler = new class($this->config) extends MemcachedHandler + { public false | string $lockId; }; $handler->lockId = false; diff --git a/tests/SaveHandlers/RedisHandlerTest.php b/tests/SaveHandlers/RedisHandlerTest.php index b8eec6e..8de490b 100644 --- a/tests/SaveHandlers/RedisHandlerTest.php +++ b/tests/SaveHandlers/RedisHandlerTest.php @@ -70,7 +70,8 @@ public function testFailToConnectWithDatabase() : void public function testFailToRead() : void { - $handler = new class($this->config) extends RedisHandler { + $handler = new class($this->config) extends RedisHandler + { public ?Redis $redis; }; $handler->redis = null; @@ -79,7 +80,8 @@ public function testFailToRead() : void public function testFailToWrite() : void { - $handler = new class($this->config) extends RedisHandler { + $handler = new class($this->config) extends RedisHandler + { public ?Redis $redis; public ?string $sessionId; @@ -103,7 +105,8 @@ public function testFailToDestroy() : void public function testFailToClose() : void { - $handler = new class($this->config) extends RedisHandler { + $handler = new class($this->config) extends RedisHandler + { public ?Redis $redis; }; $handler->open('', ''); @@ -118,7 +121,8 @@ public function testFailToClose() : void public function testUnlock() : void { - $handler = new class($this->config, $this->logger) extends RedisHandler { + $handler = new class($this->config, $this->logger) extends RedisHandler + { public false | string $lockId; public function unlock() : bool From 0a99c785f616bcee5706288bcbc8540cb67305f5 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 22 May 2026 20:01:49 -0300 Subject: [PATCH 13/19] Add SaveHandler::getConfig method --- src/SaveHandler.php | 28 ++++++++++++++++++++---- src/SaveHandlers/DatabaseHandler.php | 14 ++++++------ src/SaveHandlers/FilesHandler.php | 22 +++++++++---------- src/SaveHandlers/MemcachedHandler.php | 18 ++++++++-------- src/SaveHandlers/RedisHandler.php | 30 +++++++++++++------------- tests/SaveHandlers/AbstractHandler.php | 14 ++++++++++++ 6 files changed, 80 insertions(+), 46 deletions(-) diff --git a/src/SaveHandler.php b/src/SaveHandler.php index 71d240d..b34380a 100644 --- a/src/SaveHandler.php +++ b/src/SaveHandler.php @@ -11,6 +11,7 @@ use Framework\Log\Logger; use Framework\Log\LogLevel; +use OutOfBoundsException; use SensitiveParameter; /** @@ -106,6 +107,25 @@ public function getConfigs() : array return $this->configs; } + /** + * Get a config item by key. + * + * @param string $key + * @param bool $nullable + * + * @return mixed + */ + public function getConfig(string $key, bool $nullable = false) : mixed + { + if (!\array_key_exists($key, $this->configs)) { + if($nullable) { + return null; + } + throw new OutOfBoundsException('Invalid config key: ' . $key); + } + return $this->configs[$key]; + } + /** * Log a message if the Logger is set. * @@ -161,7 +181,7 @@ protected function hasSameFingerprint(string $data) : bool */ protected function getMaxlifetime() : int { - return (int) ($this->configs['maxlifetime'] ?? \ini_get('session.gc_maxlifetime')); + return (int) ($this->getConfig('maxlifetime', true) ?? \ini_get('session.gc_maxlifetime')); } /** @@ -171,7 +191,7 @@ protected function getMaxlifetime() : int */ protected function getIP() : string { - $key = $this->configs['ip_key'] ?? 'REMOTE_ADDR'; + $key = $this->getConfig('ip_key', true) ?? 'REMOTE_ADDR'; return $_SERVER[$key] ?? ''; } @@ -188,10 +208,10 @@ protected function getUA() : string protected function getKeySuffix() : string { $suffix = ''; - if ($this->configs['match_ip']) { + if ($this->getConfig('match_ip')) { $suffix .= ':' . $this->getIP(); } - if ($this->configs['match_ua']) { + if ($this->getConfig('match_ua')) { $suffix .= ':' . $this->getUA(); } if ($suffix) { diff --git a/src/SaveHandlers/DatabaseHandler.php b/src/SaveHandlers/DatabaseHandler.php index bf2f59d..6bbf707 100644 --- a/src/SaveHandlers/DatabaseHandler.php +++ b/src/SaveHandlers/DatabaseHandler.php @@ -116,7 +116,7 @@ public function getDatabase() : ?Database */ protected function getTable() : string { - return $this->configs['table']; + return $this->getConfig('table'); } /** @@ -128,7 +128,7 @@ protected function getTable() : string */ protected function getColumn(string $key) : string { - return $this->configs['columns'][$key]; + return $this->getConfig('columns')[$key]; } /** @@ -138,10 +138,10 @@ protected function getColumn(string $key) : string */ protected function addWhereMatchs(Delete | Select | Update $statement) : void { - if ($this->configs['match_ip']) { + if ($this->getConfig('match_ip')) { $statement->whereEqual($this->getColumn('ip'), $this->getIP()); } - if ($this->configs['match_ua']) { + if ($this->getConfig('match_ua')) { $statement->whereEqual($this->getColumn('ua'), $this->getUA()); } } @@ -153,7 +153,7 @@ protected function addWhereMatchs(Delete | Select | Update $statement) : void */ protected function addUserIdColumn(array &$columns) : void { - if ($this->configs['save_user_id']) { + if ($this->getConfig('save_user_id')) { $key = $this->getColumn('user_id'); $columns[$key] = $_SESSION[$key] ?? null; } @@ -222,10 +222,10 @@ protected function writeInsert(string $id, string $data) : bool }, $this->getColumn('data') => $data, ]; - if ($this->configs['match_ip'] || $this->configs['save_ip']) { + if ($this->getConfig('match_ip') || $this->getConfig('save_ip')) { $columns[$this->getColumn('ip')] = $this->getIP(); } - if ($this->configs['match_ua'] || $this->configs['save_ua']) { + if ($this->getConfig('match_ua') || $this->getConfig('save_ua')) { $columns[$this->getColumn('ua')] = $this->getUA(); } $this->addUserIdColumn($columns); diff --git a/src/SaveHandlers/FilesHandler.php b/src/SaveHandlers/FilesHandler.php index 5f29746..c04bb55 100644 --- a/src/SaveHandlers/FilesHandler.php +++ b/src/SaveHandlers/FilesHandler.php @@ -55,20 +55,20 @@ protected function prepareConfigs(#[SensitiveParameter] array $configs) : void 'match_ip' => false, 'match_ua' => false, ], $configs); - if (empty($this->configs['directory'])) { + if (empty($this->getConfig('directory'))) { throw new LogicException('Session config has not a directory'); } $this->configs['directory'] = \rtrim( - $this->configs['directory'], + $this->getConfig('directory'), \DIRECTORY_SEPARATOR ) . \DIRECTORY_SEPARATOR; - if (!\is_dir($this->configs['directory'])) { + if (!\is_dir($this->getConfig('directory'))) { throw new LogicException( - 'Session config directory does not exist: ' . $this->configs['directory'] + 'Session config directory does not exist: ' . $this->getConfig('directory') ); } - if ($this->configs['prefix']) { - $dirname = $this->configs['directory'] . $this->configs['prefix'] . \DIRECTORY_SEPARATOR; + if ($this->getConfig('prefix')) { + $dirname = $this->getConfig('directory') . $this->getConfig('prefix') . \DIRECTORY_SEPARATOR; if (!\is_dir($dirname) && !\mkdir($dirname, 0700) && !\is_dir($dirname)) { throw new RuntimeException( "Session prefix directory '{$dirname}' was not created", @@ -88,7 +88,7 @@ protected function prepareConfigs(#[SensitiveParameter] array $configs) : void */ protected function getFilename(string $id) : string { - $filename = $this->configs['directory'] . $id[0] . $id[1] . \DIRECTORY_SEPARATOR . $id; + $filename = $this->getConfig('directory') . $id[0] . $id[1] . \DIRECTORY_SEPARATOR . $id; return $filename . $this->getKeySuffix(); } @@ -189,10 +189,10 @@ public function destroy($id) : bool public function gc($max_lifetime) : false | int { - $dirHandle = \opendir($this->configs['directory']); + $dirHandle = \opendir($this->getConfig('directory')); if ($dirHandle === false) { $this->log( - "Session (files): Garbage Collector could not open directory '{$this->configs['directory']}'", + "Session (files): Garbage Collector could not open directory '{$this->getConfig('directory')}'", LogLevel::DEBUG ); return false; @@ -202,10 +202,10 @@ public function gc($max_lifetime) : false | int while (($filename = \readdir($dirHandle)) !== false) { if ($filename !== '.' && $filename !== '..' - && \is_dir($this->configs['directory'] . $filename) + && \is_dir($this->getConfig('directory') . $filename) ) { $gcCount += $this->gcSubdir( - $this->configs['directory'] . $filename, + $this->getConfig('directory') . $filename, $max_lifetime ); } diff --git a/src/SaveHandlers/MemcachedHandler.php b/src/SaveHandlers/MemcachedHandler.php index 8ce97eb..ec1b3d9 100644 --- a/src/SaveHandlers/MemcachedHandler.php +++ b/src/SaveHandlers/MemcachedHandler.php @@ -83,7 +83,7 @@ protected function prepareConfigs(#[SensitiveParameter] array $configs) : void 'match_ip' => false, 'match_ua' => false, ], $configs); - foreach ($this->configs['servers'] as $index => $server) { + foreach ($this->getConfig('servers') as $index => $server) { if (!isset($server['host'])) { throw new OutOfBoundsException( "Memcached host not set on server config '{$index}'" @@ -132,7 +132,7 @@ protected function getExpiration(int $seconds) : int */ protected function getKey(string $id) : string { - return $this->configs['prefix'] . $id . $this->getKeySuffix(); + return $this->getConfig('prefix') . $id . $this->getKeySuffix(); } public function open($path, $name) : bool @@ -142,7 +142,7 @@ public function open($path, $name) : bool } $this->memcached = new Memcached(); $pool = []; - foreach ($this->configs['servers'] as $server) { + foreach ($this->getConfig('servers') as $server) { $host = $server['host'] . ':' . ($server['port'] ?? 11211); if (\in_array($host, $pool, true)) { $this->log( @@ -162,7 +162,7 @@ public function open($path, $name) : bool } $pool[] = $host; } - $result = $this->memcached->setOptions($this->configs['options']); + $result = $this->memcached->setOptions($this->getConfig('options')); if ($result === false) { $this->log('Session (memcached): ' . $this->memcached->getLastErrorMessage()); } @@ -204,7 +204,7 @@ public function write($id, $data) : bool $this->memcached->replace( $this->lockId, \time(), - $this->getExpiration($this->configs['lock_ttl']) + $this->getExpiration($this->getConfig('lock_ttl')) ); $maxlifetime = $this->getExpiration($this->getMaxlifetime()); if ($this->hasSameFingerprint($data)) { @@ -256,16 +256,16 @@ public function gc($max_lifetime) : false | int protected function lock(string $id) : bool { - $expiration = $this->getExpiration($this->configs['lock_ttl']); + $expiration = $this->getExpiration($this->getConfig('lock_ttl')); if ($this->lockId && $this->memcached->get($this->lockId)) { return $this->memcached->replace($this->lockId, \time(), $expiration); } $lockId = $this->getKey($id) . ':lock'; $attempt = 0; - while ($attempt < $this->configs['lock_attempts']) { + while ($attempt < $this->getConfig('lock_attempts')) { $attempt++; if ($this->memcached->get($lockId)) { - \usleep($this->configs['lock_sleep']); + \usleep($this->getConfig('lock_sleep')); continue; } if (!$this->memcached->set($lockId, \time(), $expiration)) { @@ -275,7 +275,7 @@ protected function lock(string $id) : bool $this->lockId = $lockId; break; } - if ($attempt === $this->configs['lock_attempts']) { + if ($attempt === $this->getConfig('lock_attempts')) { $this->log( "Session (memcached): Unable to lock {$lockId} after {$attempt} attempts" ); diff --git a/src/SaveHandlers/RedisHandler.php b/src/SaveHandlers/RedisHandler.php index e0bc825..77e9918 100644 --- a/src/SaveHandlers/RedisHandler.php +++ b/src/SaveHandlers/RedisHandler.php @@ -100,7 +100,7 @@ public function getRedis() : ?Redis */ protected function getKey(string $id) : string { - return $this->configs['prefix'] . $id . $this->getKeySuffix(); + return $this->getConfig('prefix') . $id . $this->getKeySuffix(); } public function open($path, $name) : bool @@ -111,30 +111,30 @@ public function open($path, $name) : bool $this->redis = new Redis(); try { @$this->redis->connect( - $this->configs['host'], - $this->configs['port'], - $this->configs['timeout'] + $this->getConfig('host'), + $this->getConfig('port'), + $this->getConfig('timeout') ); } catch (RedisException) { $this->log( 'Session (redis): Could not connect to server ' - . $this->configs['host'] . ':' . $this->configs['port'] + . $this->getConfig('host') . ':' . $this->getConfig('port') ); return false; } - if (isset($this->configs['password'])) { + if ($this->getConfig('password', true) !== null) { try { - $this->redis->auth($this->configs['password']); + $this->redis->auth($this->getConfig('password')); } catch (RedisException) { $this->log('Session (redis): Authentication failed'); return false; } } - if (isset($this->configs['database']) - && !$this->redis->select($this->configs['database']) + if ($this->getConfig('database', true) !== null + && !$this->redis->select($this->getConfig('database')) ) { $this->log( - "Session (redis): Could not select the database '{$this->configs['database']}'" + "Session (redis): Could not select the database '{$this->getConfig('database')}'" ); return false; } @@ -171,7 +171,7 @@ public function write($id, $data) : bool return false; } $maxlifetime = $this->getMaxlifetime(); - $this->redis->expire($this->lockId, $this->configs['lock_ttl']); + $this->redis->expire($this->lockId, $this->getConfig('lock_ttl')); if ($this->sessionExists === false || !$this->hasSameFingerprint($data)) { if ($this->redis->set($this->getKey($id), $data, $maxlifetime)) { $this->setFingerprint($data); @@ -233,17 +233,17 @@ public function gc($max_lifetime) : false | int protected function lock(string $id) : bool { - $ttl = $this->configs['lock_ttl']; + $ttl = $this->getConfig('lock_ttl'); if ($this->lockId && $this->redis->get($this->lockId)) { return $this->redis->expire($this->lockId, $ttl); } $lockId = $this->getKey($id) . ':lock'; $attempt = 0; - while ($attempt < $this->configs['lock_attempts']) { + while ($attempt < $this->getConfig('lock_attempts')) { $attempt++; $oldTtl = $this->redis->ttl($lockId); if (\is_int($oldTtl) && $oldTtl > 0) { - \usleep($this->configs['lock_sleep']); + \usleep($this->getConfig('lock_sleep')); continue; } if (!$this->redis->setex($lockId, $ttl, (string) \time())) { @@ -253,7 +253,7 @@ protected function lock(string $id) : bool $this->lockId = $lockId; break; } - if ($attempt === $this->configs['lock_attempts']) { + if ($attempt === $this->getConfig('lock_attempts')) { $this->log( "Session (redis): Unable to lock {$lockId} after {$attempt} attempts" ); diff --git a/tests/SaveHandlers/AbstractHandler.php b/tests/SaveHandlers/AbstractHandler.php index 1e113f7..01ed3c3 100644 --- a/tests/SaveHandlers/AbstractHandler.php +++ b/tests/SaveHandlers/AbstractHandler.php @@ -86,4 +86,18 @@ public function testReset() : void { self::assertTrue($this->session->reset()); } + + public function testGetConfig() : void + { + if (isset($this->config['match_ip'])) { + self::assertSame( + $this->config['match_ip'], + $this->handler->getConfig('match_ip') + ); + } + self::assertNull($this->handler->getConfig('foo', true)); + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage('Invalid config key: foo'); + $this->handler->getConfig('foo'); + } } From b16a8f36076bee29a6e8636692771ed19290fa52 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 22 May 2026 20:48:29 -0300 Subject: [PATCH 14/19] Add 'database' config --- guide/index.rst | 2 ++ src/SaveHandlers/DatabaseHandler.php | 8 +++++--- tests/Debug/SessionCollectorTest.php | 14 +++++++------ tests/SaveHandlers/DatabaseHandlerTest.php | 24 +++++++++++++--------- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/guide/index.rst b/guide/index.rst index 1be97a6..57e731c 100644 --- a/guide/index.rst +++ b/guide/index.rst @@ -274,6 +274,8 @@ These are the DatabaseHandler configs: .. code-block:: php $configs = [ + // The Database configs + 'database' => [], // The name of the table used for sessions 'table' => 'Sessions', // The maxlifetime used for locking diff --git a/src/SaveHandlers/DatabaseHandler.php b/src/SaveHandlers/DatabaseHandler.php index 6bbf707..e67edb9 100644 --- a/src/SaveHandlers/DatabaseHandler.php +++ b/src/SaveHandlers/DatabaseHandler.php @@ -50,6 +50,8 @@ class DatabaseHandler extends SaveHandler * * ```php * $configs = [ + * // The Database configs + * 'database' => [], * // The name of the table used for sessions * 'table' => 'Sessions', * // The maxlifetime used for locking @@ -78,7 +80,7 @@ class DatabaseHandler extends SaveHandler protected function prepareConfigs(#[SensitiveParameter] array $configs) : void { $this->configs = \array_replace_recursive([ - 'handler' => [], + 'database' => [], 'table' => 'Sessions', 'maxlifetime' => null, 'columns' => [ @@ -162,7 +164,7 @@ protected function addUserIdColumn(array &$columns) : void public function open($path, $name) : bool { try { - $this->database ??= new Database($this->configs); + $this->database ??= new Database($this->getConfig('database')); return true; } catch (\Exception $exception) { $this->log( @@ -307,7 +309,7 @@ public function destroy($id) : bool public function gc($max_lifetime) : false | int { try { - $this->database ??= new Database($this->configs); + $this->database ??= new Database($this->getConfig('database')); } catch (\Exception $exception) { $this->log( 'Session (database): Thrown a ' . \get_class($exception) diff --git a/tests/Debug/SessionCollectorTest.php b/tests/Debug/SessionCollectorTest.php index 169d434..425a65f 100644 --- a/tests/Debug/SessionCollectorTest.php +++ b/tests/Debug/SessionCollectorTest.php @@ -264,14 +264,16 @@ public static function saveHandlerProvider() : Generator ]), ]; $config = [ - 'username' => \getenv('DB_USERNAME'), - 'password' => \getenv('DB_PASSWORD'), - 'schema' => \getenv('DB_SCHEMA'), - 'host' => \getenv('DB_HOST'), - 'port' => \getenv('DB_PORT'), + 'database' => [ + 'username' => \getenv('DB_USERNAME'), + 'password' => \getenv('DB_PASSWORD'), + 'schema' => \getenv('DB_SCHEMA'), + 'host' => \getenv('DB_HOST'), + 'port' => \getenv('DB_PORT'), + ], 'table' => \getenv('DB_TABLE'), ]; - $database = new Database($config); + $database = new Database($config['database']); $database->dropTable($config['table'])->ifExists()->run(); // @phpstan-ignore-line // @phpstan-ignore-next-line $database->createTable($config['table']) diff --git a/tests/SaveHandlers/DatabaseHandlerTest.php b/tests/SaveHandlers/DatabaseHandlerTest.php index 0788a83..693508d 100644 --- a/tests/SaveHandlers/DatabaseHandlerTest.php +++ b/tests/SaveHandlers/DatabaseHandlerTest.php @@ -26,11 +26,13 @@ class DatabaseHandlerTest extends AbstractHandler public function setUp() : void { $this->replaceConfig([ - 'username' => \getenv('DB_USERNAME'), - 'password' => \getenv('DB_PASSWORD'), - 'schema' => \getenv('DB_SCHEMA'), - 'host' => \getenv('DB_HOST'), - 'port' => \getenv('DB_PORT'), + 'database' => [ + 'username' => \getenv('DB_USERNAME'), + 'password' => \getenv('DB_PASSWORD'), + 'schema' => \getenv('DB_SCHEMA'), + 'host' => \getenv('DB_HOST'), + 'port' => \getenv('DB_PORT'), + ], 'table' => \getenv('DB_TABLE'), ]); $this->createDummyData(); @@ -39,7 +41,7 @@ public function setUp() : void protected function createDummyData() : void { - $database = new Database($this->config); + $database = new Database($this->config['database']); $database->dropTable($this->config['table'])->ifExists()->run(); $database->createTable($this->config['table']) ->definition(static function (TableDefinition $definition) : void { @@ -84,9 +86,11 @@ public function testOpenError() : void { $this->session->stop(); $handler = new DatabaseHandler([ - 'username' => 'user-error', - 'password' => \getenv('DB_PASSWORD'), - 'host' => \getenv('DB_HOST'), + 'database' => [ + 'username' => 'user-error', + 'password' => \getenv('DB_PASSWORD'), + 'host' => \getenv('DB_HOST'), + ], ], $this->logger); $session = new Session([], $handler); $this->expectException(\RuntimeException::class); @@ -122,7 +126,7 @@ public function testFailToWrite() : void }; $handler->database = null; self::assertFalse($handler->write('foo', 'data')); - $handler->database = new Database($this->config); + $handler->database = new Database($this->config['database']); $handler->lockId = false; self::assertFalse($handler->write('foo', 'data')); } From 6777b06073815af6e729cb1d786c99e03a87cda6 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 22 May 2026 20:50:04 -0300 Subject: [PATCH 15/19] Update PHPDocs --- src/SaveHandlers/DatabaseHandler.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/SaveHandlers/DatabaseHandler.php b/src/SaveHandlers/DatabaseHandler.php index e67edb9..dcd3cf7 100644 --- a/src/SaveHandlers/DatabaseHandler.php +++ b/src/SaveHandlers/DatabaseHandler.php @@ -63,6 +63,7 @@ class DatabaseHandler extends SaveHandler * 'timestamp' => 'timestamp', * 'ip' => 'ip', * 'ua' => 'ua', + * 'user_id' => 'user_id', * ], * // Match IP? * 'match_ip' => false, @@ -72,6 +73,8 @@ class DatabaseHandler extends SaveHandler * 'save_ip' => false, * // Independent of match_ua, save the initial User-Agent in the ua column? * 'save_ua' => false, + * // Save the user_id? + * 'save_user_id' => false, * ]; * ``` * From e09f5000a4f0ad76f413b855dc722c8bab2bde63 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 17 Jul 2026 19:02:37 -0300 Subject: [PATCH 16/19] Remove fallback to empty string --- src/SaveHandler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SaveHandler.php b/src/SaveHandler.php index b34380a..7801001 100644 --- a/src/SaveHandler.php +++ b/src/SaveHandler.php @@ -192,7 +192,7 @@ protected function getMaxlifetime() : int protected function getIP() : string { $key = $this->getConfig('ip_key', true) ?? 'REMOTE_ADDR'; - return $_SERVER[$key] ?? ''; + return $_SERVER[$key]; } /** @@ -202,7 +202,7 @@ protected function getIP() : string */ protected function getUA() : string { - return $_SERVER['HTTP_USER_AGENT'] ?? ''; + return $_SERVER['HTTP_USER_AGENT']; } protected function getKeySuffix() : string From 434993f6931a88c453570c2ad1f25da1d7b34064 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 17 Jul 2026 19:55:20 -0300 Subject: [PATCH 17/19] Allow User-Agent fallback to an empty string --- src/SaveHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SaveHandler.php b/src/SaveHandler.php index 7801001..f379f74 100644 --- a/src/SaveHandler.php +++ b/src/SaveHandler.php @@ -202,7 +202,7 @@ protected function getIP() : string */ protected function getUA() : string { - return $_SERVER['HTTP_USER_AGENT']; + return $_SERVER['HTTP_USER_AGENT'] ?? ''; } protected function getKeySuffix() : string From 2b9a62467c1040c206d4240f94bd1031fd152510 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 17 Jul 2026 19:55:42 -0300 Subject: [PATCH 18/19] Update PHPDocs --- src/SaveHandler.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/SaveHandler.php b/src/SaveHandler.php index f379f74..212ca7b 100644 --- a/src/SaveHandler.php +++ b/src/SaveHandler.php @@ -187,6 +187,8 @@ protected function getMaxlifetime() : int /** * Get the remote IP address. * + * - The IP address is present in all HTTP requests. + * * @return string */ protected function getIP() : string @@ -198,6 +200,9 @@ protected function getIP() : string /** * Get the HTTP User-Agent. * + * - Allows falling back to an empty string because the User-Agent header + * can be omitted. + * * @return string */ protected function getUA() : string From 65d93a6867665274ea5b5c6ce8b5c082ca26a7c9 Mon Sep 17 00:00:00 2001 From: Natan Felles Date: Fri, 17 Jul 2026 20:00:40 -0300 Subject: [PATCH 19/19] Update Checkout to v6 --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9deb65a..e5ad299 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -112,7 +112,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2