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..e10403b 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,14 +23,15 @@ extra:
project: Mezzio
installation:
config_provider_class: 'Mezzio\Session\ConfigProvider'
- current_version: v1
+ current_version: v2
versions:
+ - v2
- v1
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/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([]);