Skip to content

Commit

Permalink
Merge pull request #71 from alexmerlin/issue-59-v2
Browse files Browse the repository at this point in the history
Removed `SessionIdentifierAwareInterface`
  • Loading branch information
gsteel authored Aug 20, 2024
2 parents ddcbc3b + 8dc2dcc commit 9c6d475
Show file tree
Hide file tree
Showing 13 changed files with 600 additions and 61 deletions.
5 changes: 5 additions & 0 deletions docs/book/v2/installation.md
Original file line number Diff line number Diff line change
@@ -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
73 changes: 73 additions & 0 deletions docs/book/v2/intro.md
Original file line number Diff line number Diff line change
@@ -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.
142 changes: 142 additions & 0 deletions docs/book/v2/middleware.md
Original file line number Diff line number Diff line change
@@ -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]);
}
}

```
108 changes: 108 additions & 0 deletions docs/book/v2/persistence.md
Original file line number Diff line number Diff line change
@@ -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.
Loading

0 comments on commit 9c6d475

Please sign in to comment.