Skip to content

Commit c015627

Browse files
UPDATE
1 parent b2fb9e0 commit c015627

File tree

7 files changed

+164
-16
lines changed

7 files changed

+164
-16
lines changed

Process/Concerns/SessionInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ public function all(): array;
6666

6767
/**
6868
* Destroy the session and clear stored data.
69+
* If a key is provided, only that key is removed.
70+
* @param string|null $key
6971
* @return void
7072
*/
71-
public function destroy(): void;
73+
public function destroy(?string $key = null): void;
7274
}

Process/Session.php

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,69 @@
99
/**
1010
* Native PHP session implementation for SessionInterface.
1111
*/
12+
/**
13+
* Native PHP session implementation for SessionInterface.
14+
* Supports optional configuration via configure() before start().
15+
*/
1216
final class Session implements SessionInterface
1317
{
18+
/** @var array<string,mixed> */
19+
private array $config = [];
20+
21+
/**
22+
* Configure session behavior (call before start()).
23+
* Supported keys: name, expire, path, cookie (array params for session_set_cookie_params), env
24+
* - path: when provided, ensures directory exists and sets session.save_path
25+
* - expire: accepts integer seconds
26+
*/
27+
public function configure(array $config = []): void
28+
{
29+
$this->config = $config + $this->config;
30+
}
31+
1432
/** @inheritDoc */
1533
public function start(): void
1634
{
17-
if (session_status() !== PHP_SESSION_ACTIVE) {
18-
@session_start();
35+
if (session_status() === PHP_SESSION_ACTIVE) {
36+
return;
37+
}
38+
39+
$cfg = $this->applyDefaults($this->config);
40+
41+
// Name
42+
if (!empty($cfg['name']) && is_string($cfg['name'])) {
43+
@session_name($cfg['name']);
1944
}
45+
46+
// Cookie params
47+
if (!empty($cfg['expire'])) {
48+
$lifetime = (int) $cfg['expire'];
49+
$cookie = $cfg['cookie'] ?? [
50+
'lifetime' => $lifetime,
51+
'path' => '/',
52+
'domain' => '',
53+
'secure' => false,
54+
'httponly' => true,
55+
'samesite' => 'Lax',
56+
];
57+
@session_set_cookie_params($cookie);
58+
@ini_set('session.gc_maxlifetime', (string) $lifetime);
59+
@ini_set('session.gc_probability', '1');
60+
@ini_set('session.gc_divisor', '100');
61+
}
62+
63+
// Path (create if missing)
64+
if (!empty($cfg['path'])) {
65+
$path = (string) $cfg['path'];
66+
if (!is_dir($path)) {
67+
@mkdir($path, 0777, true);
68+
}
69+
if (is_dir($path)) {
70+
@ini_set('session.save_path', $path);
71+
}
72+
}
73+
74+
@session_start();
2075
}
2176

2277
/** @inheritDoc */
@@ -65,11 +120,34 @@ public function all(): array
65120
}
66121

67122
/** @inheritDoc */
68-
public function destroy(): void
123+
public function destroy(?string $key = null): void
69124
{
125+
if ($key !== null) {
126+
unset($_SESSION[$key]);
127+
return;
128+
}
129+
70130
if (session_status() === PHP_SESSION_ACTIVE) {
71131
@session_unset();
72132
@session_destroy();
73133
}
74134
}
135+
136+
/**
137+
* Apply default values similar to legacy Configuration::setSession
138+
* - expire: default 10 days (in seconds)
139+
* - path: defaults to storage_path('session') if available
140+
*/
141+
private function applyDefaults(array $config): array
142+
{
143+
$defaults = [
144+
'name' => null,
145+
'expire' => 10 * 24 * 60 * 60,
146+
'path' => function_exists('storage_path') ? storage_path('session') : null,
147+
'cookie' => null,
148+
'env' => function_exists('env') ? env('APP_ENV', 'production') : 'production',
149+
];
150+
151+
return $config + $defaults;
152+
}
75153
}

Process/Session/Handlers/DatabaseSessionHandler.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,28 @@
99

