From 8bf0b3db425e9384c3518019e66f78480dbd4aa0 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 12 Sep 2018 10:50:57 -0500 Subject: [PATCH] Use session identifier instead of memoizing it This patch uses the fact that `Zend\Expressive\Session\Session` now implements `Zend\Expressive\Session\SessionIdentifierAwareInterface`. When creating a session, it passes it the session identifier discovered when introspecting cookies, if any. When persistence is invoked, it pulls the identifier from the session. This approach should solve concurrency issues, and makes it more clear when a session cookie should be sent. --- src/PhpSessionPersistence.php | 64 +++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/src/PhpSessionPersistence.php b/src/PhpSessionPersistence.php index 7db2365..de8a872 100644 --- a/src/PhpSessionPersistence.php +++ b/src/PhpSessionPersistence.php @@ -45,9 +45,6 @@ */ class PhpSessionPersistence implements SessionPersistenceInterface { - /** @var string|null */ - private $cookie; - /** @var string */ private $cacheLimiter; @@ -91,45 +88,53 @@ public function __construct() public function initializeSessionFromRequest(ServerRequestInterface $request) : SessionInterface { $this->scriptFile = $request->getServerParams()['SCRIPT_FILENAME'] ?? __FILE__; - $this->cookie = FigRequestCookies::get($request, session_name())->getValue(); - $id = $this->cookie ?: $this->generateSessionId(); + $sessionId = FigRequestCookies::get($request, session_name())->getValue() ?? ''; + $id = $sessionId ?: $this->generateSessionId(); $this->startSession($id); - return new Session($_SESSION); + return new Session($_SESSION, $sessionId); } public function persistSession(SessionInterface $session, ResponseInterface $response) : ResponseInterface { - // Regenerate if the session is marked as regenerated - // Regenerate if there is no cookie id set but the session has changed (new session with data) + $id = $session->getId(); + + // Regenerate if: + // - the session is marked as regenerated + // - the id is empty, but the data has changed (new session) if ($session->isRegenerated() - || (! $this->cookie && $session->hasChanged()) + || ('' === $id && $session->hasChanged()) ) { - $this->regenerateSession(); + $id = $this->regenerateSession(); } $_SESSION = $session->toArray(); session_write_close(); - if ($this->cookie) { - $sessionCookie = SetCookie::create(session_name()) - ->withValue($this->cookie) - ->withPath(ini_get('session.cookie_path')); + // If we do not have an identifier at this point, it means a new + // session was created, but never written to. In that case, there's + // no reason to provide a cookie back to the user. + if ('' === $id) { + return $response; + } - if ($cookieLifetime = (int) ini_get('session.cookie_lifetime')) { - $sessionCookie = $sessionCookie->withExpires(time() + $cookieLifetime); - } + $sessionCookie = SetCookie::create(session_name()) + ->withValue($id) + ->withPath(ini_get('session.cookie_path')); - $response = FigResponseCookies::set($response, $sessionCookie); + if ($cookieLifetime = (int) ini_get('session.cookie_lifetime')) { + $sessionCookie = $sessionCookie->withExpires(time() + $cookieLifetime); + } - if (! $this->cacheLimiter || $this->responseAlreadyHasCacheHeaders($response)) { - return $response; - } + $response = FigResponseCookies::set($response, $sessionCookie); + + if (! $this->cacheLimiter || $this->responseAlreadyHasCacheHeaders($response)) { + return $response; + } - $cacheHeaders = $this->generateCacheHeaders(); - foreach ($cacheHeaders as $name => $value) { - if (false !== $value) { - $response = $response->withHeader($name, $value); - } + $cacheHeaders = $this->generateCacheHeaders(); + foreach ($cacheHeaders as $name => $value) { + if (false !== $value) { + $response = $response->withHeader($name, $value); } } @@ -154,13 +159,14 @@ private function startSession(string $id, array $options = []) : void * * @link http://php.net/manual/en/function.session-regenerate-id.php (Example #2) */ - private function regenerateSession() : void + private function regenerateSession() : string { session_write_close(); - $this->cookie = $this->generateSessionId(); - $this->startSession($this->cookie, [ + $id = $this->generateSessionId(); + $this->startSession($id, [ 'use_strict_mode' => false, ]); + return $id; } /**