Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 38 additions & 79 deletions packages/http/src/Session/Managers/DatabaseSessionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
use Tempest\Clock\Clock;
use Tempest\Database\Database;
use Tempest\Http\Session\Session;
use Tempest\Http\Session\SessionCache;
use Tempest\Http\Session\SessionConfig;
use Tempest\Http\Session\SessionDestroyed;
use Tempest\Http\Session\SessionId;
use Tempest\Http\Session\SessionManager;
use Tempest\Support\Arr;
use Tempest\Support\Arr\ArrayInterface;

use function Tempest\Database\query;
use function Tempest\event;
Expand All @@ -23,128 +22,88 @@ public function __construct(
private Clock $clock,
private SessionConfig $config,
private Database $database,
private SessionCache $cache,
) {}

public function create(SessionId $id): Session
{
return $this->persist($id);
}
$session = $this->resolve(id: $id);

public function set(SessionId $id, string $key, mixed $value): void
{
$this->persist($id, [...$this->getData($id), ...[$key => $value]]);
}

public function get(SessionId $id, string $key, mixed $default = null): mixed
{
$value = Arr\get_by_key($this->getData($id), $key, $default);

if ($value instanceof ArrayInterface) {
return $value->toArray();
if ($session) {
return $session;
}

return $value;
}

public function all(SessionId $id): array
{
return $this->getData($id);
}
$session = new Session(
id: $id,
createdAt: $this->clock->now(),
lastActiveAt: $this->clock->now(),
data: [],
);

public function remove(SessionId $id, string $key): void
{
$data = $this->getData($id);
$data = Arr\remove_keys($data, $key);
$this->cache->store(session: $session);

$this->persist($id, $data);
return $session;
}

public function destroy(SessionId $id): void
{
query(DatabaseSession::class)
query(model: DatabaseSession::class)
->delete()
->where('session_id', (string) $id)
->execute();

event(new SessionDestroyed($id));
}

public function isValid(SessionId $id): bool
{
$session = $this->resolve($id);

if ($session === null) {
return false;
}

return $this->clock->now()->before(
other: $session->lastActiveAt->plus($this->config->expiration),
);
event(event: new SessionDestroyed(id: $id));
}

public function cleanup(): void
{
$expired = $this->clock
->now()
->minus($this->config->expiration);
->minus(duration: $this->config->expiration);

query(DatabaseSession::class)
query(model: DatabaseSession::class)
->delete()
->whereBefore('last_active_at', $expired)
->whereBefore(field: 'last_active_at', date: $expired)
->execute();
}

private function resolve(SessionId $id): ?Session
public function resolve(SessionId $id): ?Session
{
$session = query(DatabaseSession::class)
$session = $this->cache->find(sessionId: $id);

if ($session) {
return $session;
}

$session = query(model: DatabaseSession::class)
->select()
->where('session_id', (string) $id)
->first();

if ($session === null) {
if (! $session) {
return null;
}

return new Session(
$session = new Session(
id: $id,
createdAt: $session->created_at,
lastActiveAt: $session->last_active_at,
data: unserialize($session->data),
data: unserialize(data: $session->data),
);
}

/**
* @return array<mixed>
*/
private function getData(SessionId $id): array
{
return $this->resolve($id)->data ?? [];
$this->cache->store(session: $session);

return $session;
}

/**
* @param array<mixed>|null $data
*/
private function persist(SessionId $id, ?array $data = null): Session
public function persist(Session $session): void
{
$now = $this->clock->now();
$session = $this->resolve($id) ?? new Session(
id: $id,
createdAt: $now,
lastActiveAt: $now,
);

if ($data !== null) {
$session->data = $data;
}

query(DatabaseSession::class)->updateOrCreate([
'session_id' => (string) $id,
], [
'data' => serialize($session->data),
query(model: DatabaseSession::class)->updateOrCreate(find: [
'session_id' => (string) $session->id,
], update: [
'data' => serialize(value: $session->data),
'created_at' => $session->createdAt,
'last_active_at' => $now,
'last_active_at' => $this->clock->now(),
]);

return $session;
}
}
126 changes: 44 additions & 82 deletions packages/http/src/Session/Managers/FileSessionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Tempest\Clock\Clock;
use Tempest\Http\Session\Session;
use Tempest\Http\Session\SessionCache;
use Tempest\Http\Session\SessionConfig;
use Tempest\Http\Session\SessionDestroyed;
use Tempest\Http\Session\SessionId;
Expand All @@ -21,138 +22,99 @@
public function __construct(
private Clock $clock,
private SessionConfig $sessionConfig,
private SessionCache $cache,
) {}

public function create(SessionId $id): Session
{
return $this->persist($id);
}

public function set(SessionId $id, string $key, mixed $value): void
{
$this->persist($id, [...$this->getData($id), ...[$key => $value]]);
}
$session = $this->resolve(id:$id);

public function get(SessionId $id, string $key, mixed $default = null): mixed
{
return $this->getData($id)[$key] ?? $default;
}
if ($session) {
return $session;
}

public function remove(SessionId $id, string $key): void
{
$data = $this->getData($id);
$session = new Session(
id: $id,
createdAt: $this->clock->now(),
lastActiveAt: $this->clock->now(),
data: [],
);

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

$this->persist($id, $data);
return $session;
}

public function destroy(SessionId $id): void
{
unlink($this->getPath($id));
unlink(filename:$this->getPath(id:$id));

event(new SessionDestroyed($id));
event(event:new SessionDestroyed(id:$id));
}

public function isValid(SessionId $id): bool
public function resolve(SessionId $id): ?Session
{
$session = $this->resolve($id);
$session = $this->cache->find(sessionId:$id);

if ($session === null) {
return false;
if ($session) {
return $session;
}

if (! ($session->lastActiveAt ?? null)) {
return false;
}

return $this->clock->now()->before(
other: $session->lastActiveAt->plus($this->sessionConfig->expiration),
);
}

private function getPath(SessionId $id): string
{
return internal_storage_path($this->sessionConfig->path, (string) $id);
}

private function resolve(SessionId $id): ?Session
{
$path = $this->getPath($id);
$path = $this->getPath(id:$id);

try {
if (! Filesystem\is_file($path)) {
if (! Filesystem\is_file(path:$path)) {
return null;
}

$file_pointer = fopen($path, 'rb');
flock($file_pointer, LOCK_SH);
$file_pointer = fopen(filename:$path, mode:'rb');
flock(stream:$file_pointer, operation:LOCK_SH);

$content = Filesystem\read_file(filename:$path);

flock(stream:$file_pointer, operation:LOCK_UN);
fclose(stream:$file_pointer);

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

flock($file_pointer, LOCK_UN);
fclose($file_pointer);
$this->cache->store(session:$session);

return unserialize($content, ['allowed_classes' => true]);
return $session;
} catch (Throwable) {
return null;
}
}

public function all(SessionId $id): array
public function persist(Session $session): void
{
return $this->getData($id);
}
$session->lastActiveAt = $this->clock->now();

/**
* @return array<mixed>
*/
private function getData(SessionId $id): array
{
return $this->resolve($id)->data ?? [];
}

/**
* @param array<mixed>|null $data
*/
private function persist(SessionId $id, ?array $data = null): Session
{
$now = $this->clock->now();
$session = $this->resolve($id) ?? new Session(
id: $id,
createdAt: $now,
lastActiveAt: $now,
);

$session->lastActiveAt = $now;

if ($data !== null) {
$session->data = $data;
}

Filesystem\write_file($this->getPath($id), serialize($session), LOCK_EX);

return $session;
Filesystem\write_file(filename:$this->getPath(id:$session->id), content:serialize(value:$session), flags:LOCK_EX);
}

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

foreach ($sessionFiles as $sessionFile) {
$id = new SessionId(pathinfo($sessionFile, PATHINFO_FILENAME));
$id = new SessionId(id:pathinfo(path:$sessionFile, flags:PATHINFO_FILENAME));

$session = $this->resolve($id);
$session = $this->resolve(id:$id);

if ($session === null) {
continue;
}

if ($this->isValid($session->id)) {
if ($this->cache->isValid(session:$session)) {
continue;
}

$session->destroy();
}
}

private function getPath(SessionId $id): string
{
return internal_storage_path($this->sessionConfig->path, (string) $id);
}
}
Loading
Loading