From 0c5409e94f22b8ce5f323e855190e4eab7a07fe1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jan 2022 04:11:54 +0000 Subject: [PATCH 01/33] Update overtrue/phplint requirement from ^2 to ^2 || ^3 Updates the requirements on [overtrue/phplint](https://github.com/overtrue/phplint) to permit the latest version. - [Release notes](https://github.com/overtrue/phplint/releases) - [Commits](https://github.com/overtrue/phplint/compare/2.0.0...3.0.6) --- updated-dependencies: - dependency-name: overtrue/phplint dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0bbb6ef..1860ff1 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3", "middlewares/utils": "^3", - "overtrue/phplint": "^2", + "overtrue/phplint": "^2 || ^3", "phpstan/phpstan": "^1", "phpunit/phpunit": "^7 || ^8 || ^9", "slim/psr7": "^1", From f33f8cbd5522bec44261340e2911de9b519fb4db Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 15 Sep 2022 12:47:18 +0200 Subject: [PATCH 02/33] Init v6 #28 --- composer.json | 7 +- phpcs.xml | 26 +- phpunit.xml | 29 ++- src/Flash.php | 48 +--- src/MemorySession.php | 237 +++--------------- src/Middleware/SessionMiddleware.php | 25 +- src/PhpSession.php | 226 +++++------------ src/SessionInterface.php | 155 +----------- src/SessionManagerInterface.php | 68 +++++ tests/FlashTest.php | 37 +-- tests/MemorySessionTest.php | 19 +- tests/PhpSessionTest.php | 214 +++------------- .../SessionMiddlewareTest.php | 25 +- 13 files changed, 270 insertions(+), 846 deletions(-) create mode 100644 src/SessionManagerInterface.php rename tests/{Middleware => }/SessionMiddlewareTest.php (71%) diff --git a/composer.json b/composer.json index 0bbb6ef..9935c0a 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "homepage": "https://github.com/odan/session", "license": "MIT", "require": { - "php": "^7.3 || ^8.0", + "php": "^8.0", "psr/http-message": "^1", "psr/http-server-handler": "^1", "psr/http-server-middleware": "^1" @@ -17,22 +17,19 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3", "middlewares/utils": "^3", - "overtrue/phplint": "^2", + "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1", "phpunit/phpunit": "^7 || ^8 || ^9", - "slim/psr7": "^1", "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", "sniffer:check": "phpcs --standard=phpcs.xml", "sniffer:fix": "phpcbf --standard=phpcs.xml", "test": "phpunit --configuration phpunit.xml --do-not-cache-result --colors=always", "test:all": [ - "@lint", "@cs:check", "@sniffer:check", "@stan", diff --git a/phpcs.xml b/phpcs.xml index 55c4e02..f226e19 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -3,6 +3,7 @@ + @@ -10,12 +11,11 @@ ./tests - + + warning warning - \ No newline at end of file + --> + + + + + + + + + + + + + diff --git a/phpunit.xml b/phpunit.xml index ee34618..b613eab 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,17 +1,26 @@ - + + + + src + + + build + vendor + + tests - - - src - - vendor - build - - - + + + + diff --git a/src/Flash.php b/src/Flash.php index 2b79fa7..ca173d4 100644 --- a/src/Flash.php +++ b/src/Flash.php @@ -2,42 +2,23 @@ namespace Odan\Session; -use ArrayObject; +use ArrayAccess; /** * Flash messages. */ final class Flash implements FlashInterface { - /** - * Message storage. - * - * @var ArrayObject - */ - 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 - */ - 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 +30,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 +42,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/MemorySession.php b/src/MemorySession.php index c5e2e61..3eafa4b 100644 --- a/src/MemorySession.php +++ b/src/MemorySession.php @@ -2,90 +2,43 @@ 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 - */ - private $storage; - - /** - * @var Flash - */ - private $flash; - - /** - * @var string - */ - private $id = ''; + private array $options = [ + 'name' => 'app', + 'lifetime' => 7200, + ]; - /** - * @var bool - */ - private $started = false; + private array $storage; - /** - * @var string - */ - private $name = ''; + private Flash $flash; - /** - * @var array - */ - private $config = []; + private string $id = ''; - /** - * @var array - */ - private $cookie = []; + private bool $started = false; - /** - * The constructor. - */ - 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); + $session = []; + $this->storage = &$session; + $this->flash = new Flash($session); } - /** - * Get storage. - * - * @return ArrayObject The storage - */ - public function getStorage(): ArrayObject - { - return $this->storage; - } - - /** - * 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 +48,71 @@ 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; - } - - /** - * {@inheritdoc} - */ - public function setName(string $name): void - { - if ($this->isStarted()) { - throw new RuntimeException('Cannot change session name when session is active'); - } - $this->name = $name; - } - - /** - * {@inheritdoc} - */ - public function has(string $key): bool - { - if (!count($this->storage)) { - return false; - } - - return $this->storage->offsetExists($key); + return $this->options['name']; } - /** - * {@inheritdoc} - */ - public function get(string $key) + public function get(string $key, $default = null) { - if ($this->has($key)) { - return $this->storage->offsetGet($key); - } - - return null; + return $this->storage[$key] ?? $default; } - /** - * {@inheritdoc} - */ public function all(): array { return (array)$this->storage; } - /** - * {@inheritdoc} - */ public function set(string $key, $value): void { $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 delete(string $key): void { - $this->storage->offsetUnset($key); + unset($this->storage[$key]); } - /** - * {@inheritdoc} - */ public function clear(): void { - $this->storage->exchangeArray([]); - } - - /** - * {@inheritdoc} - */ - public function count(): int - { - return $this->storage->count(); - } - - /** - * {@inheritdoc} - */ - public function save(): void - { - } - - /** - * {@inheritdoc} - */ - public function setOptions(array $config): 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/SessionMiddleware.php index 556b44b..7cf2841 100644 --- a/src/Middleware/SessionMiddleware.php +++ b/src/Middleware/SessionMiddleware.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 { - /** - * @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..bbb4783 100644 --- a/src/PhpSession.php +++ b/src/PhpSession.php @@ -2,49 +2,44 @@ 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 - */ - private $storage; - - /** - * @var FlashInterface - */ - private $flash; - - /** - * The constructor. - * - * @param ArrayObject|null $storage The session storage - * @param FlashInterface|null $flash The flash component - */ - public function __construct(ArrayObject $storage = null, FlashInterface $flash = null) - { - $this->storage = $storage ?? new ArrayObject(); - $this->flash = $flash ?? new Flash($this->storage); - } + private array $storage; + + private FlashInterface $flash; + + private array $options = [ + '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', + ]; + + public function __construct(array $options = []) + { + $keys = array_keys($this->options); + foreach ($keys as $key) { + if (array_key_exists($key, $options)) { + $this->options[$key] = $options[$key]; + unset($options[$key]); + } + } - /** - * Get flash instance. - * - * @return FlashInterface The flash instance - */ - public function getFlash(): FlashInterface - { - return $this->flash; + foreach ($options as $key => $value) { + ini_set('session.' . $key, $value); + } } - /** - * {@inheritdoc} - */ public function start(): void { if ($this->isStarted()) { @@ -61,26 +56,33 @@ 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']); + // 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,9 +98,6 @@ public function regenerateId(): void } } - /** - * {@inheritdoc} - */ public function destroy(): void { // Cannot regenerate the session ID for non-active sessions. @@ -130,166 +129,59 @@ 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, $default = null) { - 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 { - $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 delete(string $key): void { - $this->storage->offsetUnset($key); + unset($this->storage[$key]); } - /** - * {@inheritdoc} - */ public function clear(): void { - $this->storage->exchangeArray([]); - } - - /** - * {@inheritdoc} - */ - public function count(): int - { - 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 = (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/SessionInterface.php b/src/SessionInterface.php index ac02791..2f63579 100644 --- a/src/SessionInterface.php +++ b/src/SessionInterface.php @@ -2,105 +2,20 @@ namespace Odan\Session; -use Odan\Session\Exception\SessionException; - /** * Interface. */ 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 null $default The default value * * @return mixed|null Should return null if the key is not found */ - public function get(string $key); + public function get(string $key, $default = null); /** * Gets all values as array. @@ -122,16 +37,16 @@ public function set(string $key, $value): void; /** * Sets multiple attributes at once: takes a keyed array and sets each key => value pair. * - * @param array $attributes The new attributes + * @param array $values The new values */ - public function replace(array $attributes): void; + public function setValues(array $values): void; /** * Deletes an attribute by key. * * @param string $key The key to remove */ - public function remove(string $key): void; + public function delete(string $key): void; /** * Clear all attributes. @@ -139,63 +54,9 @@ public function remove(string $key): void; public function clear(): void; /** - * Returns the number of attributes. - * - * @return int The number of keys - */ - public function count(): int; - - /** - * 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. - * - * @throws SessionException On error - */ - public function save(): void; - - /** - * Set session runtime configuration. - * - * @see http://php.net/manual/en/session.configuration.php - * - * @param array $config The session options - */ - public function setOptions(array $config): void; - - /** - * Get session runtime configuration. - * - * @return array The options - */ - public function getOptions(): array; - - /** - * 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 - */ - public function setCookieParams( - int $lifetime, - string $path = null, - string $domain = null, - bool $secure = false, - bool $httpOnly = false - ): 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..d727281 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 */ 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..b69682a 100644 --- a/tests/PhpSessionTest.php +++ b/tests/PhpSessionTest.php @@ -4,8 +4,8 @@ use Odan\Session\PhpSession; use Odan\Session\SessionInterface; +use Odan\Session\SessionManagerInterface; use PHPUnit\Framework\TestCase; -use RuntimeException; /** * Test. @@ -14,24 +14,23 @@ */ 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,32 @@ protected function setUp(): void ] ); - $lifetime = strtotime('20 minutes') - time(); - $this->session->setCookieParams($lifetime, '/', '', false, false); + $this->manager = $this->session; + } - $this->session->setName('app'); + protected function tearDown(): void + { + $this->manager->destroy(); + unset($this->session); } - /** - * 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 +71,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()); - } - - /** - * 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'); + $this->manager->start(); + $this->assertSame('app', $this->manager->getName()); } - /** {@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 +103,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 +128,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 +145,10 @@ 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()); - } - - /** - * Test. - * - * @covers ::setOptions - * @covers ::getOptions - */ - public function testConfig(): 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']); - } - - /** - * 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->assertEmpty($this->session->all()); } } diff --git a/tests/Middleware/SessionMiddlewareTest.php b/tests/SessionMiddlewareTest.php similarity index 71% rename from tests/Middleware/SessionMiddlewareTest.php rename to tests/SessionMiddlewareTest.php index d17e7b3..88cc4b2 100644 --- a/tests/Middleware/SessionMiddlewareTest.php +++ b/tests/SessionMiddlewareTest.php @@ -1,11 +1,10 @@ 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); } - /** - * Test. - * - * @covers ::__construct - * @covers ::process - */ public function testInvoke(): void { // Session must not be started From ce3f224c230f2b6be463eccedceb3b2ec72fe1f3 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 15 Sep 2022 12:53:07 +0200 Subject: [PATCH 03/33] Require php 8 --- .github/workflows/build.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ef65bbd..6abf64a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['7.3', '7.4', '8.0'] + php-versions: ['8.0', '8.1'] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: @@ -34,12 +34,7 @@ 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' + - name: Install dependencies run: composer update --prefer-dist --no-progress --ignore-platform-req=php - name: Run test suite From 03fe5367fd14212cad2276197d9624e0caa8ab31 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 15 Sep 2022 12:54:50 +0200 Subject: [PATCH 04/33] Require php 8 --- composer.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9935c0a..580cbaf 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,11 @@ "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1", "phpunit/phpunit": "^7 || ^8 || ^9", - "squizlabs/php_codesniffer": "^3" + "squizlabs/php_codesniffer": "^3", + "symfony/console": "6.0.*", + "symfony/event-dispatcher": "6.0.*", + "symfony/filesystem": "6.0.*", + "symfony/finder": "6.0.*" }, "scripts": { "cs:check": "php-cs-fixer fix --dry-run --format=txt --verbose --diff --config=.cs.php", From e43e596f2b585531969f51e18a0f1bba10fbab0a Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 18 Sep 2022 19:20:38 +0200 Subject: [PATCH 05/33] Add session id option #28 --- src/PhpSession.php | 13 +++++++++++-- tests/PhpSessionTest.php | 22 +++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/PhpSession.php b/src/PhpSession.php index bbb4783..8bfdba6 100644 --- a/src/PhpSession.php +++ b/src/PhpSession.php @@ -14,6 +14,7 @@ final class PhpSession implements SessionInterface, SessionManagerInterface private FlashInterface $flash; private array $options = [ + 'id' => null, 'name' => 'app', 'lifetime' => 7200, 'path' => null, @@ -27,6 +28,11 @@ final class PhpSession implements SessionInterface, SessionManagerInterface public function __construct(array $options = []) { + // 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)) { @@ -68,6 +74,11 @@ public function start(): void 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.'); @@ -100,7 +111,6 @@ public function regenerateId(): void public function destroy(): void { - // Cannot regenerate the session ID for non-active sessions. if (!$this->isStarted()) { return; } @@ -176,7 +186,6 @@ public function clear(): void public function save(): void { - // $_SESSION = (array)$this->storage; session_write_close(); } diff --git a/tests/PhpSessionTest.php b/tests/PhpSessionTest.php index b69682a..8df0a20 100644 --- a/tests/PhpSessionTest.php +++ b/tests/PhpSessionTest.php @@ -43,8 +43,12 @@ protected function setUp(): void protected function tearDown(): void { - $this->manager->destroy(); + if (isset($this->manager) && $this->manager->isStarted()) { + $this->manager->destroy(); + } + unset($this->session); + unset($this->manager); } public function testStart(): void @@ -151,4 +155,20 @@ public function testRemoveAndClear(): void $this->session->clear(); $this->assertEmpty($this->session->all()); } + + public function testGetId(): void + { + $session = new PhpSession( + [ + 'id' => 'test123', + 'name' => 'app', + 'save_path' => getenv('GITHUB_ACTIONS') ? '/tmp' : '', + ] + ); + $session->start(); + + $this->assertSame('test123', $session->getId()); + + $session->destroy(); + } } From 949695a74e55ed6f1b3c86d4699d58ea6d1eaac9 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 1 Dec 2022 16:35:10 +0100 Subject: [PATCH 06/33] Update dependencies --- .cs.php | 7 +++++-- .github/workflows/build.yml | 11 ++++------- .scrutinizer.yml | 20 +++++++++++--------- LICENSE | 2 +- composer.json | 8 ++++---- phpcs.xml | 26 +------------------------- 6 files changed, 26 insertions(+), 48 deletions(-) diff --git a/.cs.php b/.cs.php index e4ab7fc..9653bee 100644 --- a/.cs.php +++ b/.cs.php @@ -1,6 +1,8 @@ setUsingCache(false) ->setRiskyAllowed(true) ->setRules( @@ -18,7 +20,6 @@ 'cast_spaces' => ['space' => 'none'], 'concat_space' => ['spacing' => 'one'], 'compact_nullable_typehint' => true, - 'declare_equal_normalize' => ['space' => 'single'], 'increment_style' => ['style' => 'post'], 'list_syntax' => ['syntax' => 'short'], 'echo_tag_syntax' => ['format' => 'long'], @@ -35,6 +36,8 @@ 'imports_order' => ['class', 'const', 'function'] ], 'single_line_throw' => false, + 'fully_qualified_strict_types' => true, + 'global_namespace_import' => false, ] ) ->setFinder( diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6abf64a..8ca1e0f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,14 +1,14 @@ name: build -on: [push, pull_request] +on: [ push, pull_request ] jobs: run: runs-on: ${{ matrix.operating-system }} strategy: matrix: - operating-system: [ubuntu-latest] - php-versions: ['8.0', '8.1'] + operating-system: [ ubuntu-latest ] + php-versions: [ '8.0', '8.1' ] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: @@ -19,7 +19,6 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - extensions: mbstring, intl, zip coverage: none - name: Check PHP Version @@ -35,9 +34,7 @@ jobs: run: composer validate - name: Install dependencies - run: composer update --prefer-dist --no-progress --ignore-platform-req=php + 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/.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/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/composer.json b/composer.json index 580cbaf..ef44ab9 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "middlewares/utils": "^3", "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1", - "phpunit/phpunit": "^7 || ^8 || ^9", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10", "squizlabs/php_codesniffer": "^3", "symfony/console": "6.0.*", "symfony/event-dispatcher": "6.0.*", @@ -27,11 +27,11 @@ "symfony/finder": "6.0.*" }, "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", - "stan": "phpstan analyse -c phpstan.neon --no-progress --ansi", + "cs:check": "php-cs-fixer fix --dry-run --format=txt --verbose --diff --config=.cs.php --ansi", + "cs:fix": "php-cs-fixer fix --config=.cs.php --ansi", "sniffer:check": "phpcs --standard=phpcs.xml", "sniffer:fix": "phpcbf --standard=phpcs.xml", + "stan": "phpstan analyse -c phpstan.neon --no-progress --ansi --xdebug", "test": "phpunit --configuration phpunit.xml --do-not-cache-result --colors=always", "test:all": [ "@cs:check", diff --git a/phpcs.xml b/phpcs.xml index f226e19..82204fd 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -11,31 +11,7 @@ ./tests - - - + From c2fb281e1c29b5bb8bb67307a0ebcff107d83f39 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 1 Dec 2022 17:29:49 +0100 Subject: [PATCH 07/33] Rename SessionMiddleware to SessionStartMiddleware --- ...essionMiddleware.php => SessionStartMiddleware.php} | 2 +- ...ddlewareTest.php => SessionStartMiddlewareTest.php} | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename src/Middleware/{SessionMiddleware.php => SessionStartMiddleware.php} (91%) rename tests/{SessionMiddlewareTest.php => SessionStartMiddlewareTest.php} (77%) diff --git a/src/Middleware/SessionMiddleware.php b/src/Middleware/SessionStartMiddleware.php similarity index 91% rename from src/Middleware/SessionMiddleware.php rename to src/Middleware/SessionStartMiddleware.php index 7cf2841..97090f9 100644 --- a/src/Middleware/SessionMiddleware.php +++ b/src/Middleware/SessionStartMiddleware.php @@ -8,7 +8,7 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -final class SessionMiddleware implements MiddlewareInterface +final class SessionStartMiddleware implements MiddlewareInterface { private SessionManagerInterface $session; diff --git a/tests/SessionMiddlewareTest.php b/tests/SessionStartMiddlewareTest.php similarity index 77% rename from tests/SessionMiddlewareTest.php rename to tests/SessionStartMiddlewareTest.php index 88cc4b2..f62abd4 100644 --- a/tests/SessionMiddlewareTest.php +++ b/tests/SessionStartMiddlewareTest.php @@ -3,20 +3,20 @@ namespace Odan\Session\Test; use Middlewares\Utils\Dispatcher; -use Odan\Session\Middleware\SessionMiddleware; +use Odan\Session\Middleware\SessionStartMiddleware; use Odan\Session\PhpSession; use PHPUnit\Framework\TestCase; /** * Test. * - * @coversDefaultClass \Odan\Session\Middleware\SessionMiddleware + * @coversDefaultClass \Odan\Session\Middleware\SessionStartMiddleware */ -class SessionMiddlewareTest extends TestCase +class SessionStartMiddlewareTest extends TestCase { private PhpSession $session; - private SessionMiddleware $middleware; + private SessionStartMiddleware $middleware; protected function setUp(): void { @@ -33,7 +33,7 @@ protected function setUp(): void 'save_path' => getenv('GITHUB_ACTIONS') ? '/tmp' : '', ]); - $this->middleware = new SessionMiddleware($this->session); + $this->middleware = new SessionStartMiddleware($this->session); } public function testInvoke(): void From c2e01c426fa38b82dcc8ae888bb57568ea4eb486 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 1 Dec 2022 17:30:11 +0100 Subject: [PATCH 08/33] Add docs for v6 --- docs/index.md | 4 +- docs/v5/index.md | 8 +- docs/v6/index.md | 299 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 305 insertions(+), 6 deletions(-) create mode 100644 docs/v6/index.md 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..fd20e69 --- /dev/null +++ b/docs/v6/index.md @@ -0,0 +1,299 @@ +--- +layout: default +title: Version 6 +nav_order: 3 +description: "Version 6" +--- + +# Session v5 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.0+ + +## 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(); + +// Clears all session +$session->destroy(); + +// 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 + +Add the Flash instance as global twig variable within the `Twig::class` container definition: + +```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: The cookie is not accessible for 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, + '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); +``` From 9786f7d4caf1ead78321571d49baa93af036ff83 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 1 Dec 2022 17:31:38 +0100 Subject: [PATCH 09/33] Add docs for v6 --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 8e2e86c..8f2daff 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,4 @@ A middleware (PSR-15) oriented session and flash message handler for PHP. -* Blog post: -* Documentation for v4: -* Documentation for v5: -* Issues: +* Documentation for v6: From 33698294ea96e6102f9eccb69f9d807b9de2f730 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 15 Sep 2022 12:47:18 +0200 Subject: [PATCH 10/33] Init v6 #28 --- composer.json | 7 +- phpcs.xml | 26 +- phpunit.xml | 29 ++- src/Flash.php | 48 +--- src/MemorySession.php | 237 +++--------------- src/Middleware/SessionMiddleware.php | 25 +- src/PhpSession.php | 226 +++++------------ src/SessionInterface.php | 155 +----------- src/SessionManagerInterface.php | 68 +++++ tests/FlashTest.php | 37 +-- tests/MemorySessionTest.php | 19 +- tests/PhpSessionTest.php | 214 +++------------- .../SessionMiddlewareTest.php | 25 +- 13 files changed, 270 insertions(+), 846 deletions(-) create mode 100644 src/SessionManagerInterface.php rename tests/{Middleware => }/SessionMiddlewareTest.php (71%) diff --git a/composer.json b/composer.json index 1860ff1..9935c0a 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "homepage": "https://github.com/odan/session", "license": "MIT", "require": { - "php": "^7.3 || ^8.0", + "php": "^8.0", "psr/http-message": "^1", "psr/http-server-handler": "^1", "psr/http-server-middleware": "^1" @@ -17,22 +17,19 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3", "middlewares/utils": "^3", - "overtrue/phplint": "^2 || ^3", + "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1", "phpunit/phpunit": "^7 || ^8 || ^9", - "slim/psr7": "^1", "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", "sniffer:check": "phpcs --standard=phpcs.xml", "sniffer:fix": "phpcbf --standard=phpcs.xml", "test": "phpunit --configuration phpunit.xml --do-not-cache-result --colors=always", "test:all": [ - "@lint", "@cs:check", "@sniffer:check", "@stan", diff --git a/phpcs.xml b/phpcs.xml index 55c4e02..f226e19 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -3,6 +3,7 @@ + @@ -10,12 +11,11 @@ ./tests - + + warning warning - \ No newline at end of file + --> + + + + + + + + + + + + + diff --git a/phpunit.xml b/phpunit.xml index ee34618..b613eab 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,17 +1,26 @@ - + + + + src + + + build + vendor + + tests - - - src - - vendor - build - - - + + + + diff --git a/src/Flash.php b/src/Flash.php index 2b79fa7..ca173d4 100644 --- a/src/Flash.php +++ b/src/Flash.php @@ -2,42 +2,23 @@ namespace Odan\Session; -use ArrayObject; +use ArrayAccess; /** * Flash messages. */ final class Flash implements FlashInterface { - /** - * Message storage. - * - * @var ArrayObject - */ - 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 - */ - 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 +30,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 +42,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/MemorySession.php b/src/MemorySession.php index c5e2e61..3eafa4b 100644 --- a/src/MemorySession.php +++ b/src/MemorySession.php @@ -2,90 +2,43 @@ 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 - */ - private $storage; - - /** - * @var Flash - */ - private $flash; - - /** - * @var string - */ - private $id = ''; + private array $options = [ + 'name' => 'app', + 'lifetime' => 7200, + ]; - /** - * @var bool - */ - private $started = false; + private array $storage; - /** - * @var string - */ - private $name = ''; + private Flash $flash; - /** - * @var array - */ - private $config = []; + private string $id = ''; - /** - * @var array - */ - private $cookie = []; + private bool $started = false; - /** - * The constructor. - */ - 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); + $session = []; + $this->storage = &$session; + $this->flash = new Flash($session); } - /** - * Get storage. - * - * @return ArrayObject The storage - */ - public function getStorage(): ArrayObject - { - return $this->storage; - } - - /** - * 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 +48,71 @@ 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; - } - - /** - * {@inheritdoc} - */ - public function setName(string $name): void - { - if ($this->isStarted()) { - throw new RuntimeException('Cannot change session name when session is active'); - } - $this->name = $name; - } - - /** - * {@inheritdoc} - */ - public function has(string $key): bool - { - if (!count($this->storage)) { - return false; - } - - return $this->storage->offsetExists($key); + return $this->options['name']; } - /** - * {@inheritdoc} - */ - public function get(string $key) + public function get(string $key, $default = null) { - if ($this->has($key)) { - return $this->storage->offsetGet($key); - } - - return null; + return $this->storage[$key] ?? $default; } - /** - * {@inheritdoc} - */ public function all(): array { return (array)$this->storage; } - /** - * {@inheritdoc} - */ public function set(string $key, $value): void { $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 delete(string $key): void { - $this->storage->offsetUnset($key); + unset($this->storage[$key]); } - /** - * {@inheritdoc} - */ public function clear(): void { - $this->storage->exchangeArray([]); - } - - /** - * {@inheritdoc} - */ - public function count(): int - { - return $this->storage->count(); - } - - /** - * {@inheritdoc} - */ - public function save(): void - { - } - - /** - * {@inheritdoc} - */ - public function setOptions(array $config): 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/SessionMiddleware.php index 556b44b..7cf2841 100644 --- a/src/Middleware/SessionMiddleware.php +++ b/src/Middleware/SessionMiddleware.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 { - /** - * @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..bbb4783 100644 --- a/src/PhpSession.php +++ b/src/PhpSession.php @@ -2,49 +2,44 @@ 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 - */ - private $storage; - - /** - * @var FlashInterface - */ - private $flash; - - /** - * The constructor. - * - * @param ArrayObject|null $storage The session storage - * @param FlashInterface|null $flash The flash component - */ - public function __construct(ArrayObject $storage = null, FlashInterface $flash = null) - { - $this->storage = $storage ?? new ArrayObject(); - $this->flash = $flash ?? new Flash($this->storage); - } + private array $storage; + + private FlashInterface $flash; + + private array $options = [ + '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', + ]; + + public function __construct(array $options = []) + { + $keys = array_keys($this->options); + foreach ($keys as $key) { + if (array_key_exists($key, $options)) { + $this->options[$key] = $options[$key]; + unset($options[$key]); + } + } - /** - * Get flash instance. - * - * @return FlashInterface The flash instance - */ - public function getFlash(): FlashInterface - { - return $this->flash; + foreach ($options as $key => $value) { + ini_set('session.' . $key, $value); + } } - /** - * {@inheritdoc} - */ public function start(): void { if ($this->isStarted()) { @@ -61,26 +56,33 @@ 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']); + // 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,9 +98,6 @@ public function regenerateId(): void } } - /** - * {@inheritdoc} - */ public function destroy(): void { // Cannot regenerate the session ID for non-active sessions. @@ -130,166 +129,59 @@ 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, $default = null) { - 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 { - $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 delete(string $key): void { - $this->storage->offsetUnset($key); + unset($this->storage[$key]); } - /** - * {@inheritdoc} - */ public function clear(): void { - $this->storage->exchangeArray([]); - } - - /** - * {@inheritdoc} - */ - public function count(): int - { - 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 = (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/SessionInterface.php b/src/SessionInterface.php index ac02791..2f63579 100644 --- a/src/SessionInterface.php +++ b/src/SessionInterface.php @@ -2,105 +2,20 @@ namespace Odan\Session; -use Odan\Session\Exception\SessionException; - /** * Interface. */ 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 null $default The default value * * @return mixed|null Should return null if the key is not found */ - public function get(string $key); + public function get(string $key, $default = null); /** * Gets all values as array. @@ -122,16 +37,16 @@ public function set(string $key, $value): void; /** * Sets multiple attributes at once: takes a keyed array and sets each key => value pair. * - * @param array $attributes The new attributes + * @param array $values The new values */ - public function replace(array $attributes): void; + public function setValues(array $values): void; /** * Deletes an attribute by key. * * @param string $key The key to remove */ - public function remove(string $key): void; + public function delete(string $key): void; /** * Clear all attributes. @@ -139,63 +54,9 @@ public function remove(string $key): void; public function clear(): void; /** - * Returns the number of attributes. - * - * @return int The number of keys - */ - public function count(): int; - - /** - * 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. - * - * @throws SessionException On error - */ - public function save(): void; - - /** - * Set session runtime configuration. - * - * @see http://php.net/manual/en/session.configuration.php - * - * @param array $config The session options - */ - public function setOptions(array $config): void; - - /** - * Get session runtime configuration. - * - * @return array The options - */ - public function getOptions(): array; - - /** - * 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 - */ - public function setCookieParams( - int $lifetime, - string $path = null, - string $domain = null, - bool $secure = false, - bool $httpOnly = false - ): 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..d727281 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 */ 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..b69682a 100644 --- a/tests/PhpSessionTest.php +++ b/tests/PhpSessionTest.php @@ -4,8 +4,8 @@ use Odan\Session\PhpSession; use Odan\Session\SessionInterface; +use Odan\Session\SessionManagerInterface; use PHPUnit\Framework\TestCase; -use RuntimeException; /** * Test. @@ -14,24 +14,23 @@ */ 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,32 @@ protected function setUp(): void ] ); - $lifetime = strtotime('20 minutes') - time(); - $this->session->setCookieParams($lifetime, '/', '', false, false); + $this->manager = $this->session; + } - $this->session->setName('app'); + protected function tearDown(): void + { + $this->manager->destroy(); + unset($this->session); } - /** - * 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 +71,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()); - } - - /** - * 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'); + $this->manager->start(); + $this->assertSame('app', $this->manager->getName()); } - /** {@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 +103,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 +128,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 +145,10 @@ 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()); - } - - /** - * Test. - * - * @covers ::setOptions - * @covers ::getOptions - */ - public function testConfig(): 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']); - } - - /** - * 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->assertEmpty($this->session->all()); } } diff --git a/tests/Middleware/SessionMiddlewareTest.php b/tests/SessionMiddlewareTest.php similarity index 71% rename from tests/Middleware/SessionMiddlewareTest.php rename to tests/SessionMiddlewareTest.php index d17e7b3..88cc4b2 100644 --- a/tests/Middleware/SessionMiddlewareTest.php +++ b/tests/SessionMiddlewareTest.php @@ -1,11 +1,10 @@ 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); } - /** - * Test. - * - * @covers ::__construct - * @covers ::process - */ public function testInvoke(): void { // Session must not be started From 48dd3d0c3acb12478a5d1bf27bf1a98b990cf30a Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 15 Sep 2022 12:53:07 +0200 Subject: [PATCH 11/33] Require php 8 --- .github/workflows/build.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ef65bbd..6abf64a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['7.3', '7.4', '8.0'] + php-versions: ['8.0', '8.1'] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: @@ -34,12 +34,7 @@ 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' + - name: Install dependencies run: composer update --prefer-dist --no-progress --ignore-platform-req=php - name: Run test suite From fb4d9577dc021191a4e2f6dd3abb3fd67ac30237 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 15 Sep 2022 12:54:50 +0200 Subject: [PATCH 12/33] Require php 8 --- composer.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9935c0a..580cbaf 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,11 @@ "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1", "phpunit/phpunit": "^7 || ^8 || ^9", - "squizlabs/php_codesniffer": "^3" + "squizlabs/php_codesniffer": "^3", + "symfony/console": "6.0.*", + "symfony/event-dispatcher": "6.0.*", + "symfony/filesystem": "6.0.*", + "symfony/finder": "6.0.*" }, "scripts": { "cs:check": "php-cs-fixer fix --dry-run --format=txt --verbose --diff --config=.cs.php", From 1511406befcead88f91ad2aac2c8e66a254a3b36 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 18 Sep 2022 19:20:38 +0200 Subject: [PATCH 13/33] Add session id option #28 --- src/PhpSession.php | 13 +++++++++++-- tests/PhpSessionTest.php | 22 +++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/PhpSession.php b/src/PhpSession.php index bbb4783..8bfdba6 100644 --- a/src/PhpSession.php +++ b/src/PhpSession.php @@ -14,6 +14,7 @@ final class PhpSession implements SessionInterface, SessionManagerInterface private FlashInterface $flash; private array $options = [ + 'id' => null, 'name' => 'app', 'lifetime' => 7200, 'path' => null, @@ -27,6 +28,11 @@ final class PhpSession implements SessionInterface, SessionManagerInterface public function __construct(array $options = []) { + // 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)) { @@ -68,6 +74,11 @@ public function start(): void 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.'); @@ -100,7 +111,6 @@ public function regenerateId(): void public function destroy(): void { - // Cannot regenerate the session ID for non-active sessions. if (!$this->isStarted()) { return; } @@ -176,7 +186,6 @@ public function clear(): void public function save(): void { - // $_SESSION = (array)$this->storage; session_write_close(); } diff --git a/tests/PhpSessionTest.php b/tests/PhpSessionTest.php index b69682a..8df0a20 100644 --- a/tests/PhpSessionTest.php +++ b/tests/PhpSessionTest.php @@ -43,8 +43,12 @@ protected function setUp(): void protected function tearDown(): void { - $this->manager->destroy(); + if (isset($this->manager) && $this->manager->isStarted()) { + $this->manager->destroy(); + } + unset($this->session); + unset($this->manager); } public function testStart(): void @@ -151,4 +155,20 @@ public function testRemoveAndClear(): void $this->session->clear(); $this->assertEmpty($this->session->all()); } + + public function testGetId(): void + { + $session = new PhpSession( + [ + 'id' => 'test123', + 'name' => 'app', + 'save_path' => getenv('GITHUB_ACTIONS') ? '/tmp' : '', + ] + ); + $session->start(); + + $this->assertSame('test123', $session->getId()); + + $session->destroy(); + } } From 915c5dbf67fd68c5edefb65af9957831873e0459 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 1 Dec 2022 16:35:10 +0100 Subject: [PATCH 14/33] Update dependencies --- .cs.php | 7 +++++-- .github/workflows/build.yml | 11 ++++------- .scrutinizer.yml | 20 +++++++++++--------- LICENSE | 2 +- composer.json | 8 ++++---- phpcs.xml | 26 +------------------------- 6 files changed, 26 insertions(+), 48 deletions(-) diff --git a/.cs.php b/.cs.php index e4ab7fc..9653bee 100644 --- a/.cs.php +++ b/.cs.php @@ -1,6 +1,8 @@ setUsingCache(false) ->setRiskyAllowed(true) ->setRules( @@ -18,7 +20,6 @@ 'cast_spaces' => ['space' => 'none'], 'concat_space' => ['spacing' => 'one'], 'compact_nullable_typehint' => true, - 'declare_equal_normalize' => ['space' => 'single'], 'increment_style' => ['style' => 'post'], 'list_syntax' => ['syntax' => 'short'], 'echo_tag_syntax' => ['format' => 'long'], @@ -35,6 +36,8 @@ 'imports_order' => ['class', 'const', 'function'] ], 'single_line_throw' => false, + 'fully_qualified_strict_types' => true, + 'global_namespace_import' => false, ] ) ->setFinder( diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6abf64a..8ca1e0f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,14 +1,14 @@ name: build -on: [push, pull_request] +on: [ push, pull_request ] jobs: run: runs-on: ${{ matrix.operating-system }} strategy: matrix: - operating-system: [ubuntu-latest] - php-versions: ['8.0', '8.1'] + operating-system: [ ubuntu-latest ] + php-versions: [ '8.0', '8.1' ] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: @@ -19,7 +19,6 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - extensions: mbstring, intl, zip coverage: none - name: Check PHP Version @@ -35,9 +34,7 @@ jobs: run: composer validate - name: Install dependencies - run: composer update --prefer-dist --no-progress --ignore-platform-req=php + 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/.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/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/composer.json b/composer.json index 580cbaf..ef44ab9 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "middlewares/utils": "^3", "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1", - "phpunit/phpunit": "^7 || ^8 || ^9", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10", "squizlabs/php_codesniffer": "^3", "symfony/console": "6.0.*", "symfony/event-dispatcher": "6.0.*", @@ -27,11 +27,11 @@ "symfony/finder": "6.0.*" }, "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", - "stan": "phpstan analyse -c phpstan.neon --no-progress --ansi", + "cs:check": "php-cs-fixer fix --dry-run --format=txt --verbose --diff --config=.cs.php --ansi", + "cs:fix": "php-cs-fixer fix --config=.cs.php --ansi", "sniffer:check": "phpcs --standard=phpcs.xml", "sniffer:fix": "phpcbf --standard=phpcs.xml", + "stan": "phpstan analyse -c phpstan.neon --no-progress --ansi --xdebug", "test": "phpunit --configuration phpunit.xml --do-not-cache-result --colors=always", "test:all": [ "@cs:check", diff --git a/phpcs.xml b/phpcs.xml index f226e19..82204fd 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -11,31 +11,7 @@ ./tests - - - + From 2ff43c84b60ae300217f29014ad624bbd5792fcb Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 1 Dec 2022 17:29:49 +0100 Subject: [PATCH 15/33] Rename SessionMiddleware to SessionStartMiddleware --- ...essionMiddleware.php => SessionStartMiddleware.php} | 2 +- ...ddlewareTest.php => SessionStartMiddlewareTest.php} | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename src/Middleware/{SessionMiddleware.php => SessionStartMiddleware.php} (91%) rename tests/{SessionMiddlewareTest.php => SessionStartMiddlewareTest.php} (77%) diff --git a/src/Middleware/SessionMiddleware.php b/src/Middleware/SessionStartMiddleware.php similarity index 91% rename from src/Middleware/SessionMiddleware.php rename to src/Middleware/SessionStartMiddleware.php index 7cf2841..97090f9 100644 --- a/src/Middleware/SessionMiddleware.php +++ b/src/Middleware/SessionStartMiddleware.php @@ -8,7 +8,7 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -final class SessionMiddleware implements MiddlewareInterface +final class SessionStartMiddleware implements MiddlewareInterface { private SessionManagerInterface $session; diff --git a/tests/SessionMiddlewareTest.php b/tests/SessionStartMiddlewareTest.php similarity index 77% rename from tests/SessionMiddlewareTest.php rename to tests/SessionStartMiddlewareTest.php index 88cc4b2..f62abd4 100644 --- a/tests/SessionMiddlewareTest.php +++ b/tests/SessionStartMiddlewareTest.php @@ -3,20 +3,20 @@ namespace Odan\Session\Test; use Middlewares\Utils\Dispatcher; -use Odan\Session\Middleware\SessionMiddleware; +use Odan\Session\Middleware\SessionStartMiddleware; use Odan\Session\PhpSession; use PHPUnit\Framework\TestCase; /** * Test. * - * @coversDefaultClass \Odan\Session\Middleware\SessionMiddleware + * @coversDefaultClass \Odan\Session\Middleware\SessionStartMiddleware */ -class SessionMiddlewareTest extends TestCase +class SessionStartMiddlewareTest extends TestCase { private PhpSession $session; - private SessionMiddleware $middleware; + private SessionStartMiddleware $middleware; protected function setUp(): void { @@ -33,7 +33,7 @@ protected function setUp(): void 'save_path' => getenv('GITHUB_ACTIONS') ? '/tmp' : '', ]); - $this->middleware = new SessionMiddleware($this->session); + $this->middleware = new SessionStartMiddleware($this->session); } public function testInvoke(): void From a5cf5ace5138a2ad9b1c5b8b90b8f4bfa9e13bba Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 1 Dec 2022 17:30:11 +0100 Subject: [PATCH 16/33] Add docs for v6 --- docs/index.md | 4 +- docs/v5/index.md | 8 +- docs/v6/index.md | 299 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 305 insertions(+), 6 deletions(-) create mode 100644 docs/v6/index.md 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..fd20e69 --- /dev/null +++ b/docs/v6/index.md @@ -0,0 +1,299 @@ +--- +layout: default +title: Version 6 +nav_order: 3 +description: "Version 6" +--- + +# Session v5 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.0+ + +## 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(); + +// Clears all session +$session->destroy(); + +// 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 + +Add the Flash instance as global twig variable within the `Twig::class` container definition: + +```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: The cookie is not accessible for 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, + '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); +``` From b8a713c2749387dd485b80b5c00f79ebdb66b537 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 1 Dec 2022 17:31:38 +0100 Subject: [PATCH 17/33] Add docs for v6 --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 8e2e86c..8f2daff 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,4 @@ A middleware (PSR-15) oriented session and flash message handler for PHP. -* Blog post: -* Documentation for v4: -* Documentation for v5: -* Issues: +* Documentation for v6: From 9828ffefc169f0348e1bf18dedc01a307cb8eba9 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Thu, 1 Dec 2022 17:41:36 +0100 Subject: [PATCH 18/33] Update docs --- docs/index.md | 4 +--- docs/v6/index.md | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/index.md b/docs/index.md index 7d1b2f0..9e201fc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,6 +18,4 @@ composer require odan/session ## Support * Issues: -* Source: -* Documentation for v5: -* Documentation for v6: \ No newline at end of file +* Source: \ No newline at end of file diff --git a/docs/v6/index.md b/docs/v6/index.md index fd20e69..a6d05d9 100644 --- a/docs/v6/index.md +++ b/docs/v6/index.md @@ -5,7 +5,7 @@ nav_order: 3 description: "Version 6" --- -# Session v5 Documentation +# Session v6 Documentation ## Table of contents From 436fde5bcf34a22c68b633d4438e6ac5c812b978 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 4 Dec 2022 11:56:34 +0100 Subject: [PATCH 19/33] Remove SessionAwareInterface and SessionAwareTrait --- src/SessionAwareInterface.php | 16 ---------------- src/SessionAwareTrait.php | 28 ---------------------------- 2 files changed, 44 deletions(-) delete mode 100644 src/SessionAwareInterface.php delete mode 100644 src/SessionAwareTrait.php 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; - } -} From 8dc8ab932de7d7f86e9a22eb5041d56339778222 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 4 Dec 2022 12:00:38 +0100 Subject: [PATCH 20/33] Add changelog for v6 --- CHANGELOG.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c67ac6e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,54 @@ +# 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). + +## [Unreleased] + +v6.x + +### 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. + +## [1.1.0] - 2019-02-15 + +### Added + +- Danish translation from [@frederikspang](https://github.com/frederikspang). +- Georgian translation from [@tatocaster](https://github.com/tatocaster). +- Changelog inconsistency section in Bad Practices + +### Changed + +- Fixed typos in Italian translation from [@lorenzo-arena](https://github.com/lorenzo-arena). +- Fixed typos in Indonesian translation from [@ekojs](https://github.com/ekojs). + +## [1.0.0] - 2017-06-20 + +### Added From 213829b68af9c3b32abfbc11c5c5f1a64dcad30c Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 4 Dec 2022 12:01:02 +0100 Subject: [PATCH 21/33] Add changelog for v6 --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c67ac6e..ef2939b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,7 @@ 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). -## [Unreleased] - -v6.x +## [6.0.0] ### Changes From f9c7669bf8410fab066ba98444409a45ff97447c Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 4 Dec 2022 12:05:09 +0100 Subject: [PATCH 22/33] Update changelog --- CHANGELOG.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef2939b..1a682d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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.0.0] +## [6.0.0] - 2022-12-04 ### Changes @@ -34,19 +34,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Remove session `count` method. * Remove `SessionAwareInterface` in favor of dependency injection. -## [1.1.0] - 2019-02-15 - -### Added - -- Danish translation from [@frederikspang](https://github.com/frederikspang). -- Georgian translation from [@tatocaster](https://github.com/tatocaster). -- Changelog inconsistency section in Bad Practices - -### Changed - -- Fixed typos in Italian translation from [@lorenzo-arena](https://github.com/lorenzo-arena). -- Fixed typos in Indonesian translation from [@ekojs](https://github.com/ekojs). - -## [1.0.0] - 2017-06-20 - -### Added From 2666bd1afffbda520c82baa1f4f15169280bea36 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 4 Dec 2022 12:12:26 +0100 Subject: [PATCH 23/33] Add mixed as parameter and return ype --- src/MemorySession.php | 4 ++-- src/PhpSession.php | 4 ++-- src/SessionInterface.php | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/MemorySession.php b/src/MemorySession.php index 3eafa4b..7f090a9 100644 --- a/src/MemorySession.php +++ b/src/MemorySession.php @@ -77,7 +77,7 @@ public function getName(): string return $this->options['name']; } - public function get(string $key, $default = null) + public function get(string $key, mixed $default = null): mixed { return $this->storage[$key] ?? $default; } @@ -87,7 +87,7 @@ public function all(): array return (array)$this->storage; } - public function set(string $key, $value): void + public function set(string $key, mixed $value): void { $this->storage[$key] = $value; } diff --git a/src/PhpSession.php b/src/PhpSession.php index 8bfdba6..e882e03 100644 --- a/src/PhpSession.php +++ b/src/PhpSession.php @@ -149,7 +149,7 @@ public function getName(): string return (string)session_name(); } - public function get(string $key, $default = null) + public function get(string $key, mixed $default = null): mixed { return $this->storage[$key] ?? $default; } @@ -159,7 +159,7 @@ public function all(): array return (array)$this->storage; } - public function set(string $key, $value): void + public function set(string $key, mixed $value): void { $this->storage[$key] = $value; } diff --git a/src/SessionInterface.php b/src/SessionInterface.php index 2f63579..fc76307 100644 --- a/src/SessionInterface.php +++ b/src/SessionInterface.php @@ -3,7 +3,7 @@ namespace Odan\Session; /** - * Interface. + * The session data operations. */ interface SessionInterface { @@ -11,11 +11,11 @@ interface SessionInterface * Gets an attribute by key. * * @param string $key The key name or null to get all values - * @param null $default The default value + * @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, $default = null); + public function get(string $key, mixed $default = null): mixed; /** * Gets all values as array. @@ -32,7 +32,7 @@ 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. From 5fb95a1f899dddb01dd7570bc254923922bc98a0 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 22 Jan 2023 11:36:22 +0100 Subject: [PATCH 24/33] Add has method to FlashInterface --- src/FlashInterface.php | 4 ++-- src/MemorySession.php | 5 +++++ src/PhpSession.php | 5 +++++ src/SessionInterface.php | 9 +++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/FlashInterface.php b/src/FlashInterface.php index bfb6307..60cc73c 100644 --- a/src/FlashInterface.php +++ b/src/FlashInterface.php @@ -29,9 +29,9 @@ 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; diff --git a/src/MemorySession.php b/src/MemorySession.php index 7f090a9..c50fdbd 100644 --- a/src/MemorySession.php +++ b/src/MemorySession.php @@ -99,6 +99,11 @@ public function setValues(array $values): void } } + public function has(string $key): bool + { + return array_key_exists($key, $this->storage); + } + public function delete(string $key): void { unset($this->storage[$key]); diff --git a/src/PhpSession.php b/src/PhpSession.php index e882e03..0e8e437 100644 --- a/src/PhpSession.php +++ b/src/PhpSession.php @@ -171,6 +171,11 @@ public function setValues(array $values): void } } + public function has(string $key): bool + { + return array_key_exists($key, $this->storage); + } + public function delete(string $key): void { unset($this->storage[$key]); diff --git a/src/SessionInterface.php b/src/SessionInterface.php index fc76307..d7066d1 100644 --- a/src/SessionInterface.php +++ b/src/SessionInterface.php @@ -41,6 +41,15 @@ public function set(string $key, mixed $value): void; */ public function setValues(array $values): void; + /** + * Check if an attribute key exists. + * + * @param string $key The key + * + * @return bool True if the key is set or not + */ + public function has(string $key): bool; + /** * Deletes an attribute by key. * From 27e52b49f26b484d5cdd1c1aa569cae30e87db80 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 22 Jan 2023 11:36:38 +0100 Subject: [PATCH 25/33] Fix build for php 8.2 --- composer.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index ef44ab9..eb2dbcc 100644 --- a/composer.json +++ b/composer.json @@ -27,8 +27,14 @@ "symfony/finder": "6.0.*" }, "scripts": { - "cs:check": "php-cs-fixer fix --dry-run --format=txt --verbose --diff --config=.cs.php --ansi", - "cs:fix": "php-cs-fixer fix --config=.cs.php --ansi", + "cs:check": [ + "@putenv PHP_CS_FIXER_IGNORE_ENV=1", + "php-cs-fixer fix --dry-run --format=txt --verbose --diff --config=.cs.php --ansi" + ], + "cs:fix": [ + "@putenv PHP_CS_FIXER_IGNORE_ENV=1", + "php-cs-fixer fix --config=.cs.php --ansi" + ], "sniffer:check": "phpcs --standard=phpcs.xml", "sniffer:fix": "phpcbf --standard=phpcs.xml", "stan": "phpstan analyse -c phpstan.neon --no-progress --ansi --xdebug", From 52dcd0774588e421237897536ce4d5af386112ce Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 22 Jan 2023 11:36:50 +0100 Subject: [PATCH 26/33] Update docs --- docs/v6/index.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/v6/index.md b/docs/v6/index.md index a6d05d9..3fa18c7 100644 --- a/docs/v6/index.md +++ b/docs/v6/index.md @@ -91,9 +91,6 @@ $session->clear(); // Generate a new session ID $session->regenerateId(); -// Clears all session -$session->destroy(); - // Get the current session ID $sessionId = $session->getId(); From 6484bcc662f32d6bc0036e2ca87a838a3dd8298f Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 22 Jan 2023 11:40:11 +0100 Subject: [PATCH 27/33] Add php 8.2 to build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ca1e0f..6490dfd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: operating-system: [ ubuntu-latest ] - php-versions: [ '8.0', '8.1' ] + php-versions: [ '8.0', '8.1', '8.2' ] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: From 379f41775a6d5b3b6a1dd41e025b50241d5ea7aa Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 5 Mar 2023 13:34:52 +0100 Subject: [PATCH 28/33] Update Twig flash messages example --- docs/v6/index.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/v6/index.md b/docs/v6/index.md index 3fa18c7..607d5a0 100644 --- a/docs/v6/index.md +++ b/docs/v6/index.md @@ -130,7 +130,16 @@ $messages = $flash->all(); ### Twig flash messages -Add the Flash instance as global twig variable within the `Twig::class` container definition: +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; From c501dc3c0a7e789166dee1402eca75d1edcf1ba7 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 5 Mar 2023 13:39:24 +0100 Subject: [PATCH 29/33] Remove phpunit 10 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index eb2dbcc..eaa1ff6 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "middlewares/utils": "^3", "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1", - "phpunit/phpunit": "^7 || ^8 || ^9 || ^10", + "phpunit/phpunit": "^7 || ^8 || ^9", "squizlabs/php_codesniffer": "^3", "symfony/console": "6.0.*", "symfony/event-dispatcher": "6.0.*", From c7afc83519a109dd45039662ea8dd8aea7675761 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Mon, 31 Jul 2023 10:21:22 +0200 Subject: [PATCH 30/33] Update docs --- docs/v6/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/v6/index.md b/docs/v6/index.md index 607d5a0..8462d50 100644 --- a/docs/v6/index.md +++ b/docs/v6/index.md @@ -1,7 +1,7 @@ --- layout: default title: Version 6 -nav_order: 3 +nav_order: 4 description: "Version 6" --- @@ -177,7 +177,7 @@ $options = [ // Optional: Send cookie only over https 'cookie_secure' => true, // Optional: Additional XSS protection - // Note: The cookie is not accessible for JavaScript! + // Note: This cookie is not accessible in JavaScript! 'cookie_httponly' => false, ]; From 6961573542276e44b6c48871b811ba8207002108 Mon Sep 17 00:00:00 2001 From: RoomCays Date: Mon, 25 Nov 2024 12:08:01 +0100 Subject: [PATCH 31/33] Replace `path` with `save_path` in documentation (#38) There is no `session.path` configuration key in PHP, but there is `session.save_path`, which - I believe - is the right one to be communicated in docs. --- docs/v6/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v6/index.md b/docs/v6/index.md index 8462d50..75037b1 100644 --- a/docs/v6/index.md +++ b/docs/v6/index.md @@ -229,7 +229,7 @@ Add your application-specific settings: $settings['session'] = [ 'name' => 'app', 'lifetime' => 7200, - 'path' => null, + 'save_path' => null, 'domain' => null, 'secure' => false, 'httponly' => true, From 6344a65fbe9322ddbecd7c546eac8a74cc5975f9 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Sun, 15 Dec 2024 14:22:10 +0100 Subject: [PATCH 32/33] Merge 6.x (#40) --- .cs.php | 33 ++++++++++++++++-- .github/workflows/build.yml | 10 ++++-- .gitignore | 25 +++++++++----- CHANGELOG.md | 30 ++++++++++++++++ composer.json | 27 +++++++-------- docs/v6/index.md | 2 +- phpstan.neon | 3 -- phpunit.xml | 51 +++++++++++++++------------- src/Flash.php | 6 ++++ src/FlashInterface.php | 4 +-- src/MemorySession.php | 9 +++++ src/PhpSession.php | 9 +++++ src/SessionInterface.php | 4 +-- tests/MemorySessionTest.php | 2 +- tests/PhpSessionTest.php | 2 +- tests/SessionStartMiddlewareTest.php | 2 +- 16 files changed, 156 insertions(+), 63 deletions(-) diff --git a/.cs.php b/.cs.php index 9653bee..5568e99 100644 --- a/.cs.php +++ b/.cs.php @@ -19,7 +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'], @@ -33,11 +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, - 'global_namespace_import' => false, + '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 6490dfd..f2acf8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,12 @@ name: build -on: [ push, pull_request ] +on: + push: + branches: + - '6.x' + pull_request: + branches: + - '*' jobs: run: @@ -8,7 +14,7 @@ jobs: strategy: matrix: operating-system: [ ubuntu-latest ] - php-versions: [ '8.0', '8.1', '8.2' ] + php-versions: [ '8.2', '8.3', '8.4' ] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: 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/CHANGELOG.md b/CHANGELOG.md index 1a682d4..5a34a36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,36 @@ 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 diff --git a/composer.json b/composer.json index eaa1ff6..5668aca 100644 --- a/composer.json +++ b/composer.json @@ -9,8 +9,8 @@ "homepage": "https://github.com/odan/session", "license": "MIT", "require": { - "php": "^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" }, @@ -18,34 +18,33 @@ "friendsofphp/php-cs-fixer": "^3", "middlewares/utils": "^3", "nyholm/psr7": "^1.5", - "phpstan/phpstan": "^1", - "phpunit/phpunit": "^7 || ^8 || ^9", - "squizlabs/php_codesniffer": "^3", - "symfony/console": "6.0.*", - "symfony/event-dispatcher": "6.0.*", - "symfony/filesystem": "6.0.*", - "symfony/finder": "6.0.*" + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^11", + "squizlabs/php_codesniffer": "^3" }, "scripts": { "cs:check": [ "@putenv PHP_CS_FIXER_IGNORE_ENV=1", - "php-cs-fixer fix --dry-run --format=txt --verbose --diff --config=.cs.php --ansi" + "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" + "php-cs-fixer fix --config=.cs.php --ansi --verbose" ], "sniffer:check": "phpcs --standard=phpcs.xml", "sniffer:fix": "phpcbf --standard=phpcs.xml", - "stan": "phpstan analyse -c phpstan.neon --no-progress --ansi --xdebug", - "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": [ "@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/v6/index.md b/docs/v6/index.md index 75037b1..2ac2212 100644 --- a/docs/v6/index.md +++ b/docs/v6/index.md @@ -24,7 +24,7 @@ description: "Version 6" ## Requirements -* PHP 8.0+ +* PHP 8.2+ ## Installation 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 b613eab..20e59cb 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,26 +1,29 @@ - - - - src - - - build - vendor - - - - - tests - - - - - - + + + + tests + + + + + + + + + src + + + build + vendor + + diff --git a/src/Flash.php b/src/Flash.php index ca173d4..38038bc 100644 --- a/src/Flash.php +++ b/src/Flash.php @@ -9,10 +9,16 @@ */ final class Flash implements FlashInterface { + /** + * @var array|ArrayAccess + */ private array|ArrayAccess $storage; private string $storageKey; + /** + * @param array|ArrayAccess $storage + */ public function __construct(array|ArrayAccess &$storage, string $storageKey = '_flash') { $this->storage = &$storage; diff --git a/src/FlashInterface.php b/src/FlashInterface.php index 60cc73c..674e83e 100644 --- a/src/FlashInterface.php +++ b/src/FlashInterface.php @@ -22,7 +22,7 @@ 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; @@ -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 c50fdbd..aa17d80 100644 --- a/src/MemorySession.php +++ b/src/MemorySession.php @@ -7,11 +7,17 @@ */ final class MemorySession implements SessionInterface, SessionManagerInterface { + /** + * @var array + */ private array $options = [ 'name' => 'app', 'lifetime' => 7200, ]; + /** + * @var array + */ private array $storage; private Flash $flash; @@ -20,6 +26,9 @@ final class MemorySession implements SessionInterface, SessionManagerInterface private bool $started = false; + /** + * @param array $options + */ public function __construct(array $options = []) { $keys = array_keys($this->options); diff --git a/src/PhpSession.php b/src/PhpSession.php index 0e8e437..50c4100 100644 --- a/src/PhpSession.php +++ b/src/PhpSession.php @@ -9,10 +9,16 @@ */ final class PhpSession implements SessionInterface, SessionManagerInterface { + /** + * @var array + */ private array $storage; private FlashInterface $flash; + /** + * @var array + */ private array $options = [ 'id' => null, 'name' => 'app', @@ -26,6 +32,9 @@ final class PhpSession implements SessionInterface, SessionManagerInterface 'cache_limiter' => 'nocache', ]; + /** + * @param array $options + */ public function __construct(array $options = []) { // Prevent uninitialized state diff --git a/src/SessionInterface.php b/src/SessionInterface.php index d7066d1..fc51313 100644 --- a/src/SessionInterface.php +++ b/src/SessionInterface.php @@ -20,7 +20,7 @@ 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; @@ -37,7 +37,7 @@ 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 $values The new values + * @param array $values The new values */ public function setValues(array $values): void; diff --git a/tests/MemorySessionTest.php b/tests/MemorySessionTest.php index d727281..bc43237 100644 --- a/tests/MemorySessionTest.php +++ b/tests/MemorySessionTest.php @@ -7,7 +7,7 @@ /** * Memory Session Test. * - * @coversDefaultClass \Odan\Session\MemorySession + * #[\PHPUnit\Framework\Attributes\CoversClass(\Odan\Session\MemorySession)] */ class MemorySessionTest extends PhpSessionTest { diff --git a/tests/PhpSessionTest.php b/tests/PhpSessionTest.php index 8df0a20..8ff0533 100644 --- a/tests/PhpSessionTest.php +++ b/tests/PhpSessionTest.php @@ -10,7 +10,7 @@ /** * Test. * - * @coversDefaultClass \Odan\Session\PhpSession + * #[\PHPUnit\Framework\Attributes\CoversClass(\Odan\Session\PhpSession)] */ class PhpSessionTest extends TestCase { diff --git a/tests/SessionStartMiddlewareTest.php b/tests/SessionStartMiddlewareTest.php index f62abd4..4f4d67c 100644 --- a/tests/SessionStartMiddlewareTest.php +++ b/tests/SessionStartMiddlewareTest.php @@ -10,7 +10,7 @@ /** * Test. * - * @coversDefaultClass \Odan\Session\Middleware\SessionStartMiddleware + * #[\PHPUnit\Framework\Attributes\CoversClass(\Odan\Session\Middleware\SessionStartMiddleware)] */ class SessionStartMiddlewareTest extends TestCase { From 26727935276bd9ee1bb7ea918a8a8abb91da3017 Mon Sep 17 00:00:00 2001 From: Daniel Opitz Date: Tue, 29 Jul 2025 22:24:16 +0200 Subject: [PATCH 33/33] Update readme --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 8f2daff..2a04416 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,6 @@ [![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.