diff --git a/.cs.php b/.cs.php index e4ab7fc..5568e99 100644 --- a/.cs.php +++ b/.cs.php @@ -1,6 +1,8 @@ setUsingCache(false) ->setRiskyAllowed(true) ->setRules( @@ -17,8 +19,14 @@ 'array_syntax' => ['syntax' => 'short'], 'cast_spaces' => ['space' => 'none'], 'concat_space' => ['spacing' => 'one'], - 'compact_nullable_typehint' => true, + 'compact_nullable_type_declaration' => true, 'declare_equal_normalize' => ['space' => 'single'], + 'general_phpdoc_annotation_remove' => [ + 'annotations' => [ + 'author', + 'package', + ], + ], 'increment_style' => ['style' => 'post'], 'list_syntax' => ['syntax' => 'short'], 'echo_tag_syntax' => ['format' => 'long'], @@ -32,9 +40,31 @@ 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], 'ordered_imports' => [ 'sort_algorithm' => 'alpha', - 'imports_order' => ['class', 'const', 'function'] + 'imports_order' => ['class', 'function', 'const'], ], 'single_line_throw' => false, + 'declare_strict_types' => false, + 'blank_line_between_import_groups' => true, + 'fully_qualified_strict_types' => true, + 'no_null_property_initialization' => false, + 'nullable_type_declaration_for_default_null_value' => false, + 'operator_linebreak' => [ + 'only_booleans' => true, + 'position' => 'beginning', + ], + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => null, + 'import_functions' => null + ], + 'class_definition' => [ + 'space_before_parenthesis' => true, + ], + 'declare_equal_normalize' => false, + 'phpdoc_summary' => false, + 'phpdoc_add_missing_param_annotation' => false, + 'no_useless_concat_operator' => false, + 'fully_qualified_strict_types' => false, ] ) ->setFinder( diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ef65bbd..f2acf8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,14 +1,20 @@ name: build -on: [push, pull_request] +on: + push: + branches: + - '6.x' + pull_request: + branches: + - '*' jobs: run: runs-on: ${{ matrix.operating-system }} strategy: matrix: - operating-system: [ubuntu-latest] - php-versions: ['7.3', '7.4', '8.0'] + operating-system: [ ubuntu-latest ] + php-versions: [ '8.2', '8.3', '8.4' ] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: @@ -19,7 +25,6 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - extensions: mbstring, intl, zip coverage: none - name: Check PHP Version @@ -34,15 +39,8 @@ jobs: - name: Validate composer.json and composer.lock run: composer validate - - name: Install dependencies for PHP 7 - if: matrix.php-versions < '8.0' - run: composer update --prefer-dist --no-progress - - - name: Install dependencies for PHP 8 - if: matrix.php-versions >= '8.0' - run: composer update --prefer-dist --no-progress --ignore-platform-req=php + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest - name: Run test suite run: composer test:all - env: - PHP_CS_FIXER_IGNORE_ENV: 1 diff --git a/.gitignore b/.gitignore index 0f64bed..d1e4bf6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,18 @@ -nbproject/ -.idea/ -composer.phar +# Composer composer.lock -.DS_STORE -cache.properties +/vendor + +# PHPUnit +/.phpunit.cache .phpunit.result.cache -.php_cs.cache -.vscode -vendor/ -build/ \ No newline at end of file + +# IDEs +/.fleet +/.idea +/.vscode + +# Build artifacts and temporary files +.DS_Store +clover.xml +/build +/coverage diff --git a/.scrutinizer.yml b/.scrutinizer.yml index fce7d02..5df7262 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,6 +1,6 @@ filter: - paths: ["src/*"] - excluded_paths: ["vendor/*", "tests/*"] + paths: [ "src/*" ] + excluded_paths: [ "vendor/*", "tests/*" ] checks: php: @@ -12,7 +12,10 @@ tools: build: environment: - php: 7.4 + php: + version: 8.1.2 + ini: + xdebug.mode: coverage mysql: false node: false postgresql: false @@ -30,11 +33,10 @@ build: dependencies: before: - composer self-update - - composer update --no-interaction --prefer-dist --no-progress + - composer install --no-interaction --prefer-dist --no-progress tests: before: - - - command: composer test:coverage - coverage: - file: 'build/logs/clover.xml' - format: 'clover' + - command: composer test:coverage + coverage: + file: 'build/logs/clover.xml' + format: 'clover' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5a34a36 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,66 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [6.3.0] - 2024-12-15 + +* Add support for PHP 8.4 + +## [6.2.0] - 2024-09-20 + +### Added + +* Add support for PHP 8.3 +* Add support for psr/http-message 2.x + +### Changes + +* Upgrade tests to PHPUnit 11 + +### Removed + +* Drop support for PHP 8.0 and 8.1 + +## [6.1.0] - 2023-02-22 + +### Added + +* Add `has` method to `SessionInterface` #30 #29 +* Add PHP 8.2 to build pipeline + +### Changed + +* Update docs + +## [6.0.0] - 2022-12-04 + +### Changes + +* Require PHP 8.0+ +* Make session settings "immutable". +* Move all session settings to the `PhpSession` constructor. +* Provide interfaces for each concern (management and session data). +* Change `SessionInterface` to handle session data operations only, e.g. `get`, `set`. +* Rename session method `replace` to `setValues`. +* Rename session method `remove` to `delete`. +* Calling the session `save` method is now optional. +* Rename class `Odan\Session\Middleware\SessionMiddleware` to `Odan\Session\Middleware\SessionStartMiddleware`. + +### Added + +* Add `SessionManagerInterface` to handle session operations, such as `start`, `save`, `destroy`, `getName`, etc. +* Add `default` parameter to session `get` method. + +### Removed + +* Remove session method `setOptions` and `getOptions`. Pass all settings into `PhpSession` constructor instead. +* Remove session method `setCookieParams` and `getCookieParams`. The cookie parameters must be + defined in the settings and will set in the session `start` method. +* Remove session `setName` method. Use the `name` setting instead. +* Remove session `setId` method. Use the optional `id` setting instead. +* Remove session `count` method. +* Remove `SessionAwareInterface` in favor of dependency injection. + diff --git a/LICENSE b/LICENSE index fa19b85..bf7a9a3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2021 odan +Copyright (c) 2023 odan 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/README.md b/README.md index 8e2e86c..2a04416 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,8 @@ [![Latest Version on Packagist](https://img.shields.io/github/release/odan/session.svg)](https://github.com/odan/session/releases) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) -[![Build Status](https://github.com/odan/session/workflows/build/badge.svg)](https://github.com/odan/session/actions) -[![Code Coverage](https://scrutinizer-ci.com/g/odan/session/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/odan/session/?branch=master) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/odan/session/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/odan/session/?branch=master) [![Total Downloads](https://img.shields.io/packagist/dt/odan/session.svg)](https://packagist.org/packages/odan/session/stats) A middleware (PSR-15) oriented session and flash message handler for PHP. -* Blog post: -* Documentation for v4: -* Documentation for v5: -* Issues: +* Documentation for v6: diff --git a/composer.json b/composer.json index 0bbb6ef..5668aca 100644 --- a/composer.json +++ b/composer.json @@ -9,36 +9,42 @@ "homepage": "https://github.com/odan/session", "license": "MIT", "require": { - "php": "^7.3 || ^8.0", - "psr/http-message": "^1", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "psr/http-message": "^1 || ^2", "psr/http-server-handler": "^1", "psr/http-server-middleware": "^1" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3", "middlewares/utils": "^3", - "overtrue/phplint": "^2", - "phpstan/phpstan": "^1", - "phpunit/phpunit": "^7 || ^8 || ^9", - "slim/psr7": "^1", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^11", "squizlabs/php_codesniffer": "^3" }, "scripts": { - "cs:check": "php-cs-fixer fix --dry-run --format=txt --verbose --diff --config=.cs.php", - "cs:fix": "php-cs-fixer fix --config=.cs.php", - "lint": "phplint ./ --exclude=vendor --no-interaction --no-cache", - "stan": "phpstan analyse -c phpstan.neon --no-progress --ansi", + "cs:check": [ + "@putenv PHP_CS_FIXER_IGNORE_ENV=1", + "php-cs-fixer fix --dry-run --format=txt --verbose --config=.cs.php --ansi" + ], + "cs:fix": [ + "@putenv PHP_CS_FIXER_IGNORE_ENV=1", + "php-cs-fixer fix --config=.cs.php --ansi --verbose" + ], "sniffer:check": "phpcs --standard=phpcs.xml", "sniffer:fix": "phpcbf --standard=phpcs.xml", - "test": "phpunit --configuration phpunit.xml --do-not-cache-result --colors=always", + "stan": "phpstan analyse -c phpstan.neon --no-progress --ansi", + "test": "phpunit --configuration phpunit.xml --do-not-cache-result --colors=always --display-warnings --display-deprecations --no-coverage", "test:all": [ - "@lint", "@cs:check", "@sniffer:check", "@stan", "@test" ], - "test:coverage": "php -d xdebug.mode=coverage -r \"require 'vendor/bin/phpunit';\" -- --configuration phpunit.xml --do-not-cache-result --colors=always --coverage-clover build/logs/clover.xml --coverage-html build/coverage" + "test:coverage": [ + "@putenv XDEBUG_MODE=coverage", + "phpunit --configuration phpunit.xml --do-not-cache-result --colors=always --display-warnings --display-deprecations --coverage-clover build/coverage/clover.xml --coverage-html build/coverage --coverage-text" + ] }, "autoload": { "psr-4": { diff --git a/docs/index.md b/docs/index.md index 41cf310..7d1b2f0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,5 +19,5 @@ composer require odan/session * Issues: * Source: -* Documentation for v4: -* Documentation for v5: \ No newline at end of file +* Documentation for v5: +* Documentation for v6: \ No newline at end of file diff --git a/docs/v5/index.md b/docs/v5/index.md index 2e2d126..df098a3 100644 --- a/docs/v5/index.md +++ b/docs/v5/index.md @@ -44,7 +44,7 @@ composer require odan/session ```php use Odan\Session\PhpSession; -// Create a standard session hanndler +// Create a standard session handler $session = new PhpSession(); // Set session options before you start the session @@ -162,7 +162,7 @@ $messages = $flash->all(); ### Twig flash messages -Add the Flash instance as global twig variable within the `Twig::class` container definiton: +Add the Flash instance as global twig variable within the `Twig::class` container definition: ```php use Odan\Session\SessionInterface; @@ -197,9 +197,9 @@ $session = new PhpSession(); $session->setOptions([ 'name' => 'app', - // Lax will sent the cookie for cross-domain GET requests + // Lax will send the cookie for cross-domain GET requests 'cookie_samesite' => 'Lax', - // Optional: Sent cookie only over https + // Optional: Send cookie only over https 'cookie_secure' => true, // Optional: Additional XSS protection // Note: The cookie is not accessible for JavaScript! diff --git a/docs/v6/index.md b/docs/v6/index.md new file mode 100644 index 0000000..2ac2212 --- /dev/null +++ b/docs/v6/index.md @@ -0,0 +1,305 @@ +--- +layout: default +title: Version 6 +nav_order: 4 +description: "Version 6" +--- + +# Session v6 Documentation + +## Table of contents + +* [Requirements](#requirements) +* [Installation](#installation) +* [Features](#features) +* [Usage](#usage) +* [Methods](#methods) +* [Flash messages](#flash-messages) + * [Twig flash messages](#twig-flash-messages) +* [SameSite Cookies](#samesite-cookies) +* [Adapter](#adapter) + * [PHP Session](#php-session) + * [Memory Session](#memory-session) +* [Slim 4 integration](#slim-4-integration) + +## Requirements + +* PHP 8.2+ + +## Installation + +``` +composer require odan/session +``` + +## Features + +* PSR-7 and PSR-15 (middleware) support +* DI container (PSR-11) support +* Lazy session start + +## Usage + +```php +$config = [ + 'name' => 'app', +]; + +// Create a standard session handler +$session = new \Odan\Session\PhpSession($config); + +// Start the session +$session->start(); + +// Set session value +$session->set('bar', 'foo'); + +// Get session value +echo $session->get('bar'); // foo + +// Add flash message +$session->getFlash()->add('error', 'My flash message') +``` + +## Methods + +```php +// Get session variable +$foo = $session->get('foo'); + +// Get session variable or the default value +$bar = $session->get('bar', 'my default value'); + +// Set session variable +$session->set('bar', 'new value'); + +// Sets multiple values at once +$session->setValues(['foo' => 'value1', 'bar' => 'value2']); + +// Get all session variables +$values = $session->all(); + +// Returns true if the attribute exists +$hasKey = $session->has('foo'); + +// Delete a session variable +$session->delete('key'); + +// Clear all session variables +$session->clear(); + +// Generate a new session ID +$session->regenerateId(); + +// Get the current session ID +$sessionId = $session->getId(); + +// Get the session name +$sessionName = $session->getName(); + +// Force the session to be saved and closed +$session->save(); +``` + +## Flash messages + +The library provides its own implementation of Flash messages. + +```php +// Get flash object +$flash = $session->getFlash(); + +// Clear all flash messages +$flash->clear(); + +// Add flash message +$flash->add('error', 'Login failed'); + +// Get flash messages +$messages = $flash->get('error'); + +// Has flash message +$has = $flash->has('error'); + +// Set all messages +$flash->set('error', ['Message 1', 'Message 2']); + +// Gets all flash messages +$messages = $flash->all(); +``` + +### Twig flash messages + +To display the Flash messages, you can pass the Flash +object in the array of options as the second argument: + +```php +$flash = $session->getFlash(); +$html = $twig->render('filename.html.twig', ['flash' => $flash]); +``` + +Another approach would be to add the Flash instance +as global Twig variable within the DI container definition of `Twig::class`: + +```php +use Odan\Session\SessionInterface; + +// ... + +$flash = $container->get(SessionInterface::class)->getFlash(); +$twig->getEnvironment()->addGlobal('flash', $flash); +``` + +Twig template example: + +{% raw %} +```twig +{% for message in flash.get('error') %} + +{% endfor %} +``` +{% endraw %} + +## SameSite Cookies + +A SameSite cookie that tells browser to send the cookie to the server only +when the request is made from the same domain of the website. + +```php +use Odan\Session\PhpSession; + +$options = [ + 'name' => 'app', + // Lax will send the cookie for cross-domain GET requests + 'cookie_samesite' => 'Lax', + // Optional: Send cookie only over https + 'cookie_secure' => true, + // Optional: Additional XSS protection + // Note: This cookie is not accessible in JavaScript! + 'cookie_httponly' => false, +]; + +$session = new PhpSession($options); +$session->start(); +``` + +Read more: + +* [SameSite cookie middleware](https://github.com/selective-php/samesite-cookie) +* +* +* + +## Adapter + +### PHP Session + +* The default PHP session handler +* Uses the native PHP session functions + +Example: + +```php +use Odan\Session\PhpSession; + +$session = new PhpSession(); +``` + +### Memory Session + +* Optimized for integration tests (with phpunit) +* Prevent output buffer issues +* Run sessions only in memory + +```php +use Odan\Session\MemorySession; + +$session = new MemorySession(); +``` + +## Slim 4 Integration + +### Configuration + +Add your application-specific settings: + +```php +$settings['session'] = [ + 'name' => 'app', + 'lifetime' => 7200, + 'save_path' => null, + 'domain' => null, + 'secure' => false, + 'httponly' => true, + 'cache_limiter' => 'nocache', +]; +``` + +For this example we use the [PHP-DI](http://php-di.org/) package. + +Add the container definitions as follows: + +```php + function (ContainerInterface $container) { + return $container->get(SessionInterface::class); + }, + + SessionInterface::class => function (ContainerInterface $container) { + $options = $container->get('settings')['session']; + + return new PhpSession($options); + }, +]; +``` + +### Session middleware + +**Lazy session start** + +The DI container should (must) never start a session automatically because: + +* The DI container is not responsible for the HTTP context. +* In some use cases an API call from a REST client generates a session. +* Only an HTTP middleware or an action handler should start the session. + +Register the session middleware for all routes: + +```php +use Odan\Session\Middleware\SessionStartMiddleware; +//... + +$app->add(SessionStartMiddleware::class); +``` + +Register middleware for a routing group: + +```php +use Odan\Session\Middleware\SessionStartMiddleware; +use Slim\Routing\RouteCollectorProxy; + +// Protect the whole group +$app->group('/admin', function (RouteCollectorProxy $group) { + // ... +})->add(SessionStartMiddleware::class); +``` + +Register middleware for a single route: + +```php +use Odan\Session\Middleware\SessionStartMiddleware; + +$app->post('/example', \App\Action\ExampleAction::class) + ->add(SessionStartMiddleware::class); +``` diff --git a/phpcs.xml b/phpcs.xml index 55c4e02..82204fd 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -3,6 +3,7 @@ + @@ -12,28 +13,15 @@ - - warning + + + + - - warning - */tests/ + + + + + - - warning - - - warning - - - - warning - - - warning - - \ No newline at end of file + diff --git a/phpstan.neon b/phpstan.neon index 45c74b8..7f33c04 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,6 +3,3 @@ parameters: paths: - src - tests - checkGenericClassInNonGenericObjectType: false - checkMissingIterableValueType: false - inferPrivatePropertyTypeFromConstructor: true diff --git a/phpunit.xml b/phpunit.xml index ee34618..20e59cb 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,17 +1,29 @@ - - - - tests - - - - - src - - vendor - build - - - + + + + tests + + + + + + + + + src + + + build + vendor + + diff --git a/src/Flash.php b/src/Flash.php index 2b79fa7..38038bc 100644 --- a/src/Flash.php +++ b/src/Flash.php @@ -2,7 +2,7 @@ namespace Odan\Session; -use ArrayObject; +use ArrayAccess; /** * Flash messages. @@ -10,34 +10,21 @@ final class Flash implements FlashInterface { /** - * Message storage. - * - * @var ArrayObject + * @var array|ArrayAccess */ - private $storage; + private array|ArrayAccess $storage; - /** - * Message storage key. - * - * @var string - */ - private $storageKey; + private string $storageKey; /** - * The constructor. - * - * @param ArrayObject $storage The storage - * @param string $storageKey The flash storage key + * @param array|ArrayAccess $storage */ - public function __construct(ArrayObject $storage, string $storageKey = '_flash') + public function __construct(array|ArrayAccess &$storage, string $storageKey = '_flash') { - $this->storage = $storage; + $this->storage = &$storage; $this->storageKey = $storageKey; } - /** - * {@inheritdoc} - */ public function add(string $key, string $message): void { // Create array for this key @@ -49,9 +36,6 @@ public function add(string $key, string $message): void $this->storage[$this->storageKey][$key][] = $message; } - /** - * {@inheritdoc} - */ public function get(string $key): array { if (!$this->has($key)) { @@ -64,35 +48,21 @@ public function get(string $key): array return (array)$return; } - /** - * {@inheritdoc} - */ public function has(string $key): bool { return isset($this->storage[$this->storageKey][$key]); } - /** - * {@inheritdoc} - */ public function clear(): void { - if ($this->storage->offsetExists($this->storageKey)) { - $this->storage->offsetUnset($this->storageKey); - } + unset($this->storage[$this->storageKey]); } - /** - * {@inheritdoc} - */ public function set(string $key, array $messages): void { $this->storage[$this->storageKey][$key] = $messages; } - /** - * {@inheritdoc} - */ public function all(): array { $result = $this->storage[$this->storageKey] ?? []; diff --git a/src/FlashInterface.php b/src/FlashInterface.php index bfb6307..674e83e 100644 --- a/src/FlashInterface.php +++ b/src/FlashInterface.php @@ -22,16 +22,16 @@ public function add(string $key, string $message): void; * * @param string $key The key to get the message from * - * @return array The messages + * @return array The messages */ public function get(string $key): array; /** * Has flash message. * - * @param string $key The key to get the message from + * @param string $key The key * - * @return bool Whether the message is set or not + * @return bool Whether the key is set or not */ public function has(string $key): bool; @@ -55,7 +55,7 @@ public function set(string $key, array $messages): void; /** * Gets all flash messages. * - * @return array All messages. Can be an empty array. + * @return array All messages. Can be an empty array. */ public function all(): array; } diff --git a/src/MemorySession.php b/src/MemorySession.php index c5e2e61..aa17d80 100644 --- a/src/MemorySession.php +++ b/src/MemorySession.php @@ -2,90 +2,52 @@ namespace Odan\Session; -use ArrayObject; -use RuntimeException; - /** * A memory (array) session handler adapter. */ -final class MemorySession implements SessionInterface +final class MemorySession implements SessionInterface, SessionManagerInterface { /** - * @var ArrayObject + * @var array */ - private $storage; + private array $options = [ + 'name' => 'app', + 'lifetime' => 7200, + ]; /** - * @var Flash + * @var array */ - private $flash; + private array $storage; - /** - * @var string - */ - private $id = ''; + private Flash $flash; - /** - * @var bool - */ - private $started = false; + private string $id = ''; - /** - * @var string - */ - private $name = ''; - - /** - * @var array - */ - private $config = []; - - /** - * @var array - */ - private $cookie = []; + private bool $started = false; /** - * The constructor. + * @param array $options */ - public function __construct() + public function __construct(array $options = []) { - $this->storage = new ArrayObject(); - $this->flash = new Flash($this->storage); - - $this->setCookieParams(0, '/', '', false, true); - - $config = []; - foreach ((array)ini_get_all('session') as $key => $value) { - $config[substr($key, 8)] = $value['local_value']; + $keys = array_keys($this->options); + foreach ($keys as $key) { + if (array_key_exists($key, $options)) { + $this->options[$key] = $options[$key]; + } } - $this->setOptions($config); - } - - /** - * Get storage. - * - * @return ArrayObject The storage - */ - public function getStorage(): ArrayObject - { - return $this->storage; + $session = []; + $this->storage = &$session; + $this->flash = new Flash($session); } - /** - * Get flash instance. - * - * @return FlashInterface The flash instance - */ public function getFlash(): FlashInterface { return $this->flash; } - /** - * {@inheritdoc} - */ public function start(): void { if (!$this->id) { @@ -95,191 +57,76 @@ public function start(): void $this->started = true; } - /** - * {@inheritdoc} - */ public function isStarted(): bool { return $this->started; } - /** - * {@inheritdoc} - */ public function regenerateId(): void { $this->id = str_replace('.', '', uniqid('sess_', true)); } - /** - * {@inheritdoc} - */ public function destroy(): void { - $this->storage->exchangeArray([]); + $keys = array_keys($this->storage); + foreach ($keys as $key) { + unset($this->storage[$key]); + } $this->regenerateId(); } - /** - * {@inheritdoc} - */ public function getId(): string { return $this->id; } - /** - * {@inheritdoc} - */ - public function setId(string $id): void - { - if ($this->isStarted()) { - throw new RuntimeException('Cannot change session id when session is active'); - } - - $this->id = $id; - } - - /** - * {@inheritdoc} - */ public function getName(): string { - return $this->name; + return $this->options['name']; } - /** - * {@inheritdoc} - */ - public function setName(string $name): void + public function get(string $key, mixed $default = null): mixed { - if ($this->isStarted()) { - throw new RuntimeException('Cannot change session name when session is active'); - } - $this->name = $name; + return $this->storage[$key] ?? $default; } - /** - * {@inheritdoc} - */ - public function has(string $key): bool - { - if (!count($this->storage)) { - return false; - } - - return $this->storage->offsetExists($key); - } - - /** - * {@inheritdoc} - */ - public function get(string $key) - { - if ($this->has($key)) { - return $this->storage->offsetGet($key); - } - - return null; - } - - /** - * {@inheritdoc} - */ public function all(): array { return (array)$this->storage; } - /** - * {@inheritdoc} - */ - public function set(string $key, $value): void + public function set(string $key, mixed $value): void { $this->storage[$key] = $value; } - /** - * {@inheritdoc} - */ - public function replace(array $values): void - { - $this->storage->exchangeArray(array_replace_recursive($this->storage->getArrayCopy(), $values)); - } - - /** - * {@inheritdoc} - */ - public function remove(string $key): void + public function setValues(array $values): void { - $this->storage->offsetUnset($key); - } - - /** - * {@inheritdoc} - */ - public function clear(): void - { - $this->storage->exchangeArray([]); + foreach ($values as $key => $value) { + $this->storage[$key] = $value; + } } - /** - * {@inheritdoc} - */ - public function count(): int + public function has(string $key): bool { - return $this->storage->count(); + return array_key_exists($key, $this->storage); } - /** - * {@inheritdoc} - */ - public function save(): void + public function delete(string $key): void { + unset($this->storage[$key]); } - /** - * {@inheritdoc} - */ - public function setOptions(array $config): void + public function clear(): void { - foreach ($config as $key => $value) { - $this->config[$key] = $value; + $keys = array_keys($this->storage); + foreach ($keys as $key) { + unset($this->storage[$key]); } } - /** - * {@inheritdoc} - */ - public function getOptions(): array - { - return $this->config; - } - - /** - * {@inheritdoc} - */ - public function setCookieParams( - int $lifetime, - string $path = null, - string $domain = null, - bool $secure = false, - bool $httpOnly = false - ): void { - $this->cookie = [ - 'lifetime' => $lifetime, - 'path' => $path, - 'domain' => $domain, - 'secure' => $secure, - 'httponly' => $httpOnly, - ]; - } - - /** - * {@inheritdoc} - */ - public function getCookieParams(): array + public function save(): void { - return $this->cookie; } } diff --git a/src/Middleware/SessionMiddleware.php b/src/Middleware/SessionStartMiddleware.php similarity index 50% rename from src/Middleware/SessionMiddleware.php rename to src/Middleware/SessionStartMiddleware.php index 556b44b..97090f9 100644 --- a/src/Middleware/SessionMiddleware.php +++ b/src/Middleware/SessionStartMiddleware.php @@ -2,40 +2,21 @@ namespace Odan\Session\Middleware; -use Odan\Session\SessionInterface; +use Odan\Session\SessionManagerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -/** - * A PSR-15 Session Middleware. - */ -final class SessionMiddleware implements MiddlewareInterface +final class SessionStartMiddleware implements MiddlewareInterface { - /** - * @var SessionInterface - */ - private $session; + private SessionManagerInterface $session; - /** - * Constructor. - * - * @param SessionInterface $session The session handler - */ - public function __construct(SessionInterface $session) + public function __construct(SessionManagerInterface $session) { $this->session = $session; } - /** - * Invoke middleware. - * - * @param ServerRequestInterface $request The request - * @param RequestHandlerInterface $handler The handler - * - * @return ResponseInterface The response - */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { if (!$this->session->isStarted()) { diff --git a/src/PhpSession.php b/src/PhpSession.php index de9d6ac..50c4100 100644 --- a/src/PhpSession.php +++ b/src/PhpSession.php @@ -2,49 +2,59 @@ namespace Odan\Session; -use ArrayObject; use Odan\Session\Exception\SessionException; /** * A PHP Session handler adapter. */ -final class PhpSession implements SessionInterface +final class PhpSession implements SessionInterface, SessionManagerInterface { /** - * @var ArrayObject + * @var array */ - private $storage; + private array $storage; - /** - * @var FlashInterface - */ - private $flash; + private FlashInterface $flash; /** - * The constructor. - * - * @param ArrayObject|null $storage The session storage - * @param FlashInterface|null $flash The flash component + * @var array */ - public function __construct(ArrayObject $storage = null, FlashInterface $flash = null) - { - $this->storage = $storage ?? new ArrayObject(); - $this->flash = $flash ?? new Flash($this->storage); - } + private array $options = [ + 'id' => null, + 'name' => 'app', + 'lifetime' => 7200, + 'path' => null, + 'domain' => null, + 'secure' => false, + 'httponly' => true, + // public, private_no_expire, private, nocache + // Setting the cache limiter to '' will turn off automatic sending of cache headers entirely. + 'cache_limiter' => 'nocache', + ]; /** - * Get flash instance. - * - * @return FlashInterface The flash instance + * @param array $options */ - public function getFlash(): FlashInterface + public function __construct(array $options = []) { - return $this->flash; + // Prevent uninitialized state + $empty = []; + $this->storage = &$empty; + $this->flash = new Flash($empty); + + $keys = array_keys($this->options); + foreach ($keys as $key) { + if (array_key_exists($key, $options)) { + $this->options[$key] = $options[$key]; + unset($options[$key]); + } + } + + foreach ($options as $key => $value) { + ini_set('session.' . $key, $value); + } } - /** - * {@inheritdoc} - */ public function start(): void { if ($this->isStarted()) { @@ -61,26 +71,38 @@ public function start(): void ); } + $current = session_get_cookie_params(); + + $lifetime = (int)($this->options['lifetime'] ?: $current['lifetime']); + $path = $this->options['path'] ?: $current['path']; + $domain = $this->options['domain'] ?: $current['domain']; + $secure = (bool)$this->options['secure']; + $httponly = (bool)$this->options['httponly']; + + session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly); + session_name($this->options['name']); + session_cache_limiter($this->options['cache_limiter']); + + $sessionId = $this->options['id'] ?: null; + if ($sessionId) { + session_id($sessionId); + } + // Try and start the session if (!session_start()) { throw new SessionException('Failed to start the session.'); } // Load the session - $this->storage->exchangeArray($_SESSION ?? []); + $this->storage = &$_SESSION; + $this->flash = new Flash($_SESSION); } - /** - * {@inheritdoc} - */ public function isStarted(): bool { return session_status() === PHP_SESSION_ACTIVE; } - /** - * {@inheritdoc} - */ public function regenerateId(): void { if (!$this->isStarted()) { @@ -96,12 +118,8 @@ public function regenerateId(): void } } - /** - * {@inheritdoc} - */ public function destroy(): void { - // Cannot regenerate the session ID for non-active sessions. if (!$this->isStarted()) { return; } @@ -130,166 +148,63 @@ public function destroy(): void } } - /** - * {@inheritdoc} - */ public function getId(): string { return (string)session_id(); } - /** - * {@inheritdoc} - */ - public function setId(string $id): void - { - if ($this->isStarted()) { - throw new SessionException('Cannot change session id when session is active'); - } - - session_id($id); - } - - /** - * {@inheritdoc} - */ public function getName(): string { return (string)session_name(); } - /** - * {@inheritdoc} - */ - public function setName(string $name): void - { - if ($this->isStarted()) { - throw new SessionException('Cannot change session name when session is active'); - } - session_name($name); - } - - /** - * {@inheritdoc} - */ - public function has(string $key): bool + public function get(string $key, mixed $default = null): mixed { - if (!count($this->storage)) { - return false; - } - - return $this->storage->offsetExists($key); + return $this->storage[$key] ?? $default; } - /** - * {@inheritdoc} - */ - public function get(string $key) - { - return $this->has($key) ? $this->storage->offsetGet($key) : null; - } - - /** - * {@inheritdoc} - */ public function all(): array { return (array)$this->storage; } - /** - * {@inheritdoc} - */ - public function set(string $key, $value): void + public function set(string $key, mixed $value): void { - $this->storage->offsetSet($key, $value); + $this->storage[$key] = $value; } - /** - * {@inheritdoc} - */ - public function replace(array $values): void + public function setValues(array $values): void { - $this->storage->exchangeArray( - array_replace_recursive($this->storage->getArrayCopy(), $values) - ); + foreach ($values as $key => $value) { + $this->storage[$key] = $value; + } } - /** - * {@inheritdoc} - */ - public function remove(string $key): void + public function has(string $key): bool { - $this->storage->offsetUnset($key); + return array_key_exists($key, $this->storage); } - /** - * {@inheritdoc} - */ - public function clear(): void + public function delete(string $key): void { - $this->storage->exchangeArray([]); + unset($this->storage[$key]); } - /** - * {@inheritdoc} - */ - public function count(): int + public function clear(): void { - return $this->storage->count(); + $keys = array_keys($this->storage); + foreach ($keys as $key) { + unset($this->storage[$key]); + } } - /** - * {@inheritdoc} - */ public function save(): void { - $_SESSION = (array)$this->storage; session_write_close(); } - /** - * {@inheritdoc} - */ - public function setOptions(array $config): void - { - foreach ($config as $key => $value) { - ini_set('session.' . $key, $value); - } - } - - /** - * {@inheritdoc} - */ - public function getOptions(): array - { - $config = []; - - foreach ((array)ini_get_all('session') as $key => $value) { - $config[substr($key, 8)] = $value['local_value']; - } - - return $config; - } - - /** - * {@inheritdoc} - */ - public function setCookieParams( - int $lifetime, - string $path = null, - string $domain = null, - bool $secure = false, - bool $httpOnly = false - ): void { - session_set_cookie_params($lifetime, $path ?? '/', $domain, $secure, $httpOnly); - } - - /** - * {@inheritdoc} - */ - public function getCookieParams(): array + public function getFlash(): FlashInterface { - return session_get_cookie_params(); + return $this->flash; } } diff --git a/src/SessionAwareInterface.php b/src/SessionAwareInterface.php deleted file mode 100644 index 8d60553..0000000 --- a/src/SessionAwareInterface.php +++ /dev/null @@ -1,16 +0,0 @@ -session = $session; - } -} diff --git a/src/SessionInterface.php b/src/SessionInterface.php index ac02791..fc51313 100644 --- a/src/SessionInterface.php +++ b/src/SessionInterface.php @@ -2,110 +2,25 @@ namespace Odan\Session; -use Odan\Session\Exception\SessionException; - /** - * Interface. + * The session data operations. */ interface SessionInterface { - /** - * Starts the session - do not use session_start(). - */ - public function start(): void; - - /** - * Get flash handler. - * - * @return FlashInterface The flash handler - */ - public function getFlash(): FlashInterface; - - /** - * Checks if the session was started. - * - * @return bool Session status - */ - public function isStarted(): bool; - - /** - * Migrates the current session to a new session id while maintaining all session attributes. - * - * Regenerates the session ID - do not use session_regenerate_id(). This method can optionally - * change the lifetime of the new cookie that will be emitted by calling this method. - * - * @throws SessionException On error - */ - public function regenerateId(): void; - - /** - * Clears all session data and regenerates session ID. - * - * Do not use session_destroy(). - * - * Invalidates the current session. - * - * Clears all session attributes and flashes and regenerates the session - * and deletes the old session from persistence. - * - * @throws SessionException On error - */ - public function destroy(): void; - - /** - * Returns the session ID. - * - * @return string The session ID - */ - public function getId(): string; - - /** - * Sets the session ID. - * - * @param string $id The session id - * - * @throws SessionException On error - */ - public function setId(string $id): void; - - /** - * Returns the session name. - * - * @return string The session name - */ - public function getName(): string; - - /** - * Sets the session name. - * - * @param string $name The session name - * - * @throws SessionException Cannot change session name when session is active - */ - public function setName(string $name): void; - - /** - * Returns true if the key exists. - * - * @param string $key The key - * - * @return bool true if the key is defined, false otherwise - */ - public function has(string $key): bool; - /** * Gets an attribute by key. * * @param string $key The key name or null to get all values + * @param mixed $default The default value * - * @return mixed|null Should return null if the key is not found + * @return mixed The value. Returns null if the key is not found */ - public function get(string $key); + public function get(string $key, mixed $default = null): mixed; /** * Gets all values as array. * - * @return array The session values + * @return array The session values */ public function all(): array; @@ -117,85 +32,40 @@ public function all(): array; * * @return void */ - public function set(string $key, $value): void; + public function set(string $key, mixed $value): void; /** * Sets multiple attributes at once: takes a keyed array and sets each key => value pair. * - * @param array $attributes The new attributes - */ - public function replace(array $attributes): void; - - /** - * Deletes an attribute by key. - * - * @param string $key The key to remove - */ - public function remove(string $key): void; - - /** - * Clear all attributes. - */ - public function clear(): void; - - /** - * Returns the number of attributes. - * - * @return int The number of keys + * @param array $values The new values */ - public function count(): int; + public function setValues(array $values): void; /** - * Force the session to be saved and closed. - * - * This method is generally not required for real sessions as the session - * will be automatically saved at the end of code execution. + * Check if an attribute key exists. * - * @throws SessionException On error - */ - public function save(): void; - - /** - * Set session runtime configuration. - * - * @see http://php.net/manual/en/session.configuration.php + * @param string $key The key * - * @param array $config The session options + * @return bool True if the key is set or not */ - public function setOptions(array $config): void; + public function has(string $key): bool; /** - * Get session runtime configuration. + * Deletes an attribute by key. * - * @return array The options + * @param string $key The key to remove */ - public function getOptions(): array; + public function delete(string $key): void; /** - * Set cookie parameters. - * - * @see http://php.net/manual/en/function.session-set-cookie-params.php - * - * @param int $lifetime The lifetime of the cookie in seconds - * @param string|null $path The path where information is stored - * @param string|null $domain The domain of the cookie - * @param bool $secure The cookie should only be sent over secure connections - * @param bool $httpOnly The cookie can only be accessed through the HTTP protocol + * Clear all attributes. */ - public function setCookieParams( - int $lifetime, - string $path = null, - string $domain = null, - bool $secure = false, - bool $httpOnly = false - ): void; + public function clear(): void; /** - * Get cookie parameters. - * - * @see http://php.net/manual/en/function.session-get-cookie-params.php + * Get flash handler. * - * @return array The cookie parameters + * @return FlashInterface The flash handler */ - public function getCookieParams(): array; + public function getFlash(): FlashInterface; } diff --git a/src/SessionManagerInterface.php b/src/SessionManagerInterface.php new file mode 100644 index 0000000..d4d3602 --- /dev/null +++ b/src/SessionManagerInterface.php @@ -0,0 +1,68 @@ +add('key1', 'value1'); $flash->add('key2', 'value2'); $flash->add('key2', 'value3'); @@ -30,12 +24,10 @@ public function testAddAndGet(): void $this->assertSame([], $flash->get('nada')); } - /** - * Test. - */ public function testHas(): void { - $flash = new Flash(new ArrayObject()); + $session = []; + $flash = new Flash($session); $flash->add('key1', 'value1'); $this->assertTrue($flash->has('key1')); @@ -43,12 +35,10 @@ public function testHas(): void $this->assertFalse($flash->has('key2')); } - /** - * Test. - */ public function testAll(): void { - $flash = new Flash(new ArrayObject()); + $session = []; + $flash = new Flash($session); $flash->add('key1', 'value1'); $flash->add('key1', 'value2'); @@ -58,26 +48,23 @@ public function testAll(): void $this->assertSame([], $flash->all()); } - /** - * Test. - */ public function testSet(): void { - $flash = new Flash(new ArrayObject()); + $session = []; + $flash = new Flash($session); $flash->set('key1', ['value1']); $this->assertSame([0 => 'value1'], $flash->get('key1')); - $flash = new Flash(new ArrayObject()); + $session = []; + $flash = new Flash($session); $flash->set('key2', ['value1', 'value2']); $this->assertSame([0 => 'value1', 1 => 'value2'], $flash->get('key2')); } - /** - * Test. - */ public function testClear(): void { - $flash = new Flash(new ArrayObject()); + $session = []; + $flash = new Flash($session); $flash->add('key1', 'value1'); $this->assertTrue($flash->has('key1')); diff --git a/tests/MemorySessionTest.php b/tests/MemorySessionTest.php index e071881..bc43237 100644 --- a/tests/MemorySessionTest.php +++ b/tests/MemorySessionTest.php @@ -5,30 +5,15 @@ use Odan\Session\MemorySession; /** - * MemorySessionTest. + * Memory Session Test. * - * @coversDefaultClass \Odan\Session\MemorySession + * #[\PHPUnit\Framework\Attributes\CoversClass(\Odan\Session\MemorySession)] */ class MemorySessionTest extends PhpSessionTest { - /** {@inheritdoc} */ protected function setUp(): void { $this->session = new MemorySession(); - - $this->session->setOptions([ - 'name' => 'app', - // turn off automatic sending of cache headers entirely - 'cache_limiter' => '', - // garbage collection - 'gc_probability' => 1, - 'gc_divisor' => 1, - 'gc_maxlifetime' => 30 * 24 * 60 * 60, - ]); - - $lifetime = strtotime('20 minutes') - time(); - $this->session->setCookieParams($lifetime, '/', '', false, false); - - $this->session->setName('app'); + $this->manager = $this->session; } } diff --git a/tests/PhpSessionTest.php b/tests/PhpSessionTest.php index 6ebc7c5..8ff0533 100644 --- a/tests/PhpSessionTest.php +++ b/tests/PhpSessionTest.php @@ -4,34 +4,33 @@ use Odan\Session\PhpSession; use Odan\Session\SessionInterface; +use Odan\Session\SessionManagerInterface; use PHPUnit\Framework\TestCase; -use RuntimeException; /** * Test. * - * @coversDefaultClass \Odan\Session\PhpSession + * #[\PHPUnit\Framework\Attributes\CoversClass(\Odan\Session\PhpSession)] */ class PhpSessionTest extends TestCase { - /** - * @var SessionInterface - */ - protected $session; + protected SessionInterface $session; + protected SessionManagerInterface $manager; - /** {@inheritdoc} */ protected function setUp(): void { $_SESSION = []; - $this->session = new PhpSession(); - - $this->session->setOptions( + $this->session = new PhpSession( [ 'name' => 'app', - // turn off automatic sending of cache headers entirely + 'lifetime' => 7200, + 'path' => '/', + 'domain' => '', + 'secure' => false, + 'httponly' => true, 'cache_limiter' => '', - // garbage collection + // init settings 'gc_probability' => 1, 'gc_divisor' => 1, 'gc_maxlifetime' => 30 * 24 * 60 * 60, @@ -39,42 +38,36 @@ protected function setUp(): void ] ); - $lifetime = strtotime('20 minutes') - time(); - $this->session->setCookieParams($lifetime, '/', '', false, false); + $this->manager = $this->session; + } + + protected function tearDown(): void + { + if (isset($this->manager) && $this->manager->isStarted()) { + $this->manager->destroy(); + } - $this->session->setName('app'); + unset($this->session); + unset($this->manager); } - /** - * Test. - * - * @covers ::start - * @covers ::isStarted - * @covers ::getId - * @covers ::setId - * @covers ::destroy - * @covers ::save - * @covers ::regenerateId - */ public function testStart(): void { - $this->session->start(); - $this->assertTrue($this->session->isStarted()); - $this->assertNotEmpty($this->session->getId()); + $this->manager->start(); + $this->assertTrue($this->manager->isStarted()); + $this->assertNotEmpty($this->manager->getId()); - $oldId = $this->session->getId(); - $this->session->regenerateId(); - $newId = $this->session->getId(); + $oldId = $this->manager->getId(); + $this->manager->regenerateId(); + $newId = $this->manager->getId(); $this->assertNotSame($oldId, $newId); - $this->session->destroy(); + $this->manager->destroy(); } - /** - * Test. - */ public function testGetFlash(): void { + $this->manager->start(); $this->session->set('key', 'value1'); $flash = $this->session->getFlash(); @@ -82,91 +75,15 @@ public function testGetFlash(): void $this->assertSame([0 => 'value'], $flash->get('key')); } - /** - * Test. - */ - public function testSetId(): void - { - $this->session->setId('123'); - $oldId = $this->session->getId(); - $this->session->setId('12345'); - $newId = $this->session->getId(); - $this->assertNotSame($oldId, $newId); - } - - /** - * Test. - */ - public function testSetIdWithError(): void - { - $this->expectException(RuntimeException::class); - $this->session->start(); - $this->assertTrue($this->session->isStarted()); - $this->assertNotEmpty($this->session->getId()); - - $oldId = $this->session->getId(); - $this->session->regenerateId(); - $this->session->start(); - $newId = $this->session->getId(); - $this->assertNotSame($oldId, $newId); - - $this->session->setId($oldId); - $this->assertSame($oldId, $this->session->getId()); - } - - /** - * Test. - * - * @covers ::setName - * @covers ::getName - */ public function testSetAndGetName(): void { - $this->session->setName('app'); - $this->session->start(); - $this->assertSame('app', $this->session->getName()); + $this->manager->start(); + $this->assertSame('app', $this->manager->getName()); } - /** - * Test. - * - * session_name(): Cannot change session name when session is active - * - * @covers ::setName - * @covers ::getName - */ - public function testSetAndGetNameError(): void - { - $this->expectException(RuntimeException::class); - $this->session->start(); - $this->session->setName('app'); - } - - /** {@inheritdoc} */ - protected function tearDown(): void - { - $this->session->destroy(); - unset($this->session); - } - - /** - * Test. - */ - public function testInstance(): void - { - $this->assertInstanceOf(SessionInterface::class, $this->session); - } - - /** - * Test. - * - * @covers ::start - * @covers ::set - * @covers ::get - */ public function testSetAndGet(): void { - $this->session->start(); + $this->manager->start(); // string $this->session->set('key', 'value'); @@ -190,14 +107,9 @@ public function testSetAndGet(): void $this->assertFalse($this->session->get('key')); } - /** - * Test. - * - * @covers ::all - */ public function testAll(): void { - $this->session->start(); + $this->manager->start(); // string $this->session->set('key', 'value'); @@ -220,34 +132,15 @@ public function testAll(): void $this->assertSame(['key' => false], $this->session->all()); } - /** - * Test. - * - * @covers ::start - * @covers ::set - * @covers ::get - * @covers ::count - * @covers ::remove - * @covers ::clear - * @covers ::has - * @covers ::replace - */ public function testRemoveAndClear(): void { - $this->session->start(); - $this->assertFalse($this->session->has('key')); + $this->manager->start(); + $this->assertNull($this->session->get('key')); $this->session->set('key', 'value'); $this->assertSame('value', $this->session->get('key')); - $this->assertTrue($this->session->has('key')); - $this->assertFalse($this->session->has('nada')); - - $this->assertSame(1, $this->session->count()); - $this->session->set('key2', 'value'); - $this->assertSame(2, $this->session->count()); - - $this->session->replace( + $this->session->setValues( [ 'key' => 'value-new', 'key2' => 'value2-new', @@ -256,47 +149,26 @@ public function testRemoveAndClear(): void $this->assertSame('value-new', $this->session->get('key')); $this->assertSame('value2-new', $this->session->get('key2')); - $this->session->remove('key'); + $this->session->delete('key'); $this->assertNull($this->session->get('key')); $this->session->clear(); - $this->assertSame(0, $this->session->count()); + $this->assertEmpty($this->session->all()); } - /** - * Test. - * - * @covers ::setOptions - * @covers ::getOptions - */ - public function testConfig(): void + public function testGetId(): void { - $config = [ - 'name' => 'app', - 'cache_limiter' => '', - 'gc_probability' => 1, - 'gc_divisor' => 1, - 'gc_maxlifetime' => 60, - ]; - - $this->session->setOptions($config); - $actual = $this->session->getOptions(); - $this->assertNotEmpty($actual); - $this->assertSame('app', $actual['name']); - } + $session = new PhpSession( + [ + 'id' => 'test123', + 'name' => 'app', + 'save_path' => getenv('GITHUB_ACTIONS') ? '/tmp' : '', + ] + ); + $session->start(); - /** - * Test. - */ - public function testCookieParams(): void - { - $this->session->setCookieParams(60, '/', '', false, false); - $actual = $this->session->getCookieParams(); - $this->assertNotEmpty($actual); - $this->assertSame(60, $actual['lifetime']); - $this->assertSame('/', $actual['path']); - $this->assertSame('', $actual['domain']); - $this->assertFalse($actual['secure']); - $this->assertFalse($actual['httponly']); + $this->assertSame('test123', $session->getId()); + + $session->destroy(); } } diff --git a/tests/Middleware/SessionMiddlewareTest.php b/tests/SessionStartMiddlewareTest.php similarity index 56% rename from tests/Middleware/SessionMiddlewareTest.php rename to tests/SessionStartMiddlewareTest.php index d17e7b3..4f4d67c 100644 --- a/tests/Middleware/SessionMiddlewareTest.php +++ b/tests/SessionStartMiddlewareTest.php @@ -1,37 +1,28 @@ session = new PhpSession(); - $this->session->setOptions([ + $this->session = new PhpSession([ 'name' => 'app', // turn off automatic sending of cache headers entirely 'cache_limiter' => '', @@ -42,17 +33,9 @@ protected function setUp(): void 'save_path' => getenv('GITHUB_ACTIONS') ? '/tmp' : '', ]); - $this->session->setName('app'); - - $this->middleware = new SessionMiddleware($this->session); + $this->middleware = new SessionStartMiddleware($this->session); } - /** - * Test. - * - * @covers ::__construct - * @covers ::process - */ public function testInvoke(): void { // Session must not be started