-
Notifications
You must be signed in to change notification settings - Fork 0
Session
Razy provides a framework-native session system that is fully decoupled from PHP's built-in session_*() functions. It supports multiple storage backends, flash data, probabilistic garbage collection, and middleware integration.
- Overview
- Quick Start
- SessionConfig
- Session API
- Session Drivers
- Middleware Integration
- Garbage Collection
- API Reference
SessionInterface (contract)
└── Session (implementation)
├── holds SessionConfig (immutable value object)
└── delegates to SessionDriverInterface (contract)
├── FileDriver — filesystem (production)
├── DatabaseDriver — PDO-backed (production)
├── ArrayDriver — in-memory (testing)
└── NullDriver — discard (stateless/API)
Key design decisions:
-
No dependency on PHP's native
$_SESSIONorsession_start() - Strategy pattern for swappable storage backends
- Two-generation flash data — set this request, available next request, auto-deleted after
-
40-character hex session IDs via
bin2hex(random_bytes(20))
use Razy\Session\Session;
use Razy\Session\SessionConfig;
use Razy\Session\Driver\FileDriver;
// Create session with file storage
$config = new SessionConfig(
name: 'MY_APP_SESSION',
lifetime: 3600,
secure: true,
sameSite: 'Strict',
);
$driver = new FileDriver('/tmp/sessions');
$session = new Session($driver, $config);
// Start, use, save
$session->start();
$session->set('user_id', 42);
$session->flash('success', 'Welcome back!');
$session->save();Immutable value object for session cookie and GC configuration.
use Razy\Session\SessionConfig;
$config = new SessionConfig(
name: 'RAZY_SESSION', // cookie name (default)
lifetime: 0, // 0 = browser session
path: '/', // cookie path
domain: '', // cookie domain
secure: false, // HTTPS only
httpOnly: true, // no JavaScript access
sameSite: 'Lax', // None | Lax | Strict
gcMaxLifetime: 1440, // GC: delete sessions older than N seconds
gcProbability: 1, // GC: probability numerator
gcDivisor: 100, // GC: probability denominator (1/100 = 1%)
);// Create a new config with overrides (original unchanged)
$apiConfig = $config->with([
'name' => 'API_SESSION',
'lifetime' => 7200,
'sameSite' => 'None',
'secure' => true,
]);// Start the session (loads data, runs GC probabilistically)
$session->start();
// Check if started
$session->isStarted(); // bool
// Save and close (writes data, ages flash data)
$session->save();
// Destroy session (deletes from storage, clears data)
$session->destroy();// Get current session ID
$id = $session->getId(); // 40-char hex string
// Set a specific ID (e.g., from cookie)
$session->setId($cookieSessionId);
// Regenerate ID (prevents session fixation)
$session->regenerate(); // keep old data
$session->regenerate(destroyOld: true); // delete old session// Set values
$session->set('user_id', 42);
$session->set('preferences', ['theme' => 'dark']);
// Get values
$userId = $session->get('user_id'); // 42
$theme = $session->get('missing', 'default'); // 'default'
// Check existence
$session->has('user_id'); // true
$session->has('missing'); // false
// Remove a value
$session->remove('user_id');
// Get all data
$all = $session->all(); // ['preferences' => [...]]
// Clear all data
$session->clear();Flash data is available for one subsequent request only, then auto-deleted.
// Set flash data (available on the NEXT request)
$session->flash('success', 'Profile updated!');
$session->flash('errors', ['name' => 'Name is required']);
// Read flash data (on the next request)
$message = $session->getFlash('success'); // 'Profile updated!'
$errors = $session->getFlash('errors', []); // array or default
// Check if flash data exists
$session->hasFlash('success'); // true
// Keep all flash data for one more request
$session->reflash();
// Keep specific flash keys
$session->keep(['success']);Flash lifecycle:
Request 1: flash('msg', 'Hi') → stored in _flash.new
Request 2: getFlash('msg') → 'Hi' (moved to _flash.old)
Request 3: getFlash('msg') → null (deleted from _flash.old)
Filesystem-based storage with atomic writes.
use Razy\Session\Driver\FileDriver;
$driver = new FileDriver(
savePath: '/var/lib/razy/sessions',
prefix: 'sess_', // file prefix (default)
);
// Files created as: /var/lib/razy/sessions/sess_abc123def456...Key features:
-
Atomic writes: writes to temp file, then
rename()— prevents corruption -
Auto-creates the save path directory (mode
0700) -
GC scans directory, deletes files with matching prefix older than
maxLifetime
$driver->getSavePath(); // '/var/lib/razy/sessions'
$driver->getPrefix(); // 'sess_'
$driver->getLastGcCount(); // number of files deleted in last GCPDO-backed session storage. Works with MySQL, PostgreSQL, and SQLite.
use Razy\Session\Driver\DatabaseDriver;
$driver = new DatabaseDriver(
pdo: $pdo,
table: 'sessions', // default table name
);
// Create the sessions table (if it doesn't exist)
$driver->createTable();Required table schema:
CREATE TABLE sessions (
id VARCHAR(128) NOT NULL PRIMARY KEY,
data TEXT NOT NULL DEFAULT '',
last_activity INTEGER NOT NULL DEFAULT 0
);Key features:
- Upsert pattern: UPDATE first, INSERT if no rows affected — driver-agnostic
-
Serialization: uses PHP
serialize()/unserialize() -
GC:
DELETE WHERE last_activity < cutoff - All operations wrapped in
try/catch— failures return empty/false gracefully
$driver->getTable(); // 'sessions'
$driver->getPdo(); // PDO instance
$driver->getLastGcCount(); // rows deleted in last GCIn-memory storage for testing. No I/O, no side effects.
use Razy\Session\Driver\ArrayDriver;
$driver = new ArrayDriver();
// Test helpers
$driver->isOpened(); // bool
$driver->getSessions(); // raw session store
$driver->count(); // number of sessions
// Inject timestamp for GC testing
$driver->setSessionTime('session_id', time() - 7200);
$gcCount = $driver->gc(3600); // clears sessions older than 1 hour
$driver->getLastGcCount(); // number deletedDiscards all data — for stateless contexts like APIs or CLI.
use Razy\Session\Driver\NullDriver;
$driver = new NullDriver();
// All operations are no-ops: read() returns [], write() returns true, etc.Implement SessionDriverInterface:
use Razy\Contract\SessionDriverInterface;
class RedisDriver implements SessionDriverInterface
{
public function __construct(
private readonly \Redis $redis,
private readonly int $ttl = 3600,
) {}
public function open(): bool
{
return $this->redis->isConnected();
}
public function close(): bool
{
return true;
}
public function read(string $id): array
{
$data = $this->redis->get("session:{$id}");
return $data ? unserialize($data) : [];
}
public function write(string $id, array $data): bool
{
return $this->redis->setex("session:{$id}", $this->ttl, serialize($data));
}
public function destroy(string $id): bool
{
return (bool) $this->redis->del("session:{$id}");
}
public function gc(int $maxLifetime): int
{
return 0; // Redis TTL handles expiration
}
}Use SessionMiddleware to auto-start/save sessions per request:
use Razy\Session\SessionMiddleware;
use Razy\Distributor\MiddlewarePipeline;
$middleware = new SessionMiddleware($session);
$pipeline = new MiddlewarePipeline();
$pipeline->pipe($middleware);
$result = $pipeline->process($context, function (array $ctx) use ($session) {
// Session is started and available here
$userId = $session->get('user_id');
// Flash data from previous request
$message = $session->getFlash('success');
return $controller->handle($ctx);
});
// Session is automatically saved after the handler completes (in finally block)See Middleware for more details on the middleware system.
GC runs probabilistically on start():
// With default config: gcProbability=1, gcDivisor=100
// → 1% chance of GC on each session start
// High-traffic: lower probability
$config = new SessionConfig(gcProbability: 1, gcDivisor: 1000); // 0.1%
// Development: always GC
$config = new SessionConfig(gcProbability: 1, gcDivisor: 1); // 100%
// Disable GC (handle externally via cron)
$config = new SessionConfig(gcDivisor: 0);GC deletes sessions older than gcMaxLifetime seconds.
| Property | Type | Default | Description |
|---|---|---|---|
name |
string |
'RAZY_SESSION' |
Cookie name |
lifetime |
int |
0 |
Cookie lifetime (0 = browser session) |
path |
string |
'/' |
Cookie path |
domain |
string |
'' |
Cookie domain |
secure |
bool |
false |
HTTPS only |
httpOnly |
bool |
true |
No JavaScript access |
sameSite |
string |
'Lax' |
SameSite policy |
gcMaxLifetime |
int |
1440 |
Session expiry (seconds) |
gcProbability |
int |
1 |
GC probability numerator |
gcDivisor |
int |
100 |
GC probability denominator |
| Method | Signature |
|---|---|
with |
(array $overrides): static |
| Category | Method | Signature |
|---|---|---|
| Lifecycle | start |
(): bool |
save |
(): void |
|
destroy |
(): void |
|
isStarted |
(): bool |
|
| ID | getId |
(): string |
setId |
(string $id): void |
|
regenerate |
(bool $destroyOld = false): bool |
|
| Data | get |
(string $key, mixed $default = null): mixed |
set |
(string $key, mixed $value): void |
|
has |
(string $key): bool |
|
remove |
(string $key): void |
|
all |
(): array |
|
clear |
(): void |
|
| Flash | flash |
(string $key, mixed $value): void |
getFlash |
(string $key, mixed $default = null): mixed |
|
hasFlash |
(string $key): bool |
|
reflash |
(): void |
|
keep |
(array $keys): void |
|
| Access | getConfig |
(): SessionConfig |
getDriver |
(): SessionDriverInterface |
| Method | Signature | Returns |
|---|---|---|
open |
(): bool |
Open session store |
close |
(): bool |
Close session store |
read |
(string $id): array |
Read session data |
write |
(string $id, array $data): bool |
Write session data |
destroy |
(string $id): bool |
Delete session |
gc |
(int $maxLifetime): int |
Garbage collect, return count |