Skip to content

marko/session-file

File-based session driver --- stores session data as files on disk with file locking for concurrent request safety. Sessions are stored as individual files (sess_{id}) in a configurable directory. Reads use shared locks and writes use exclusive locks to prevent corruption under concurrent requests. Garbage collection removes files older than the configured session lifetime. No external dependencies required.

Implements SessionHandlerInterface from marko/session.

Terminal window
composer require marko/session-file

This automatically installs marko/session.

Set the session file directory in your config:

config/session.php
return [
'driver' => 'file',
'path' => 'storage/sessions', // Relative to project root, or absolute path
];

The path directory is created automatically if it does not exist.

A relative path resolves against the project root (ProjectPaths->base), not the process working directory, so it lands in the same place under the CLI, PHP-FPM, CGI, and Apache mod_php. Absolute paths are used as-is.

Session files must never be web-reachable. If the resolved path is the project’s public/ directory or anything inside it, FileSessionHandler throws InsecureSessionPathException instead of starting.

Installing marko/session-file is all that is required to activate file-based sessions. The package registers FileSessionHandler as the SessionHandlerInterface implementation, binds SessionInterface to Session as a singleton, and adds SessionMiddleware globally --- no manual configuration is needed.

Use SessionInterface as usual --- the file driver handles storage:

use Marko\Session\Contracts\SessionInterface;
public function __construct(
private readonly SessionInterface $session,
) {}
public function handle(): void
{
$this->session->start();
$this->session->set('key', 'value');
$this->session->save();
}

Expired session files are cleaned up by the session:gc command:

Terminal window
marko session:gc

Implements all methods from SessionHandlerInterface. See marko/session for the full contract.

MethodDescription
open(string $path, string $name): boolPrepare the storage directory (created with mode 0700 if absent)
close(): boolClose the session (no-op for file driver)
read(string $id): string|falseRead session data using a shared lock (LOCK_SH)
write(string $id, string $data): boolWrite session data using an exclusive lock (LOCK_EX); sets file mode 0600; throws SessionWriteException on partial or failed write
destroy(string $id): boolDelete a session file from disk
gc(int $max_lifetime): int|falseRemove session files older than $max_lifetime seconds, returns count of deleted files
validateId(string $id): booltrue only when sess_{id} exists and was last modified within the configured lifetime; unknown and expired IDs get a fresh session
updateTimestamp(string $id, string $data): boolTouch an existing session file to the current time without rewriting it; never creates a file
ExceptionDescription
SessionWriteExceptionThrown when the session file cannot be truncated or the write is incomplete (e.g. disk full, permissions error)
InsecureSessionPathExceptionThrown on construction when the resolved session.path is inside the project’s public/ directory
  • Each session is stored as sess_{id} in the configured path.
  • The session directory is created with mode 0700 (owner-only access).
  • Each session file is created/written with mode 0600 (owner read/write only).
  • Reads acquire a shared lock (LOCK_SH) so multiple requests can read concurrently.
  • Writes acquire an exclusive lock (LOCK_EX) with truncate-then-write to prevent corruption. If the truncation or write fails (e.g. disk full or permission error), a SessionWriteException is thrown immediately --- no silent partial writes.
  • A session’s age is its file’s modification time. write() and updateTimestamp() stamp it with the current time from the injected PSR-20 clock, and validateId() and garbage collection compare against the same clock.
  • Garbage collection compares file modification times against the max lifetime and removes expired files.
  • validateId() clears PHP’s stat cache for the file first, so a long-running worker (RoadRunner) never sees a stale modification time.