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