Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Support persistent sessions #28

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 12 additions & 25 deletions CHANGELOG.md
Expand Up @@ -2,37 +2,24 @@

All notable changes to this project will be documented in this file, in reverse chronological order by release.

## 1.2.0 - TBD
## 1.2.0 - 2018-10-30

### Added

- Nothing.
- [#28](https://github.com/zendframework/zend-expressive-session/pull/28) adds a new interface, `SessionCookiePersistenceInterface`, defining:
- the constant `SESSION_LIFETIME_KEY`
- the method `persistSessionFor(int $duration) : void`, for developers to hint
to the persistence engine how long a session should last
- the method `getSessionLifetime() : int`, for persistence engines to
determine if a specific session duration was requested

### Changed

- Nothing.

### Deprecated

- Nothing.

### Removed

- Nothing.

### Fixed

- Nothing.

## 1.1.1 - TBD

### Added

- Nothing.

### Changed

- Nothing.
- [#28](https://github.com/zendframework/zend-expressive-session/pull/28) updates both `Session` and `LazySession` to implement the new
`SessionCookiePersistenceInterface. If a `SessionCookiePersistenceInterface::SESSION_LIFETIME_KEY`
is present in the initial session data provided to a `Session` instance, this
value will be used to indicate the requested session duration; otherwise, zero
is used, indicating the session should end when the browser is closed.

### Deprecated

Expand Down
30 changes: 30 additions & 0 deletions docs/book/persistence.md
Expand Up @@ -83,3 +83,33 @@ Considering that persistence implementations also _create_ the session instance,
we recommend that implementations only create instances that implement the
`SessionIdentifierAwareInterface` going forward in order to make themselves
async compatible.

## 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.
93 changes: 90 additions & 3 deletions docs/book/session.md
Expand Up @@ -79,7 +79,7 @@ interface SessionInterface
The default implementation, and the one you'll most likely interact with, is
`Zend\Expressive\Session\Session`.

Additionally, since version 1.1.0, we provide `Zend\Expressive\Session\SessionIdentifierAwareInterface`:
Since version 1.1.0, we provide `Zend\Expressive\Session\SessionIdentifierAwareInterface`:

```php
namespace Zend\Expressive\Session;
Expand All @@ -103,9 +103,78 @@ interface SessionIdentifierAwareInterface
}
```

Since version 1.2.0, we provide `Zend\Expressive\Session\SessionCookiePersistenceInterface`:

```php
namespace Zend\Expressive\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;
}
```

`Zend\Expressive\Session\Session` and `Zend\Expressive\Session\LazySession` both
implement this interface. `Session` accepts an optional identifier to its
constructor.
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

Expand Down Expand Up @@ -146,6 +215,24 @@ If none of the data is relevant, `clear()` the session:
$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
Expand Down
31 changes: 30 additions & 1 deletion src/LazySession.php
Expand Up @@ -19,7 +19,10 @@
* method only on access to any of the various session data methods; otherwise,
* the session will not be accessed, and, in most cases, started.
*/
final class LazySession implements SessionInterface, SessionIdentifierAwareInterface
final class LazySession implements
SessionCookiePersistenceInterface,
SessionIdentifierAwareInterface,
SessionInterface
{
/**
* @var SessionPersistenceInterface
Expand Down Expand Up @@ -113,6 +116,32 @@ public function getId() : string
: '';
}

/**
* {@inheritDoc}
*
* @since 1.2.0
*/
public function persistSessionFor(int $duration) : void
{
$proxiedSession = $this->getProxiedSession();
if ($proxiedSession instanceof SessionCookiePersistenceInterface) {
$proxiedSession->persistSessionFor($duration);
}
}

/**
* {@inheritDoc}
*
* @since 1.2.0
*/
public function getSessionLifetime() : int
{
$proxiedSession = $this->getProxiedSession();
return $proxiedSession instanceof SessionCookiePersistenceInterface
? $proxiedSession->getSessionLifetime()
: 0;
}

private function getProxiedSession() : SessionInterface
{
if ($this->proxiedSession) {
Expand Down
37 changes: 36 additions & 1 deletion src/Session.php
Expand Up @@ -9,7 +9,10 @@

namespace Zend\Expressive\Session;

class Session implements SessionInterface, SessionIdentifierAwareInterface
class Session implements
SessionCookiePersistenceInterface,
SessionIdentifierAwareInterface,
SessionInterface
{
/**
* Current data within the session.
Expand Down Expand Up @@ -43,10 +46,21 @@ class Session implements SessionInterface, SessionIdentifierAwareInterface
*/
private $originalData;

/**
* Lifetime of the session cookie.
*
* @var int
*/
private $sessionLifetime = 0;

public function __construct(array $data, string $id = '')
{
$this->data = $this->originalData = $data;
$this->id = $id;

if (isset($data[SessionCookiePersistenceInterface::SESSION_LIFETIME_KEY])) {
$this->sessionLifetime = $data[SessionCookiePersistenceInterface::SESSION_LIFETIME_KEY];
}
}

/**
Expand Down Expand Up @@ -133,4 +147,25 @@ public function getId() : string
{
return $this->id;
}

/**
* {@inheritDoc}
*
* @since 1.2.0
*/
public function persistSessionFor(int $duration) : void
{
$this->sessionLifetime = $duration;
$this->set(SessionCookiePersistenceInterface::SESSION_LIFETIME_KEY, $duration);
}

/**
* {@inheritDoc}
*
* @since 1.2.0
*/
public function getSessionLifetime() : int
{
return $this->sessionLifetime;
}
}
69 changes: 69 additions & 0 deletions src/SessionCookiePersistenceInterface.php
@@ -0,0 +1,69 @@
<?php
/**
* @see https://github.com/zendframework/zend-expressive-session for the canonical source repository
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-expressive-session/blob/master/LICENSE.md New BSD License
*/

namespace Zend\Expressive\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;
}