-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathArrayStore.php
More file actions
124 lines (99 loc) · 2.94 KB
/
Copy pathArrayStore.php
File metadata and controls
124 lines (99 loc) · 2.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
/**
* Italix Crypto - ArrayStore
*
* @package Italix\Crypto
*/
declare(strict_types=1);
namespace Italix\Crypto;
use Italix\Contracts\KeyValueStore;
/**
* In-memory storage, for tests and for single-process scripts.
*
* Dies with the request, which makes it useless as a limiter in production —
* every request would start from zero. It is here because a limiter whose only
* implementation needs a database is a limiter that cannot be tested quickly,
* and because the clock is injectable, so an expiry test does not have to sleep.
*/
final class ArrayStore implements KeyValueStore
{
/** @var array<string, array{value: mixed, expires_t: int|null}> */
private array $rows = [];
/** @var callable():int */
private $clock;
/**
* @param callable():int|null $clock injectable so tests can move time without sleeping
*/
public function __construct(?callable $clock = null)
{
$this->clock = $clock ?? static function (): int {
return time();
};
}
public function get(string $key)
{
$row = $this->live($key);
return $row === null ? null : $row['value'];
}
public function put(string $key, $value, int $ttl_n): void
{
$this->rows[$key] = [
'value' => $value,
'expires_t' => $ttl_n > 0 ? ($this->clock)() + $ttl_n : ($this->clock)(),
];
}
public function forget(string $key): void
{
unset($this->rows[$key]);
}
public function increment(string $key, int $ttl_n): int
{
$row = $this->live($key);
if ($row === null) {
$this->put($key, 1, $ttl_n);
return 1;
}
// The expiry is left alone: the window must not slide forward on every hit.
$next = ((int) $row['value']) + 1;
$this->rows[$key]['value'] = $next;
return $next;
}
public function expires_t(string $key): ?int
{
$row = $this->live($key);
return $row === null ? null : $row['expires_t'];
}
/**
* Number of live keys — diagnostics only.
*/
public function count(): int
{
$n = 0;
foreach (array_keys($this->rows) as $key) {
if ($this->live((string) $key) !== null) {
$n++;
}
}
return $n;
}
/**
* @return array{value: mixed, expires_t: int|null}|null
*/
private function live(string $key): ?array
{
if (!isset($this->rows[$key])) {
return null;
}
$row = $this->rows[$key];
if ($row['expires_t'] !== null && ($this->clock)() >= $row['expires_t']) {
unset($this->rows[$key]);
return null;
}
return $row;
}
}