TieredTtlSessionHandler
extends Handler
in package
Redis session handler with two lifetimes and a file fallback.
Every visitor gets a session (forms carry a CSRF token, lists remember their style), so every cookie-less request, which is every crawler request, would leave a key living the full 31 days. A session written by a request that did not send its cookie is new and gets a short lifetime. Once the browser comes back with the cookie, the next write gives it the full lifetime. A crawler never returns the cookie, so its sessions expire quickly. Login regenerates the session id, so the login request itself writes with the short lifetime; the redirect that follows carries the new cookie and extends it.
IMPORTANT: the session starts while the container is being created, so an unreachable Redis would
turn every page, front and admin, into a 500. The first Redis error of a request therefore
switches that request to plain files in $fallbackDir (temp/sessions). The next request (a new
process) tries Redis again, so the site returns to Redis by itself once it is back. Sessions
written during the outage stay in files and are lost on return, so visitors log in again. The
outage is logged once per self::LOG_INTERVAL, not once per request.
IMPORTANT: a known crawler (or a client without User-Agent) that did not send a session cookie
gets no stored session at all: write() returns true and touches neither Redis nor the fallback
directory. The page still renders (the session lives in memory for the request, CSRF tokens
included), it is just not kept. A request that sent a cookie is never treated this way, so a
logged-in user with an odd User-Agent keeps working. Switch off with
sessionTtl.skipCrawlerSessions: false.
Table of Contents
Constants
- LOG_INTERVAL : mixed = 3600
- Seconds between two "Redis down" log lines (a marker file carries the state across requests).
- MARKER : mixed = '.redis-down'
Properties
- $fallbackDir : string|null
- $newSessionTtl : int
- $skipCrawlerSessions : bool
- $useFallback : bool
- True once this request gave up on Redis; every further call goes to the files.
- $userAgentProvider : Closure|null
Methods
- __construct() : mixed
- destroy() : bool
- gc() : bool
- isDisposableCrawlerSession() : bool
- Crawler without a session cookie: its session is never stored.
- isUsingFallback() : bool
- True when this request runs on file sessions because Redis failed.
- lifetimeFor() : int
- Full lifetime for a session the browser already knows, the short one otherwise.
- read() : string
- write() : bool
- clearOutageMarker() : void
- Redis answered again: drop the marker so the next outage is logged straight away.
- cookieSessionId() : string|null
- Session id the browser sent with this request, null when it sent none.
- fileFor() : string|null
- fileRead() : string
- fileWrite() : bool
- switchToFallback() : void
- Gives up on Redis for the rest of the request; without a fallback dir it rethrows (fatal).
- userAgent() : string|null
- Lightweight on purpose: the handler runs while the container is built, so there is no `Http\Request` yet.
Constants
LOG_INTERVAL
Seconds between two "Redis down" log lines (a marker file carries the state across requests).
private
mixed
LOG_INTERVAL
= 3600
MARKER
private
mixed
MARKER
= '.redis-down'
Properties
$fallbackDir read-only
private
string|null
$fallbackDir
= null
$newSessionTtl read-only
private
int
$newSessionTtl
= 7200
$skipCrawlerSessions read-only
private
bool
$skipCrawlerSessions
= false
$useFallback
True once this request gave up on Redis; every further call goes to the files.
private
bool
$useFallback
= false
$userAgentProvider read-only
private
Closure|null
$userAgentProvider
= null
Methods
__construct()
public
__construct(ClientInterface $client[, array<string, mixed> $options = [] ][, int $newSessionTtl = 7200 ][, string|null $fallbackDir = null ][, bool $skipCrawlerSessions = false ][, Closure|null $userAgentProvider = null ]) : mixed
Parameters
- $client : ClientInterface
- $options : array<string, mixed> = []
-
Predis handler options (
gc_maxlifetime= full lifetime) - $newSessionTtl : int = 7200
-
seconds a session lives until its cookie is seen again
- $fallbackDir : string|null = null
-
directory for file sessions while Redis is down; null = no fallback
- $skipCrawlerSessions : bool = false
-
do not persist cookie-less sessions of crawlers
- $userAgentProvider : Closure|null = null
-
returns the request User-Agent; null =
$_SERVER['HTTP_USER_AGENT'](seam for tests)
destroy()
public
destroy(string $session_id) : bool
Parameters
- $session_id : string
Attributes
- #[ReturnTypeWillChange]
Return values
boolgc()
public
gc(int $maxlifetime) : bool
Parameters
- $maxlifetime : int
Attributes
- #[ReturnTypeWillChange]
Return values
boolisDisposableCrawlerSession()
Crawler without a session cookie: its session is never stored.
public
isDisposableCrawlerSession() : bool
Return values
boolisUsingFallback()
True when this request runs on file sessions because Redis failed.
public
isUsingFallback() : bool
Return values
boollifetimeFor()
Full lifetime for a session the browser already knows, the short one otherwise.
public
lifetimeFor(string $sessionId) : int
Parameters
- $sessionId : string
Return values
intread()
public
read(string $session_id) : string
Parameters
- $session_id : string
Attributes
- #[ReturnTypeWillChange]
Return values
stringwrite()
public
write(string $session_id, string $session_data) : bool
Parameters
- $session_id : string
- $session_data : string
Attributes
- #[ReturnTypeWillChange]
Return values
boolclearOutageMarker()
Redis answered again: drop the marker so the next outage is logged straight away.
private
clearOutageMarker() : void
cookieSessionId()
Session id the browser sent with this request, null when it sent none.
private
cookieSessionId() : string|null
Return values
string|nullfileFor()
private
fileFor(string $sessionId) : string|null
Parameters
- $sessionId : string
Return values
string|nullfileRead()
private
fileRead(string $sessionId) : string
Parameters
- $sessionId : string
Return values
stringfileWrite()
private
fileWrite(string $sessionId, string $data) : bool
Parameters
- $sessionId : string
- $data : string
Return values
boolswitchToFallback()
Gives up on Redis for the rest of the request; without a fallback dir it rethrows (fatal).
private
switchToFallback(PredisException $e) : void
Parameters
- $e : PredisException
userAgent()
Lightweight on purpose: the handler runs while the container is built, so there is no `Http\Request` yet.
private
userAgent() : string|null