From c1d603e1b6d75408ecca1006193ea3c040fc419b Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Mon, 19 Aug 2024 18:44:43 +0300 Subject: [PATCH 1/5] Removed SessionIdentifierAwareInterface Signed-off-by: alexmerlin --- docs/book/v2/installation.md | 5 + docs/book/v2/intro.md | 73 +++++++ docs/book/v2/middleware.md | 142 +++++++++++++ docs/book/v2/persistence.md | 108 ++++++++++ docs/book/v2/session.md | 252 ++++++++++++++++++++++++ mkdocs.yml | 10 +- psalm-baseline.xml | 13 -- src/LazySession.php | 6 +- src/Session.php | 1 - src/SessionIdentifierAwareInterface.php | 27 --- src/SessionInterface.php | 5 + src/SessionPersistenceInterface.php | 2 - test/SessionTest.php | 7 - 13 files changed, 595 insertions(+), 56 deletions(-) create mode 100644 docs/book/v2/installation.md create mode 100644 docs/book/v2/intro.md create mode 100644 docs/book/v2/middleware.md create mode 100644 docs/book/v2/persistence.md create mode 100644 docs/book/v2/session.md delete mode 100644 src/SessionIdentifierAwareInterface.php diff --git a/docs/book/v2/installation.md b/docs/book/v2/installation.md new file mode 100644 index 0000000..be1198c --- /dev/null +++ b/docs/book/v2/installation.md @@ -0,0 +1,5 @@ +# This Is Only a Placeholder + +The content of this page can be found under: + +https://github.com/laminas/documentation-theme/blob/master/theme/pages/installation.html diff --git a/docs/book/v2/intro.md b/docs/book/v2/intro.md new file mode 100644 index 0000000..57fe4f5 --- /dev/null +++ b/docs/book/v2/intro.md @@ -0,0 +1,73 @@ +# mezzio-session + +Web applications often need to persist user state between requests, and the +generally accepted way to do so is via _sessions_. While PHP provides its own +session extension, it: + +- uses global functions that affect global state. +- relies on a superglobal for access to both read and write the session data. +- incurs either filesystem or network I/O on every request, depending on the + session storage handler. +- can clobber the `Set-Cookie` header when other processes also set it. + +Some projects, such as [psr-7-sessions/storageless](https://github.com/psr7-sessions/storageless), +take a different approach, using [JSON Web Tokens](https://tools.ietf.org/html/rfc7519) (JWT). + +The goals of mezzio-session are: + +- to abstract the way users interact with session storage. +- to abstract how sessions are persisted, to allow both standard ext-session, + but also other paradigms such as JWT. +- to provide session capabilities that "play nice" with + [PSR-7](http://www.php-fig.org/psr/psr-7/) and middleware. + +## Installation + +Use [Composer](https://getcomposer.org) to install this package: + +```bash +$ composer require mezzio/mezzio-session +``` + +However, the package is not immediately useful unless you have a persistence +adapter. If you are okay with using ext-session, you can install the following +package as well: + +```bash +$ composer require mezzio/mezzio-session-ext +``` + +## Features + +mezzio-session provides the following: + +- Interfaces for: + - session containers + - session persistence +- An implementation of the session container. +- A "lazy-loading" implementation of the session container, to allow delaying + any de/serialization and/or I/O processes until session data is requested; + this implementation decorates a normal session container. +- PSR-7 middleware that: + - composes a session persistence implementation. + - initializes the lazy-loading session container, using the session + persistence implementation. + - delegates to the next middleware, passing the session container into the + request. + - finalizes the session before returning the response. + +Persistence implementations locate session information from the requests (e.g., +via a cookie) in order to initialize the session. On completion of the request, +they examine the session container for changes and/or to see if it is empty, and +provide data to the response so as to notify the client of the session (e.g., +via a `Set-Cookie` header). + +Note that the goals of this package are solely focused on _session persistence_ +and _access to session data by middleware_. If you also need other features +often related to session data, you may want to consider the following packages: + +- [mezzio-flash](https://github.com/mezzio/mezzio-flash): + provides flash message capabilities. +- [mezzio-csrf](https://github.com/mezzio/mezzio-csrf): + provides CSRF token generation, storage, and verification, using either a + session container, or flash messages. diff --git a/docs/book/v2/middleware.md b/docs/book/v2/middleware.md new file mode 100644 index 0000000..be60cd9 --- /dev/null +++ b/docs/book/v2/middleware.md @@ -0,0 +1,142 @@ +# Session Middleware + +mezzio-session provides middleware consuming +[PSR-7](http://www.php-fig.org/psr/psr-7/) HTTP message instances, via +implementation of [PSR-15](https://www.php-fig.org/psr/psr-15/) +interfaces. + +This middleware composes a [persistence](persistence.md) instance, and uses that +in order to generate a session container, which it pushes into the request it +delegates to the next middleware. Once a response is returned, it uses the +persistence instance to persist the session data and provide information back to +the client. + +The above two paragraphs are longer than the body of the middleware +implementation: + +```php +namespace Mezzio\Session; + +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +class SessionMiddleware implements MiddlewareInterface +{ + public const SESSION_ATTRIBUTE = 'session'; + + private $persistence; + + public function __construct(SessionPersistenceInterface $persistence) + { + $this->persistence = $persistence; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface + { + $session = new LazySession($this->persistence, $request); + $response = $handler->handle( + $request + ->withAttribute(self::SESSION_ATTRIBUTE, $session) + ->withAttribute(SessionInterface::class, $session) + ); + return $this->persistence->persistSession($session, $response); + } +} +``` + +## Configuration + +This package provides a factory for `Mezzio\Session\SessionMiddleware` +via `Mezzio\Session\SessionMiddlewareFactory`; this factory is +auto-wired if you are using Mezzio and the laminas-component-installer Composer +plugin. If not, you will need to wire these into your application. + +The factory depends on one service: `Mezzio\Session\SessionPersistenceInterface`. +You will need to either wire in your persistence implementation of choice, or +have the package providing it do so for you. + +## Adding the middleware to your application + +You may pipe this middleware anywhere in your application. If you want to have +it available anywhere, pipe it early in your application, prior to any routing. +As an example, within Mezzio, you could pipe it in the `config/pipeline.php` +file: + +```php +$app->pipe(\Mezzio\Session\SessionMiddleware::class); +$app->pipe(\Mezzio\Router\Middleware\RouteMiddleware::class); +``` + +This will generally be an inexpensive operation; since the middleware uses a +`LazySession` instance, unless your persistence implementation does any work in +its constructor, the cost is just that of instantiating a few objects. + +However, it's often useful to specifically include such middleware directly in +the routed middleware pipelines, to ensure other developers are aware of its +presence in that route's workflow. + +Within Mezzio, you can do this when routing, in your `config/routes.php` +file, or within a [delegator factory](https://docs.mezzio.dev/mezzio/cookbook/autowiring-routes-and-pipelines/#delegator-factories): + +```php +$app->post('/login', [ + \Mezzio\Session\SessionMiddleware::class, + \User\Middleware\LoginHandler::class +]); +``` + +## Retrieving the session in your own middleware + +Whilst it is trivial to retrieve the initialised session from the request with `$session = $request->getAttribute(SessionInterface::class);`, static analysers cannot automatically infer the value assigned to `$session`. + +To provide a convenient and type safe way to retrieve the session from the current request without manually asserting its type, `SessionRetrieval::fromRequest($request)` can be called so that you can use the request without further assertions. + +Furthermore, a static method exists to optionally retrieve a session when you cannot be sure the middleware has previously been piped: `SessionRetrieval::fromRequestOrNull($request)` + +```php +namespace My\NameSpace; + +use Mezzio\Session\Exception\SessionNotInitializedException; +use Mezzio\Session\RetrieveSession; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; + +class MyRequestHandler implements RequestHandlerInterface { + + // ... + + public function handle(ServerRequestInterface $request) : ResponseInterface + { + try { + $session = RetrieveSession::fromRequest($request); + } catch (SessionNotInitializedException $error) { + // Handle the uninitialized session: + return $this->redirectToLogin(); + } + + $value = $session->get('SomeKey'); + $this->templateRenderer->render('some:template', ['value' => $value]); + } +} + +class AnotherRequestHandler implements RequestHandlerInterface { + + // ... + + public function handle(ServerRequestInterface $request) : ResponseInterface + { + $session = RetrieveSession::fromRequestOrNull($request); + if (! $session) { + // Handle the uninitialized session: + return $this->redirectToLogin(); + } + + $value = $session->get('SomeKey'); + $this->templateRenderer->render('some:template', ['value' => $value]); + } +} + +``` diff --git a/docs/book/v2/persistence.md b/docs/book/v2/persistence.md new file mode 100644 index 0000000..9714da2 --- /dev/null +++ b/docs/book/v2/persistence.md @@ -0,0 +1,108 @@ +# Session Persistence + +Session persistence within mezzio-session refers to one or both of the +following: + +- Identifying session information provided by the client making the request. +- Storing session data for access on subsequent requests. +- Providing session information to the client making the request. + +In some scenarios, such as usage of JSON Web Tokens (JWT), the serialized +session data is provided _by_ the client, and provided _to_ the client directly, +without any server-side storage whatsoever. + +To describe these operations, we provide `Mezzio\Session\SessionPersistenceInterface`: + +```php +namespace Mezzio\Session; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +interface SessionPersistenceInterface +{ + /** + * Generate a session data instance based on the request. + */ + public function initializeSessionFromRequest(ServerRequestInterface $request) : SessionInterface; + + /** + * Persist the session data instance. + * + * Persists the session data, returning a response instance with any + * artifacts required to return to the client. + */ + public function persistSession(SessionInterface $session, ResponseInterface $response) : ResponseInterface; +} +``` + +Session initialization pulls data from the request (a cookie, a header value, +etc.) in order to produce a session container. Session persistence pulls data +from the session container, does something with it, and then optionally provides +a response containing session artifacts (a cookie, a header value, etc.). + +For sessions to work, _you must provide a persistence implementation_. We +provide one such implementation using PHP's session extension via the package +[mezzio-session-ext](https://github.com/mezzio/mezzio-session-ext). + +## Session identifiers + +Typically, the session identifier will be retrieved from the request (usually +via a cookie), and a new identifier created if none was discovered. + +During persistence, if an existing session's contents have changed, or +`regenerateId()` was called on the session, the persistence implementation +becomes responsible for: + +- Removing the original session. +- Generating a new identifier for the session. + +In all situations, it then needs to store the session data in such a way that a +later lookup by the current identifier will retrieve that data. + +Prior to version 1.1.0, persistence engines had two ways to determine what the +original session identifier was when it came time to regenerate or persist a +session: + +- Store the identifier as a property of the persistence implementation. +- Store the identifier in the session data under a "magic" key (e.g., + `__SESSION_ID__`). + +The first approach is problematic when using mezzio-session in an async +environment such as [Swoole](https://swoole.co.uk) or +[ReactPHP](https://reactphp.org), as the same persistence instance may be used +by several simultaneous requests. `Mezzio\Session\SessionInterface` defines a new +`getId` method, implementations can thus store the +identifier internally, and, when it comes time to store the session data, +persistence implementations can query that method in order to retrieve the +session identifier. + +## Persistent sessions + +- Since 1.2.0. + +If your persistence implementation supports persistent sessions — for +example, by setting an `Expires` or `Max-Age` cookie directive — then you +can opt to globally set a default session duration, or allow developers to hint +a desired session duration via the session container using +`SessionContainerPersistenceInterface::persistSessionFor()`. + +Implementations SHOULD honor the value of `SessionContainerPersistenceInterface::getSessionLifetime()` +when persisting the session data. This could mean either or both of the +following: + +- Ensuring that the session data will not be purged until after the specified + TTL value. +- Setting an `Expires` or `Max-Age` cookie directive. + +In each case, the persistence engine should query the `Session` instance for a +TTL value: + +```php +$ttl = $session instanceof SessionContainerPersistenceInterface + ? $session->getSessionLifetime() + : $defaultLifetime; // likely 0, to indicate automatic expiry +``` + +`getSessionLifetime()` returns an `integer` value indicating the number of +seconds the session should persist. diff --git a/docs/book/v2/session.md b/docs/book/v2/session.md new file mode 100644 index 0000000..0dbe0df --- /dev/null +++ b/docs/book/v2/session.md @@ -0,0 +1,252 @@ +# Session Containers + +Session containers are the primary interface with which most application +developers will work; they contain the data currently in the session, and allow +you to push data to the session. + +All session containers implement `Mezzio\Session\SessionInterface`: + +```php +namespace Mezzio\Session; + +interface SessionInterface +{ + /** + * Serialize the session data to an array for storage purposes. + */ + public function toArray() : array; + + /** + * Retrieve a value from the session. + * + * @param mixed $default Default value to return if $name does not exist. + * @return mixed + */ + public function get(string $name, $default = null); + + /** + * Whether or not the container has the given key. + */ + public function has(string $name) : bool; + + /** + * Set a value within the session. + * + * Values MUST be serializable in any format; we recommend ensuring the + * values are JSON serializable for greatest portability. + * + * @param mixed $value + */ + public function set(string $name, $value) : void; + + /** + * Remove a value from the session. + */ + public function unset(string $name) : void; + + /** + * Clear all values. + */ + public function clear() : void; + + /** + * Does the session contain changes? If not, the middleware handling + * session persistence may not need to do more work. + */ + public function hasChanged() : bool; + + /** + * Regenerate the session. + * + * This can be done to prevent session fixation. When executed, it SHOULD + * return a new instance; that instance should always return true for + * isRegenerated(). + * + * An example of where this WOULD NOT return a new instance is within the + * shipped LazySession, where instead it would return itself, after + * internally re-setting the proxied session. + */ + public function regenerate(): SessionInterface; + + /** + * Method to determine if the session was regenerated; should return + * true if the instance was produced via regenerate(). + */ + public function isRegenerated() : bool; +} +``` + +The default implementation, and the one you'll most likely interact with, is +`Mezzio\Session\Session`. + +Since version 1.2.0, we provide `Mezzio\Session\SessionCookiePersistenceInterface`: + +```php +namespace Mezzio\Session; + +/** + * Allow marking session cookies as persistent. + * + * It can be useful to mark a session as persistent: e.g., for a "Remember Me" + * feature when logging a user into your system. PHP provides this capability + * via ext-session with the $lifetime argument to session_set_cookie_params() + * as well as by the session.cookie_lifetime INI setting. The latter will set + * the value for all session cookies sent (or until the value is changed via + * an ini_set() call), while the former will only affect cookies created during + * the current script lifetime. + * + * Persistence engines may, of course, allow setting a global lifetime. This + * interface allows developers to set the lifetime programmatically. Persistence + * implementations are encouraged to use the value to set the cookie lifetime + * when creating and returning a cookie. Additionally, to ensure the cookie + * lifetime originally requested is honored when a session is regenerated, we + * recommend persistence engines to store the TTL in the session data itself, + * so that it can be re-sent in such scenarios. + */ +interface SessionCookiePersistenceInterface +{ + const SESSION_LIFETIME_KEY = '__SESSION_TTL__'; + + /** + * Define how long the session cookie should live. + * + * Use this value to detail to the session persistence engine how long the + * session cookie should live. + * + * This value could be passed as the $lifetime value of + * session_set_cookie_params(), or used to create an Expires or Max-Age + * parameter for a session cookie. + * + * Since cookie lifetime is communicated by the server to the client, and + * not vice versa, the value should likely be persisted in the session + * itself, to ensure that session regeneration uses the same value. We + * recommend using the SESSION_LIFETIME_KEY value to communicate this. + * + * @param int $duration Number of seconds the cookie should persist for. + */ + public function persistSessionFor(int $duration) : void; + + /** + * Determine how long the session cookie should live. + * + * Generally, this will return the value provided to persistFor(). + * + * If that method has not been called, the value can return one of the + * following: + * + * - 0 or a negative value, to indicate the cookie should be treated as a + * session cookie, and expire when the window is closed. This should be + * the default behavior. + * - If persistFor() was provided during session creation or anytime later, + * the persistence engine should pull the TTL value from the session itself + * and return it here. Typically, this value should be communicated via + * the SESSION_LIFETIME_KEY value of the session. + */ + public function getSessionLifetime() : int; +} +``` + +`Mezzio\Session\Session` and `Mezzio\Session\LazySession` both +implement each of the interfaces listed above. `Session` accepts an optional +identifier to its constructor, and will use the value of the +`SessionCookiePersistenceInterface::SESSION_LIFETIME_KEY` in the provided data +to seed the session cookie lifetime, if present. + +## Usage + +Session containers will typically be passed to your middleware using the +[SessionMiddleware](middleware.md), via the +`Mezzio\Session\SessionMiddleware::SESSION_ATTRIBUTE` ("session") or the +`Mezzio\Session\SessionInterface::class` ("Mezzio\Session\SessionInterface"; +available since version 1.4.0) request attribute. + +Once you have the container, you can check for data: + +```php +if ($session->has('user')) { +} +``` + +and retrieve it: + +```php +$user = $session->get('user'); +``` + +You can combine those operations, by passing a default value as a second +argument to the `get()` method: + +```php +$user = $session->get('user', new GuestUser()); +``` + +If a datum is no longer relevant in the session, `unset()` it: + +```php +$session->unset('user'); +``` + +If none of the data is relevant, `clear()` the session: + +```php +$session->clear(); +``` + +### Persistent Sessions + +- Since 1.2.0 + +You can hint to the session persistence engine how long the session should +persist: + +```php +$session->persistSessionFor(60 * 60 * 24 * 7); // persist for 7 days +``` + +To make the session expire when the browser session is terminated (default +behavior), use zero or a negative integer: + +```php +$session->persistSessionFor(0); // expire data after session is over +``` + +## Lazy Sessions + +This package provides another implementation of `SessionInterface` via +`Mezzio\Session\LazySession`. This implementation does the following: + +- It composes a [persistence](persistence.md) instance, along with the current + request. +- On _first access_ (e.g., `get()`, `set()`, etc.), it uses the composed + persistence and request instances to generate the _actual_ session container. + All methods then _proxy_ to this container. + +This approach helps delay any I/O or network operations, and/or +deserialization, until they are actually needed. + +The shipped [SessionMiddleware](middleware.md) produces a `LazySession`. + +## Session Regeneration + +Some application events benefit from _session regeneration_. In particular, +after a user has successfully logged in or out, you will generally want to +regenerate the session in order to prevent session fixation and the attack +vectors it invites. + +In those situations, call `regenerate()`: + +```php +$newSession = $session->regenerate(); +``` + +The interface indicates that a new instance _should_ be returned. However, in +the default usage, you will have a `LazySession` instance (as described above), +which _decorates_ the underlying session storage. This is done for two reasons: + +- First, the stated reasons of preventing the need to deserialize data and/or + perform I/O access until the last moment. +- Second, to ensure that the `SessionMiddleware` _always has a pointer to the + session_. + +This latter is what allows you to regenerate the session in middleware nested +deep in your application, but still have the data persisted correctly. diff --git a/mkdocs.yml b/mkdocs.yml index 0a71782..0bf8207 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,6 +2,13 @@ docs_dir: docs/book site_dir: docs/html nav: - Home: index.md + - v2: + - Introduction: v2/intro.md + - Installation: v2/installation.md + - Usage: + - "Session Containers": v2/session.md + - "Session Persistence": v2/persistence.md + - "Session Middleware": v2/middleware.md - v1: - Introduction: v1/intro.md - Installation: v1/installation.md @@ -16,8 +23,9 @@ extra: project: Mezzio installation: config_provider_class: 'Mezzio\Session\ConfigProvider' - current_version: v1 + current_version: v2 versions: + - v2 - v1 plugins: - redirects: diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 9c1b45b..6ef2312 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -5,20 +5,12 @@ generateCacheHeaders - - - LazySession - - __construct - - Session - null|bool|int|float|string|array @@ -26,11 +18,6 @@ json_decode(json_encode($value, JSON_PRESERVE_ZERO_FRACTION), true) - - - - - provideCacheHeaderValues diff --git a/src/LazySession.php b/src/LazySession.php index da327ba..b522b23 100644 --- a/src/LazySession.php +++ b/src/LazySession.php @@ -19,7 +19,6 @@ */ final class LazySession implements SessionCookiePersistenceInterface, - SessionIdentifierAwareInterface, SessionInterface, InitializeSessionIdInterface { @@ -110,10 +109,7 @@ public function hasChanged(): bool */ public function getId(): string { - $proxiedSession = $this->getProxiedSession(); - return $proxiedSession instanceof SessionIdentifierAwareInterface - ? $proxiedSession->getId() - : ''; + return $this->getProxiedSession()->getId(); } /** diff --git a/src/Session.php b/src/Session.php index 2244784..564a553 100644 --- a/src/Session.php +++ b/src/Session.php @@ -13,7 +13,6 @@ class Session implements SessionCookiePersistenceInterface, - SessionIdentifierAwareInterface, SessionInterface { /** diff --git a/src/SessionIdentifierAwareInterface.php b/src/SessionIdentifierAwareInterface.php deleted file mode 100644 index a8632a0..0000000 --- a/src/SessionIdentifierAwareInterface.php +++ /dev/null @@ -1,27 +0,0 @@ -assertSame($expected, $session->get('foo')); } - public function testImplementsSessionIdentifierAwareInterface(): void - { - $session = new Session([]); - $this->assertInstanceOf(SessionIdentifierAwareInterface::class, $session); - } - public function testGetIdReturnsEmptyStringIfNoIdentifierProvidedToConstructor(): void { $session = new Session([]); From 49412ffd8bbc7c4b6d05ce3e250d390386a99c1b Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Tue, 20 Aug 2024 08:20:14 +0300 Subject: [PATCH 2/5] Redirect non-versioned URLs to v2. Signed-off-by: alexmerlin --- mkdocs.yml | 10 +++---- src/Session.php | 10 +++++-- src/SessionInterface.php | 2 ++ .../SessionCookieAwareTraitTest.php | 6 ++-- test/SessionTest.php | 30 ++++++++----------- 5 files changed, 30 insertions(+), 28 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 0bf8207..e10403b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -30,8 +30,8 @@ extra: plugins: - redirects: redirect_maps: - intro.md: v1/intro.md - installation.md: v1/installation.md - session.md: v1/session.md - persistence.md: v1/persistence.md - middleware.md: v1/middleware.md + intro.md: v2/intro.md + installation.md: v2/installation.md + session.md: v2/session.md + persistence.md: v2/persistence.md + middleware.md: v2/middleware.md diff --git a/src/Session.php b/src/Session.php index 564a553..91fde47 100644 --- a/src/Session.php +++ b/src/Session.php @@ -30,6 +30,8 @@ class Session implements * when it is time to persist the session, instead of relying on state in * the persistence instance (which may be shared between multiple * requests). + * + * @psalm-var non-empty-string */ private string $id; @@ -47,8 +49,11 @@ class Session implements */ private int $sessionLifetime = 0; - /** @param array $data */ - public function __construct(array $data, string $id = '') + /** + * @param array $data + * @psalm-param non-empty-string $id + */ + public function __construct(array $data, string $id) { $this->data = $this->originalData = $data; $this->id = $id; @@ -141,6 +146,7 @@ public function isRegenerated(): bool /** * {@inheritDoc} * + * @return non-empty-string * @since 1.1.0 */ public function getId(): string diff --git a/src/SessionInterface.php b/src/SessionInterface.php index dcc7bb4..f31646d 100644 --- a/src/SessionInterface.php +++ b/src/SessionInterface.php @@ -71,6 +71,8 @@ public function isRegenerated(): bool; /** * Retrieve the session identifier. + * + * @return non-empty-string */ public function getId(): string; } diff --git a/test/Persistence/SessionCookieAwareTraitTest.php b/test/Persistence/SessionCookieAwareTraitTest.php index c25d930..907fe80 100644 --- a/test/Persistence/SessionCookieAwareTraitTest.php +++ b/test/Persistence/SessionCookieAwareTraitTest.php @@ -118,7 +118,7 @@ public function testAddSessionCookieHeaderToResponse(): void $consumer = $this->createConsumerInstance($cookieName); - $session = new Session([]); + $session = new Session([], 'test'); $session->persistSessionFor($sessionLifetime); $response = $consumer->addSessionCookieHeaderToResponse(new Response(), $cookieValue, $session); @@ -243,7 +243,7 @@ public function testGetSessionCookieLifetimeReturnsExpectedResults( int $expected ): void { $consumer = $this->createConsumerInstance('SESSIONCOOKIENAME', $cookieLifetime ?? 0); - $session = new Session([]); + $session = new Session([], 'test'); if (isset($sessionLifetime)) { $session->persistSessionFor($sessionLifetime); } @@ -277,7 +277,7 @@ public function testSessionCookieIsDeletedFromBrowserWhenFlagIsSetAndSessionBeco $cookieValue = 'session-cookie-value'; $consumer = $this->createConsumerInstance($cookieName, null, null, null, null, null, null, true); - $session = new Session(['foo' => 'bar']); + $session = new Session(['foo' => 'bar'], 'test'); $session->clear(); $response = $consumer->addSessionCookieHeaderToResponse(new Response(), $cookieValue, $session); diff --git a/test/SessionTest.php b/test/SessionTest.php index b064d6c..5668488 100644 --- a/test/SessionTest.php +++ b/test/SessionTest.php @@ -20,25 +20,25 @@ class SessionTest extends TestCase { public function testImplementsSessionInterface(): void { - $session = new Session([]); + $session = new Session([], 'test'); $this->assertInstanceOf(SessionInterface::class, $session); } public function testIsNotChangedAtInstantiation(): void { - $session = new Session([]); + $session = new Session([], 'test'); $this->assertFalse($session->hasChanged()); } public function testIsNotRegeneratedByDefault(): void { - $session = new Session([]); + $session = new Session([], 'test'); $this->assertFalse($session->isRegenerated()); } public function testRegenerateProducesANewInstance(): SessionInterface { - $session = new Session([]); + $session = new Session([], 'test'); $regenerated = $session->regenerate(); $this->assertNotSame($session, $regenerated); return $regenerated; @@ -58,7 +58,7 @@ public function testRegeneratedSessionIsChanged(SessionInterface $session): void public function testSettingDataInSessionMakesItAccessible(): Session { - $session = new Session([]); + $session = new Session([], 'test'); $this->assertFalse($session->has('foo')); $session->set('foo', 'bar'); $this->assertTrue($session->has('foo')); @@ -91,7 +91,7 @@ public function testClearingSessionRemovesAllData(): void 'foo' => 'bar', 'baz' => 'bat', ]; - $session = new Session($original); + $session = new Session($original, 'test'); $this->assertSame($original, $session->toArray()); $session->clear(); @@ -119,18 +119,12 @@ public static function serializedDataProvider(): array #[DataProvider('serializedDataProvider')] public function testSetEnsuresDataIsJsonSerializable(object $data, array $expected): void { - $session = new Session([]); + $session = new Session([], 'test'); $session->set('foo', $data); $this->assertNotSame($data, $session->get('foo')); $this->assertSame($expected, $session->get('foo')); } - public function testGetIdReturnsEmptyStringIfNoIdentifierProvidedToConstructor(): void - { - $session = new Session([]); - $this->assertSame('', $session->getId()); - } - public function testGetIdReturnsValueProvidedToConstructor(): void { $session = new Session([], '1234abcd'); @@ -139,19 +133,19 @@ public function testGetIdReturnsValueProvidedToConstructor(): void public function testImplementsSessionCookiePersistenceInterface(): void { - $session = new Session([]); + $session = new Session([], 'test'); $this->assertInstanceOf(SessionCookiePersistenceInterface::class, $session); } public function testDefaultSessionCookieLifetimeIsZero(): void { - $session = new Session([]); + $session = new Session([], 'test'); $this->assertSame(0, $session->getSessionLifetime()); } public function testAllowsSettingCookieLifetime(): void { - $session = new Session([]); + $session = new Session([], 'test'); $session->persistSessionFor(60); $this->assertSame(60, $session->getSessionLifetime()); } @@ -160,13 +154,13 @@ public function testGetSessionLifetimeReturnsValueOfSessionLifetimeKeyWhenPresen { $session = new Session([ SessionCookiePersistenceInterface::SESSION_LIFETIME_KEY => 60, - ]); + ], 'test'); $this->assertSame(60, $session->getSessionLifetime()); } public function testPersistingSessionCookieLifetimeSetsLifetimeKeyInSessionData(): void { - $session = new Session([]); + $session = new Session([], 'test'); $session->persistSessionFor(60); $this->assertTrue($session->has(SessionCookiePersistenceInterface::SESSION_LIFETIME_KEY)); $this->assertSame(60, $session->get(SessionCookiePersistenceInterface::SESSION_LIFETIME_KEY)); From ce30d5916d37063ab2cc3ed8ecf50cf3b6a4477e Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Tue, 20 Aug 2024 12:34:32 +0300 Subject: [PATCH 3/5] Removed non-empty-string from SessionInterface::getId() and its usages. Signed-off-by: alexmerlin --- src/Session.php | 6 +---- src/SessionInterface.php | 2 -- .../SessionCookieAwareTraitTest.php | 6 ++--- test/SessionTest.php | 24 +++++++++---------- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/Session.php b/src/Session.php index 91fde47..17e8171 100644 --- a/src/Session.php +++ b/src/Session.php @@ -30,8 +30,6 @@ class Session implements * when it is time to persist the session, instead of relying on state in * the persistence instance (which may be shared between multiple * requests). - * - * @psalm-var non-empty-string */ private string $id; @@ -51,9 +49,8 @@ class Session implements /** * @param array $data - * @psalm-param non-empty-string $id */ - public function __construct(array $data, string $id) + public function __construct(array $data, string $id = '') { $this->data = $this->originalData = $data; $this->id = $id; @@ -146,7 +143,6 @@ public function isRegenerated(): bool /** * {@inheritDoc} * - * @return non-empty-string * @since 1.1.0 */ public function getId(): string diff --git a/src/SessionInterface.php b/src/SessionInterface.php index f31646d..dcc7bb4 100644 --- a/src/SessionInterface.php +++ b/src/SessionInterface.php @@ -71,8 +71,6 @@ public function isRegenerated(): bool; /** * Retrieve the session identifier. - * - * @return non-empty-string */ public function getId(): string; } diff --git a/test/Persistence/SessionCookieAwareTraitTest.php b/test/Persistence/SessionCookieAwareTraitTest.php index 907fe80..c25d930 100644 --- a/test/Persistence/SessionCookieAwareTraitTest.php +++ b/test/Persistence/SessionCookieAwareTraitTest.php @@ -118,7 +118,7 @@ public function testAddSessionCookieHeaderToResponse(): void $consumer = $this->createConsumerInstance($cookieName); - $session = new Session([], 'test'); + $session = new Session([]); $session->persistSessionFor($sessionLifetime); $response = $consumer->addSessionCookieHeaderToResponse(new Response(), $cookieValue, $session); @@ -243,7 +243,7 @@ public function testGetSessionCookieLifetimeReturnsExpectedResults( int $expected ): void { $consumer = $this->createConsumerInstance('SESSIONCOOKIENAME', $cookieLifetime ?? 0); - $session = new Session([], 'test'); + $session = new Session([]); if (isset($sessionLifetime)) { $session->persistSessionFor($sessionLifetime); } @@ -277,7 +277,7 @@ public function testSessionCookieIsDeletedFromBrowserWhenFlagIsSetAndSessionBeco $cookieValue = 'session-cookie-value'; $consumer = $this->createConsumerInstance($cookieName, null, null, null, null, null, null, true); - $session = new Session(['foo' => 'bar'], 'test'); + $session = new Session(['foo' => 'bar']); $session->clear(); $response = $consumer->addSessionCookieHeaderToResponse(new Response(), $cookieValue, $session); diff --git a/test/SessionTest.php b/test/SessionTest.php index 5668488..1f18b05 100644 --- a/test/SessionTest.php +++ b/test/SessionTest.php @@ -20,25 +20,25 @@ class SessionTest extends TestCase { public function testImplementsSessionInterface(): void { - $session = new Session([], 'test'); + $session = new Session([]); $this->assertInstanceOf(SessionInterface::class, $session); } public function testIsNotChangedAtInstantiation(): void { - $session = new Session([], 'test'); + $session = new Session([]); $this->assertFalse($session->hasChanged()); } public function testIsNotRegeneratedByDefault(): void { - $session = new Session([], 'test'); + $session = new Session([]); $this->assertFalse($session->isRegenerated()); } public function testRegenerateProducesANewInstance(): SessionInterface { - $session = new Session([], 'test'); + $session = new Session([]); $regenerated = $session->regenerate(); $this->assertNotSame($session, $regenerated); return $regenerated; @@ -58,7 +58,7 @@ public function testRegeneratedSessionIsChanged(SessionInterface $session): void public function testSettingDataInSessionMakesItAccessible(): Session { - $session = new Session([], 'test'); + $session = new Session([]); $this->assertFalse($session->has('foo')); $session->set('foo', 'bar'); $this->assertTrue($session->has('foo')); @@ -91,7 +91,7 @@ public function testClearingSessionRemovesAllData(): void 'foo' => 'bar', 'baz' => 'bat', ]; - $session = new Session($original, 'test'); + $session = new Session($original); $this->assertSame($original, $session->toArray()); $session->clear(); @@ -119,7 +119,7 @@ public static function serializedDataProvider(): array #[DataProvider('serializedDataProvider')] public function testSetEnsuresDataIsJsonSerializable(object $data, array $expected): void { - $session = new Session([], 'test'); + $session = new Session([]); $session->set('foo', $data); $this->assertNotSame($data, $session->get('foo')); $this->assertSame($expected, $session->get('foo')); @@ -133,19 +133,19 @@ public function testGetIdReturnsValueProvidedToConstructor(): void public function testImplementsSessionCookiePersistenceInterface(): void { - $session = new Session([], 'test'); + $session = new Session([]); $this->assertInstanceOf(SessionCookiePersistenceInterface::class, $session); } public function testDefaultSessionCookieLifetimeIsZero(): void { - $session = new Session([], 'test'); + $session = new Session([]); $this->assertSame(0, $session->getSessionLifetime()); } public function testAllowsSettingCookieLifetime(): void { - $session = new Session([], 'test'); + $session = new Session([]); $session->persistSessionFor(60); $this->assertSame(60, $session->getSessionLifetime()); } @@ -154,13 +154,13 @@ public function testGetSessionLifetimeReturnsValueOfSessionLifetimeKeyWhenPresen { $session = new Session([ SessionCookiePersistenceInterface::SESSION_LIFETIME_KEY => 60, - ], 'test'); + ]); $this->assertSame(60, $session->getSessionLifetime()); } public function testPersistingSessionCookieLifetimeSetsLifetimeKeyInSessionData(): void { - $session = new Session([], 'test'); + $session = new Session([]); $session->persistSessionFor(60); $this->assertTrue($session->has(SessionCookiePersistenceInterface::SESSION_LIFETIME_KEY)); $this->assertSame(60, $session->get(SessionCookiePersistenceInterface::SESSION_LIFETIME_KEY)); From 7039690d96816dcda89a68a7596431b7db911f19 Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Tue, 20 Aug 2024 12:37:31 +0300 Subject: [PATCH 4/5] Restored testGetIdReturnsEmptyStringIfNoIdentifierProvidedToConstructor test. Signed-off-by: alexmerlin --- test/SessionTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/SessionTest.php b/test/SessionTest.php index 1f18b05..b064d6c 100644 --- a/test/SessionTest.php +++ b/test/SessionTest.php @@ -125,6 +125,12 @@ public function testSetEnsuresDataIsJsonSerializable(object $data, array $expect $this->assertSame($expected, $session->get('foo')); } + public function testGetIdReturnsEmptyStringIfNoIdentifierProvidedToConstructor(): void + { + $session = new Session([]); + $this->assertSame('', $session->getId()); + } + public function testGetIdReturnsValueProvidedToConstructor(): void { $session = new Session([], '1234abcd'); From 8dc2dccf154dae6be9820f7c16dbb44cc4fcca10 Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Tue, 20 Aug 2024 12:40:32 +0300 Subject: [PATCH 5/5] Restored Session::__construct() comment block. Signed-off-by: alexmerlin --- src/Session.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Session.php b/src/Session.php index 17e8171..564a553 100644 --- a/src/Session.php +++ b/src/Session.php @@ -47,9 +47,7 @@ class Session implements */ private int $sessionLifetime = 0; - /** - * @param array $data - */ + /** @param array $data */ public function __construct(array $data, string $id = '') { $this->data = $this->originalData = $data;