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

Commit

Permalink
Merge branch 'feature/session-identifier' into develop
Browse files Browse the repository at this point in the history
Close #27
  • Loading branch information
weierophinney committed Sep 12, 2018
2 parents 6156406 + cd21c1c commit 16c5266
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 4 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ All notable changes to this project will be documented in this file, in reverse

### Added

- Nothing.
- [#27](https://github.com/zendframework/zend-expressive-session/pull/27) adds a new interface, `Zend\Expressive\Session\SessionIdentifierAwareInterface`.
`SessionInterface` implementations should also implement this interface, and
persistence implementations should only create and consume session
implementations that implement it. The interface defines a single method,
`getId()`, representing the identifier of a discovered session. This allows
the identifier to be associated with its session data, ensuring that when
concurrent requests are made, persistence operates on the correct identifier.

### Changed

Expand Down
39 changes: 39 additions & 0 deletions docs/book/persistence.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,42 @@ 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
[zend-expressive-session-ext](https://github.com/zendframework/zend-expressive-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 zend-expressive-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. As such, version 1.1.0 introduces a new
interface for `Zend\Expressive\Session\SessionInterface` implementations to use:
`Zend\Expressive\Session\SessionIdentifierAwareInterface`. This interface
defines a single method, `getId()`; 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.

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.
28 changes: 28 additions & 0 deletions docs/book/session.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,34 @@ 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`:

```php
namespace Zend\Expressive\Session;

interface SessionIdentifierAwareInterface
{
/**
* Retrieve the session identifier.
*
* This feature was added in 1.1.0 to allow the session persistence to be
* stateless. Previously, persistence implementations had to store the
* session identifier between calls to initializeSessionFromRequest() and
* persistSession(). When SessionInterface implementations also implement
* this method, the persistence implementation no longer needs to store it.
*
* This method will become a part of the SessionInterface in 2.0.0.
*
* @since 1.1.0
*/
public function getId() : string;
}
```

`Zend\Expressive\Session\Session` and `Zend\Expressive\Session\LazySession` both
implement this interface. `Session` accepts an optional identifier to its
constructor.

## Usage

Session containers will typically be passed to your middleware using the
Expand Down
15 changes: 14 additions & 1 deletion src/LazySession.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* 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
final class LazySession implements SessionInterface, SessionIdentifierAwareInterface
{
/**
* @var SessionPersistenceInterface
Expand Down Expand Up @@ -100,6 +100,19 @@ public function hasChanged() : bool
return $proxy->hasChanged();
}

/**
* {@inheritDoc}
*
* @since 1.1.0
*/
public function getId() : string
{
$proxiedSession = $this->getProxiedSession();
return $proxiedSession instanceof SessionIdentifierAwareInterface
? $proxiedSession->getId()
: '';
}

private function getProxiedSession() : SessionInterface
{
if ($this->proxiedSession) {
Expand Down
28 changes: 26 additions & 2 deletions src/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Zend\Expressive\Session;

class Session implements SessionInterface
class Session implements SessionInterface, SessionIdentifierAwareInterface
{
/**
* Current data within the session.
Expand All @@ -18,6 +18,19 @@ class Session implements SessionInterface
*/
private $data;

/**
* The session identifier, if any.
*
* This is present in the session to allow the session persistence
* implementation to be stateless. When present here, we can query for it
* when it is time to persist the session, instead of relying on state in
* the persistence instance (which may be shared between multiple
* requests).
*
* @var string
*/
private $id;

/**
* @var bool
*/
Expand All @@ -30,9 +43,10 @@ class Session implements SessionInterface
*/
private $originalData;

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

/**
Expand Down Expand Up @@ -109,4 +123,14 @@ public function isRegenerated() : bool
{
return $this->isRegenerated;
}

/**
* {@inheritDoc}
*
* @since 1.1.0
*/
public function getId() : string
{
return $this->id;
}
}
26 changes: 26 additions & 0 deletions src/SessionIdentifierAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/**
* @see https://github.com/zendframework/zend-expressive-session for the canonical source repository
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
* @license https://github.com/zendframework/zend-expressive-session/blob/master/LICENSE.md New BSD License
*/

namespace Zend\Expressive\Session;

interface SessionIdentifierAwareInterface
{
/**
* Retrieve the session identifier.
*
* This feature was added in 1.1.0 to allow the session persistence to be
* stateless. Previously, persistence implementations had to store the
* session identifier between calls to initializeSessionFromRequest() and
* persistSession(). When SessionInterface implementations also implement
* this method, the persistence implementation no longer needs to store it.
*
* This method will become a part of the SessionInterface in 2.0.0.
*
* @since 1.1.0
*/
public function getId() : string;
}
22 changes: 22 additions & 0 deletions test/LazySessionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Prophecy\Argument;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Expressive\Session\LazySession;
use Zend\Expressive\Session\SessionIdentifierAwareInterface;
use Zend\Expressive\Session\SessionInterface;
use Zend\Expressive\Session\SessionPersistenceInterface;

Expand Down Expand Up @@ -149,4 +150,25 @@ public function testHasChangedReturnsTrueIfProxyHasBeenRegenerated()
$this->proxy->hasChanged()->shouldNotBeCalled();
$this->assertTrue($this->session->hasChanged());
}

public function testGetIdReturnsEmptyStringIfProxyDoesNotImplementIdentifierAwareInterface()
{
$this->assertProxyCreated($this->persistence, $this->request);
$this->initializeProxy();
$this->assertSame('', $this->session->getId());
}

public function testGetIdReturnsValueFromProxyIfItImplementsIdentiferAwareInterface()
{
$proxy = $this->prophesize(SessionInterface::class);
$proxy->willImplement(SessionIdentifierAwareInterface::class);
$this->persistence
->initializeSessionFromRequest(Argument::that([$this->request, 'reveal']))
->will([$proxy, 'reveal']);
$proxy->getId()->willReturn('abcd1234');

$session = new LazySession($this->persistence->reveal(), $this->request->reveal());

$this->assertSame('abcd1234', $session->getId());
}
}
19 changes: 19 additions & 0 deletions test/SessionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use PHPUnit\Framework\TestCase;
use Zend\Expressive\Session\Session;
use Zend\Expressive\Session\SessionIdentifierAwareInterface;
use Zend\Expressive\Session\SessionInterface;

class SessionTest extends TestCase
Expand Down Expand Up @@ -123,4 +124,22 @@ public function testSetEnsuresDataIsJsonSerializable($data, $expected)
$this->assertNotSame($data, $session->get('foo'));
$this->assertSame($expected, $session->get('foo'));
}

public function testImplementsSessionIdentifierAwareInterface()
{
$session = new Session([]);
$this->assertInstanceOf(SessionIdentifierAwareInterface::class, $session);
}

public function testGetIdReturnsEmptyStringIfNoIdentifierProvidedToConstructor()
{
$session = new Session([]);
$this->assertSame('', $session->getId());
}

public function testGetIdReturnsValueProvidedToConstructor()
{
$session = new Session([], '1234abcd');
$this->assertSame('1234abcd', $session->getId());
}
}

0 comments on commit 16c5266

Please sign in to comment.