1010
/**
1111
* PDO-backed session handler.
12-
* Creates table structure if missing (simple schema).
12+
*
13+
* Responsibilities:
14+
* - Persist session data in an SQL table with columns: id, payload, last_activity
15+
* - Auto-create the table if it does not exist (simple schema)
16+
* - Enforce TTL by filtering reads and running GC
1317
*/
1418
final class DatabaseSessionHandler implements SessionHandlerInterface
1519
{
20+
/** @var PDO PDO connection used for session persistence */
1621
private PDO $pdo;
22+
23+
/** @var string Database table name storing sessions */
1724
private string $table;
25+
26+
/** @var int Session TTL in seconds used for reads and GC */
1827
private int $ttl;
1928

29+
/**
30+
* @param PDO $pdo Active PDO connection
31+
* @param string $table Table name to use (default: sessions)
32+
* @param int $ttl Time-to-live in seconds (default: 1440)
33+
*/
2034
public function __construct(PDO $pdo, string $table = 'sessions', int $ttl = 1440)
2135
{
2236
$this->pdo = $pdo;
@@ -25,6 +39,11 @@ public function __construct(PDO $pdo, string $table = 'sessions', int $ttl = 144
2539
$this->createTableIfMissing();
2640
}
2741

42+
/**
43+
* Create the sessions table if it does not exist.
44+
* Columns: id (PK), payload (BLOB/TEXT), last_activity (INT timestamp)
45+
* @return void
46+
*/
2847
private function createTableIfMissing(): void
2948
{
3049
$sql = "CREATE TABLE IF NOT EXISTS {$this->table} (
@@ -35,9 +54,13 @@ private function createTableIfMissing(): void
3554
$this->pdo->exec($sql);
3655
}
3756

57+
/** @inheritDoc */
3858
public function open($savePath, $sessionName): bool { return true; }
59+
60+
/** @inheritDoc */
3961
public function close(): bool { return true; }
4062

63+
/** @inheritDoc */
4164
public function read($id): string|false
4265
{
4366
$stmt = $this->pdo->prepare("SELECT payload FROM {$this->table} WHERE id = :id AND last_activity > :exp");
@@ -49,6 +72,7 @@ public function read($id): string|false
4972
return $row !== false ? (string) $row : '';
5073
}
5174

75+
/** @inheritDoc */
5276
public function write($id, $data): bool
5377
{
5478
$stmt = $this->pdo->prepare("REPLACE INTO {$this->table} (id, payload, last_activity) VALUES (:id, :payload, :time)");
@@ -59,12 +83,14 @@ public function write($id, $data): bool
5983
]);
6084
}
6185

86+
/** @inheritDoc */
6287
public function destroy($id): bool
6388
{
6489
$stmt = $this->pdo->prepare("DELETE FROM {$this->table} WHERE id = :id");
6590
return $stmt->execute([':id' => $id]);
6691
}
6792

