CoolCMS API

TieredTtlSessionHandler extends Handler
in package

FinalYes

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

Properties

$useFallback

True once this request gave up on Redis; every further call goes to the files.

private bool $useFallback = false

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
bool

gc()

public gc(int $maxlifetime) : bool
Parameters
$maxlifetime : int
Attributes
#[ReturnTypeWillChange]
Return values
bool

isDisposableCrawlerSession()

Crawler without a session cookie: its session is never stored.

public isDisposableCrawlerSession() : bool
Return values
bool

isUsingFallback()

True when this request runs on file sessions because Redis failed.

public isUsingFallback() : bool
Return values
bool

lifetimeFor()

Full lifetime for a session the browser already knows, the short one otherwise.

public lifetimeFor(string $sessionId) : int
Parameters
$sessionId : string
Return values
int

read()

public read(string $session_id) : string
Parameters
$session_id : string
Attributes
#[ReturnTypeWillChange]
Return values
string

write()

public write(string $session_id, string $session_data) : bool
Parameters
$session_id : string
$session_data : string
Attributes
#[ReturnTypeWillChange]
Return values
bool

clearOutageMarker()

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|null

fileFor()

private fileFor(string $sessionId) : string|null
Parameters
$sessionId : string
Return values
string|null

fileRead()

private fileRead(string $sessionId) : string
Parameters
$sessionId : string
Return values
string

fileWrite()

private fileWrite(string $sessionId, string $data) : bool
Parameters
$sessionId : string
$data : string
Return values
bool

switchToFallback()

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
Return values
string|null
On this page

Search results