Skip to content

Commit 69e2cfd

Browse files
committed
feat(session): session cache and persistence
1 parent 1376c02 commit 69e2cfd

File tree

6 files changed

+233
-189
lines changed

6 files changed

+233
-189
lines changed

packages/http/src/Session/Managers/DatabaseSessionManager.php

Lines changed: 38 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@
77
use Tempest\Clock\Clock;
88
use Tempest\Database\Database;
99
use Tempest\Http\Session\Session;
10+
use Tempest\Http\Session\SessionCache;
1011
use Tempest\Http\Session\SessionConfig;
1112
use Tempest\Http\Session\SessionDestroyed;
1213
use Tempest\Http\Session\SessionId;
1314
use Tempest\Http\Session\SessionManager;
14-
use Tempest\Support\Arr;
15-
use Tempest\Support\Arr\ArrayInterface;
1615

1716
use function Tempest\Database\query;
1817
use function Tempest\event;
@@ -23,128 +22,88 @@ public function __construct(
2322
private Clock $clock,
2423
private SessionConfig $config,
2524
private Database $database,
25+
private SessionCache $cache,
2626
) {}
2727

2828
public function create(SessionId $id): Session
2929
{
30-
return $this->persist($id);
31-
}
30+
$session = $this->resolve(id: $id);
3231

