Select exactly one. Calling a second driver method switches to that driver and replaces the previous driver’s parameters.
| Driver | Storage | Multiple servers | Locks per session | Garbage collection |
|---|---|---|---|---|
file |
Files on disk | No, unless the directory is shared | Yes | Deletes files by modification time |
mysql |
Table via raw PDO | Yes | No | Deletes rows by last_activity |
model |
Table via laika-model | Yes | No | Deletes rows by last_activity |
redis |
Redis keys with a TTL | Yes | No | None needed, keys expire |
memcached |
Memcached items with a TTL | Yes | No | None needed, items expire |
“Locks per session” matters when one user sends several requests at once, such as parallel AJAX calls. See Locking and Concurrency.
Every driver implements validateId() and updateTimestamp(). As a result, use_strict_mode rejects session IDs the server never issued, and a user who only reads the session is not logged out when their session goes unwritten for a while.
SessionConfig::file([
'path' => '/var/www/app/storage/sessions', // optional
'prefix' => 'LK', // optional, default 'LK'
]);
| Param | Default | Notes |
|---|---|---|
path |
session_save_path(), then the system temp directory |
Must already exist. The driver throws SessionHandlerException rather than creating it. |
prefix |
LK |
Uppercased. Files are named <PREFIX>_<session id>. |
How it behaves:
0600.[A-Za-z0-9,-] before they touch the filesystem. Anything else is treated as a missing session.<PREFIX>_* files older than gc_maxlifetime. It never touches files with another prefix.Give the driver its own directory in production. Deployment explains why the system default often isn’t a good choice.
Talks to PDO directly, so it needs neither laika-model nor the framework. Pass in a PDO instance you have already connected; the package never handles credentials.
$pdo = new PDO('mysql:host=127.0.0.1;dbname=app;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
SessionConfig::mysql($pdo, [
'table' => 'sessions', // optional, default 'sessions'
'install' => false, // optional, default false
]);
| Param | Default | Notes |
|---|---|---|
table |
sessions |
Must match [A-Za-z0-9_]+, because it is interpolated into the SQL. All other values are bound. |
install |
false |
true runs CREATE TABLE IF NOT EXISTS once per process, before the session starts. |
Writes use a single INSERT ... ON DUPLICATE KEY UPDATE, so two first writes for the same ID can’t collide. That statement is MySQL/MariaDB syntax, so this driver doesn’t work on PostgreSQL or SQLite.
Goes through Laika\Session\Model\SessionModel on a laika-model connection. Configure the connection itself in laika-model.
SessionConfig::model([
'connection' => 'default', // optional, laika-model's default connection
'install' => false, // optional, default false
]);
| Param | Default | Notes |
|---|---|---|
connection |
laika-model’s default | The connection must already be registered. The framework’s Init::model() registers it for you. |
install |
false |
true runs SessionSchema against the same connection, once per process. |
SessionConfig::model() throws SessionHandlerException when laika-model isn’t installed. The mysql driver is the alternative when you don’t have laika-model.
The mysql and model drivers share one layout, so you can switch between them without a migration:
CREATE TABLE IF NOT EXISTS `sessions` (
`id` VARCHAR(128) NOT NULL,
`data` BLOB NULL,
`last_activity` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_last_activity` (`last_activity`)
);
Leave install off in production. Creating the table at request time adds a query to the first request of every PHP process, and it needs DDL privileges your runtime database user shouldn’t have. Create the table once, from a migration or by hand, with the SQL above. With laika-model you can also run (new \Laika\Session\Schema\SessionSchema('default'))->up();.
last_activity is written using PHP’s clock (date()), not the database’s. Keep date.timezone the same on every server that shares the table. Otherwise garbage collection compares timestamps from different zones.
Pass a client that is already connected and authenticated:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('secret'); // if needed
SessionConfig::redis($redis, [
'prefix' => 'LK', // optional, default 'LK'
'lifetime' => 1440, // optional, seconds
]);
| Param | Default | Notes |
|---|---|---|
prefix |
LK |
Uppercased. Keys are <PREFIX>_<session id>. |
lifetime |
gc_maxlifetime from session options |
The TTL is set on every write and renewed on every read-only request. |
A RedisException during a request doesn’t crash the page. A failed read returns an empty session, and a failed write returns false, which PHP reports as a warning. So an outage looks like users being logged out, not like an error page.
Pass a client with at least one server added:
$memcached = new Memcached();
$memcached->addServer('127.0.0.1', 11211);
SessionConfig::memcached($memcached, [
'prefix' => 'LK', // optional, default 'LK'
'lifetime' => 1440, // optional, seconds
]);
Parameters are the same as for Redis. Some things to know:
addServer() doesn’t connect, so an unreachable server shows up as an empty session, not as an exception.touch, the TTL is renewed with it. Otherwise the driver falls back to a full write.Only the file driver locks. With the other four, two requests on the same session that overlap each read the session, change it, and write it back. The second write wins, and changes made only by the first request are lost.
In practice:
session_write_close().See Deployment for how this plays out under PHP-FPM.