93+
/** @inheritDoc */
6894
public function gc($max_lifetime): int|false
6995
{
7096
$stmt = $this->pdo->prepare("DELETE FROM {$this->table} WHERE last_activity < :exp");

Process/Session/Handlers/RedisSessionHandler.php

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,36 @@
44

55
namespace Tamedevelopers\Support\Process\Session\Handlers;
66

7-
use Redis;
87
use SessionHandlerInterface;
98

109
/**
1110
* Redis-backed session handler using phpredis.
11+
*
12+
* Responsibilities:
13+
* - Store session payload in Redis with a key prefix and TTL
14+
* - Use SETEX to ensure automatic expiration
15+
* - Avoid hard type declarations for Redis to satisfy IDEs when extension is missing
1216
*/
1317
final class RedisSessionHandler implements SessionHandlerInterface
1418
{
15-
private Redis $redis;
19+
/** @var object Underlying phpredis client instance */
20+
private $redis;
21+
22+
/** @var string Key prefix for all session entries */
1623
private string $prefix;
24+
25+
/** @var int Time-to-live in seconds for session entries */
1726
private int $ttl;
1827

28+
/**
29+
* @param string $host Redis host (default: 127.0.0.1)
30+
* @param int $port Redis port (default: 6379)
31+
* @param float $timeout Connection timeout in seconds (default: 1.5)
32+
* @param mixed $auth Password or array for ACL auth (optional)
33+
* @param int $database Redis database index (default: 0)
34+
* @param string $prefix Key prefix for session entries (default: sess:)
35+
* @param int $ttl TTL in seconds for session data (default: 1440)
36+
*/
1937
public function __construct(
2038
string $host = '127.0.0.1',
2139
int $port = 6379,
@@ -25,7 +43,8 @@ public function __construct(
2543
string $prefix = 'sess:',
2644
int $ttl = 1440
2745
) {
28-
$this->redis = new Redis();
46+
// Avoid hard typehint to keep IDE analyzers happy when extension is missing
47+
$this->redis = new \Redis();
2948
$this->redis->connect($host, $port, $timeout);
3049
if ($auth !== null && $auth !== '') {
3150
$this->redis->auth($auth);

Process/SessionManager.php

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,27 @@
99
use Tamedevelopers\Support\Process\Concerns\SessionInterface as BaseSessionInterface;
1010
use Tamedevelopers\Support\Process\Session\Handlers\DatabaseSessionHandler;
1111
use Tamedevelopers\Support\Process\Session\Handlers\RedisSessionHandler;
12-
use Redis;
1312

1413
/**
1514
* Configurable session manager supporting file, database, and redis drivers.
15+
*
16+
* Responsibilities:
17+
* - Configure PHP sessions based on a chosen driver
18+
* - For file driver, ensure the directory exists (defaults to storage_path('session'))
19+
* - For database driver, install a PDO-backed handler
20+
* - For redis driver, install a phpredis-backed handler with TTL
21+
* - Provide a simple, framework-agnostic SessionInterface implementation
1622
*/
1723
final class SessionManager implements BaseSessionInterface
1824
{
19-
/** @var array<string,mixed> */
25+
/** @var array<string,mixed> Resolved session configuration */
2026
private array $config;
2127

2228
/**
2329
* @param array<string,mixed> $config
24-
* - driver: file|database|redis|native (default: native)
30+
* - driver: file|files|database|redis|native (default: native)
2531
* - lifetime: int seconds (optional, default from ini)
26-
* - path: string for file driver (optional)
32+
* - path: string for file driver (optional; defaults to storage_path('session') when available)
2733
* - database: [dsn, username, password, options(array), table(string)]
2834
* - redis: [host, port, timeout, auth, database, prefix, ttl]
2935
*/
@@ -47,10 +53,20 @@ public function start(): void
4753
@ini_set('session.gc_maxlifetime', (string) $lifetime);
4854
}
4955

56+
// default path to storage_path('sessions') if not provided for file driver
57+
if (in_array($driver, ['file', 'files'], true)) {
58+
if (empty($this->config['path']) && function_exists('storage_path')) {
59+
$this->config['path'] = storage_path('sessions');
60+
}
61+
}
62+
5063
switch ($driver) {
5164
case 'file':
5265
$this->configureFileDriver();
5366
break;
67+
case 'files': // alias
68+
$this->configureFileDriver();
69+
break;
5470
case 'database':
5571
$this->configureDatabaseDriver($lifetime);
5672
break;
@@ -166,8 +182,13 @@ public function all(): array
166182
}
167183

168184
/** @inheritDoc */
169-
public function destroy(): void
185+
public function destroy(?string $key = null): void
170186
{
187+
if ($key !== null) {
188+
unset($_SESSION[$key]);
189+
return;
190+
}
191+
171192
if (session_status() === PHP_SESSION_ACTIVE) {
172193
@session_unset();
173194
@session_destroy();

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ Support Package For PHP and Laravel
250250
* [host](#host)
251251
* [full](#full)
252252
* [path](#path)
253-
* [server](#server)
253+
* [server](#http-server)
254254
* [request](#request)
255255
* [referral](#referral)
256256
* [Cookie](#cookie)

Tests/session_file_driver.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
$sessionPath = __DIR__ . '/../storage/sessions';
99
$session = new SessionManager([
1010
'driver' => 'file',
11-
'path' => $sessionPath,
11+
// If omitted, defaults to storage_path('session')
12+
// 'path' => $sessionPath,
1213
'lifetime' => 1800,
1314
]);
1415

1516
$session->start();
1617
$session->put('foo', 'bar');
18+
$session->destroy('foo');
1719

1820
echo 'Session ID: ' . $session->id() . PHP_EOL;
19-
echo 'foo=' . $session->get('foo') . PHP_EOL;
21+
echo 'foo=' . var_export($session->get('foo'), true) . PHP_EOL;

0 commit comments

Comments
 (0)