33-
public function set(SessionId $id, string $key, mixed $value): void
34-
{
35-
$this->persist($id, [...$this->getData($id), ...[$key => $value]]);
36-
}
37-
38-
public function get(SessionId $id, string $key, mixed $default = null): mixed
39-
{
40-
$value = Arr\get_by_key($this->getData($id), $key, $default);
41-
42-
if ($value instanceof ArrayInterface) {
43-
return $value->toArray();
32+
if ($session) {
33+
return $session;
4434
}
4535

46-
return $value;
47-
}
48-
49-
public function all(SessionId $id): array
50-
{
51-
return $this->getData($id);
52-
}
36+
$session = new Session(
37+
id: $id,
38+
createdAt: $this->clock->now(),
39+
lastActiveAt: $this->clock->now(),
40+
data: [],
41+
);
5342

54-
public function remove(SessionId $id, string $key): void
55-
{
56-
$data = $this->getData($id);
57-
$data = Arr\remove_keys($data, $key);
43+
$this->cache->store(session: $session);
5844

59-
$this->persist($id, $data);
45+
return $session;
6046
}
6147

6248
public function destroy(SessionId $id): void
6349
{
64-
query(DatabaseSession::class)
50+
query(model: DatabaseSession::class)
6551
->delete()
6652
->where('session_id', (string) $id)
6753
->execute();
6854

69-
event(new SessionDestroyed($id));
70-
}
71-
72-
public function isValid(SessionId $id): bool
73-
{
74-
$session = $this->resolve($id);
75-
76-
if ($session === null) {
77-
return false;
78-
}
79-
80-
return $this->clock->now()->before(
81-
other: $session->lastActiveAt->plus($this->config->expiration),
82-
);
55+
event(event: new SessionDestroyed(id: $id));
8356
}
8457

8558
public function cleanup(): void
8659
{
8760
$expired = $this->clock
8861
->now()
89-
->minus($this->config->expiration);
62+
->minus(duration: $this->config->expiration);
9063

91-
query(DatabaseSession::class)
64+
query(model: DatabaseSession::class)
9265
->delete()
93-
->whereBefore('last_active_at', $expired)
66+
->whereBefore(field: 'last_active_at', date: $expired)
9467
->execute();
9568
}
9669

97-
private function resolve(SessionId $id): ?Session
70+
public function resolve(SessionId $id): ?Session
9871
{
99-
$session = query(DatabaseSession::class)
72+
$session = $this->cache->find(sessionId: $id);
73+
74+
if ($session) {
75+
return $session;
76+
}
77+
78+
$session = query(model: DatabaseSession::class)
10079
->select()
10180
->where('session_id', (string) $id)
10281
->first();
10382

104-
if ($session === null) {
83+
if (! $session) {
10584
return null;
10685
}
10786

108-
return new Session(
87+
$session = new Session(
10988
id: $id,
11089
createdAt: $session->created_at,
11190
lastActiveAt: $session->last_active_at,
112-
data: unserialize($session->data),
91+
data: unserialize(data: $session->data),
11392
);
114-
}
11593

116-
/**
117-
* @return array<mixed>
118-
*/
119-
private function getData(SessionId $id): array
120-
{
121-
return $this->resolve($id)->data ?? [];
94+
$this->cache->store(session: $session);
95+
96+
return $session;
12297
}
12398

124-
/**
125-
* @param array<mixed>|null $data
126-
*/
127-
private function persist(SessionId $id, ?array $data = null): Session
99+
public function persist(Session $session): void
128100
{
129-
$now = $this->clock->now();
130-
$session = $this->resolve($id) ?? new Session(
131-
id: $id,
132-
createdAt: $now,
133-
lastActiveAt: $now,
134-
);
135-
136-
if ($data !== null) {
137-
$session->data = $data;
138-
}
139-
140-
query(DatabaseSession::class)->updateOrCreate([
141-
'session_id' => (string) $id,
142-
], [
143-
'data' => serialize($session->data),
101+
query(model: DatabaseSession::class)->updateOrCreate(find: [
102+
'session_id' => (string) $session->id,
103+
], update: [
104+
'data' => serialize(value: $session->data),
144105
'created_at' => $session->createdAt,
145-
'last_active_at' => $now,
106+
'last_active_at' => $this->clock->now(),
146107
]);
147-
148-
return $session;
149108
}
150109
}

packages/http/src/Session/Managers/FileSessionManager.php

Lines changed: 44 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Tempest\Clock\Clock;
88
use Tempest\Http\Session\Session;
9+
use Tempest\Http\Session\SessionCache;
910
use Tempest\Http\Session\SessionConfig;
1011
use Tempest\Http\Session\SessionDestroyed;
1112
use Tempest\Http\Session\SessionId;
@@ -21,138 +22,99 @@
2122
public function __construct(
2223
private Clock $clock,
2324
private SessionConfig $sessionConfig,
25+
private SessionCache $cache,
2426
) {}
2527

2628
public function create(SessionId $id): Session
2729
{
28-
return $this->persist($id);
29-
}
30-
31-
public function set(SessionId $id, string $key, mixed $value): void
32-
{
33-
$this->persist($id, [...$this->getData($id), ...[$key => $value]]);
34-
}
30+
$session = $this->resolve(id:$id);
3531

36-
public function get(SessionId $id, string $key, mixed $default = null): mixed
37-
{
38-
return $this->getData($id)[$key] ?? $default;
39-
}
32+
if ($session) {
33+
return $session;
34+
}
4035

41-
public function remove(SessionId $id, string $key): void
42-
{
43-
$data = $this->getData($id);
36+
$session = new Session(
37+
id: $id,
38+
createdAt: $this->clock->now(),
39+
lastActiveAt: $this->clock->now(),
40+
data: [],
41+
);
4442

45-
unset($data[$key]);
43+
$this->cache->store(session:$session);
4644

47-
$this->persist($id, $data);
45+
return $session;
4846
}
4947

5048
public function destroy(SessionId $id): void
5149
{
52-
unlink($this->getPath($id));
50+
unlink(filename:$this->getPath(id:$id));
5351

54-
event(new SessionDestroyed($id));
52+
event(event:new SessionDestroyed(id:$id));
5553
}
5654

57-
public function isValid(SessionId $id): bool
55+
public function resolve(SessionId $id): ?Session
5856
{
59-
$session = $this->resolve($id);
57+
$session = $this->cache->find(sessionId:$id);
6058

61-
if ($session === null) {
62-
return false;
59+
if ($session) {
60+
return $session;
6361
}
6462

65-
if (! ($session->lastActiveAt ?? null)) {
66-
return false;
67-
}
68-
69-
return $this->clock->now()->before(
70-
other: $session->lastActiveAt->plus($this->sessionConfig->expiration),
71-
);
72-
}
73-
74-
private function getPath(SessionId $id): string
75-
{
76-
return internal_storage_path($this->sessionConfig->path, (string) $id);
77-
}
78-
79-
private function resolve(SessionId $id): ?Session
80-
{
81-
$path = $this->getPath($id);
63+
$path = $this->getPath(id:$id);
8264

8365
try {
84-
if (! Filesystem\is_file($path)) {
66+
if (! Filesystem\is_file(path:$path)) {
8567
return null;
8668
}
8769

88-
$file_pointer = fopen($path, 'rb');
89-
flock($file_pointer, LOCK_SH);
70+
$file_pointer = fopen(filename:$path, mode:'rb');
71+
flock(stream:$file_pointer, operation:LOCK_SH);
72+
73+
$content = Filesystem\read_file(filename:$path);
74+
75+
flock(stream:$file_pointer, operation:LOCK_UN);
76+
fclose(stream:$file_pointer);
9077

91-
$content = Filesystem\read_file($path);
78+
$session = unserialize(data:$content, options:['allowed_classes' => true]);
9279

93-
flock($file_pointer, LOCK_UN);
94-
fclose($file_pointer);
80+
$this->cache->store(session:$session);
9581

96-
return unserialize($content, ['allowed_classes' => true]);
82+
return $session;
9783
} catch (Throwable) {
9884
return null;
9985
}
10086
}
10187

102-
public function all(SessionId $id): array
88+
public function persist(Session $session): void
10389
{
104-
return $this->getData($id);
105-
}
90+
$session->lastActiveAt = $this->clock->now();
10691

107-
/**
108-
* @return array<mixed>
109-
*/
110-
private function getData(SessionId $id): array
111-
{
112-
return $this->resolve($id)->data ?? [];
113-
}
114-
115-
/**
116-
* @param array<mixed>|null $data
117-
*/
118-
private function persist(SessionId $id, ?array $data = null): Session
119-
{
120-
$now = $this->clock->now();
121-
$session = $this->resolve($id) ?? new Session(
122-
id: $id,
123-
createdAt: $now,
124-
lastActiveAt: $now,
125-
);
126-
127-
$session->lastActiveAt = $now;
128-
129-
if ($data !== null) {
130-
$session->data = $data;
131-
}
132-
133-
Filesystem\write_file($this->getPath($id), serialize($session), LOCK_EX);
134-
135-
return $session;
92+
Filesystem\write_file(filename:$this->getPath(id:$session->id), content:serialize(value:$session), flags:LOCK_EX);
13693
}
13794

13895
public function cleanup(): void
13996
{
140-
$sessionFiles = glob(internal_storage_path($this->sessionConfig->path, '/*'));
97+
$sessionFiles = glob(pattern:internal_storage_path($this->sessionConfig->path, '/*'));
14198

14299
foreach ($sessionFiles as $sessionFile) {
143-
$id = new SessionId(pathinfo($sessionFile, PATHINFO_FILENAME));
100+
$id = new SessionId(id:pathinfo(path:$sessionFile, flags:PATHINFO_FILENAME));
144101

145-
$session = $this->resolve($id);
102+
$session = $this->resolve(id:$id);
146103

147104
if ($session === null) {
148105
continue;
149106
}
150107

151-
if ($this->isValid($session->id)) {
108+
if ($this->cache->isValid(session:$session)) {
152109
continue;
153110
}
154111

155112
$session->destroy();
156113
}
157114
}
115+
116+
private function getPath(SessionId $id): string
117+
{
118+
return internal_storage_path($this->sessionConfig->path, (string) $id);
119+
}
158120
}

0 commit comments

Comments
 (0)