1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace ByJG\Cache\Psr16; |
4
|
|
|
|
5
|
|
|
use Psr\Cache\InvalidArgumentException; |
6
|
|
|
use Psr\Log\NullLogger; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Caching based on Unix Share Memory |
10
|
|
|
* |
11
|
|
|
* # ipcs -m |
12
|
|
|
* List all segments used |
13
|
|
|
* |
14
|
|
|
* # ipcs -lm |
15
|
|
|
* ------ Shared Memory Limits -------- |
16
|
|
|
* max number of segments = 4096 <--- this is SHMMNI |
17
|
|
|
* max seg size (kbytes) = 67108864 <--- this is SHMMAX |
18
|
|
|
* max total shared memory (kbytes) = 17179869184<- this is SHMALL |
19
|
|
|
* min seg size (bytes) = 1 |
20
|
|
|
* |
21
|
|
|
* |
22
|
|
|
*/ |
23
|
|
|
class ShmopCacheEngine extends BaseCacheEngine |
24
|
|
|
{ |
25
|
|
|
protected $logger = null; |
26
|
|
|
|
27
|
|
|
protected $config = []; |
28
|
|
|
|
29
|
|
|
public function __construct($config = [], $logger = null) |
30
|
|
|
{ |
31
|
|
|
$this->config = $config; |
32
|
|
|
|
33
|
|
|
if (!isset($this->config['max-size'])) { |
34
|
|
|
$this->config['max-size'] = 524288; |
35
|
|
|
} |
36
|
|
|
if (!isset($this->config['default-permission'])) { |
37
|
|
|
$this->config['default-permission'] = '0700'; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
$this->logger = $logger; |
41
|
|
|
if (is_null($logger)) { |
42
|
|
|
$this->logger = new NullLogger(); |
43
|
|
|
} |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
protected function getFilenameToken($key) |
47
|
|
|
{ |
48
|
|
|
return sys_get_temp_dir() . '/shmop-' . sha1($key) . '.cache'; |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
protected function getMaxSize() |
52
|
|
|
{ |
53
|
|
|
return $this->config['max-size']; |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
protected function getDefaultPermission() |
57
|
|
|
{ |
58
|
|
|
return $this->config['default-permission']; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
protected function getFTok($file) |
62
|
|
|
{ |
63
|
|
|
if (!file_exists($file)) { |
64
|
|
|
touch($file); |
65
|
|
|
} |
66
|
|
|
return ftok($file, 'j'); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @param string $key The object KEY |
71
|
|
|
* @param mixed $default The time to live in seconds of the object. Depends on implementation. |
72
|
|
|
* @return mixed The Object |
73
|
|
|
*/ |
74
|
|
|
public function get($key, $default = null) |
75
|
|
|
{ |
76
|
|
|
if ($default === false) { |
77
|
|
|
$this->logger->info("[Shmop Cache] Ignored $key because TTL=FALSE"); |
78
|
|
|
return $default; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
$file = $this->getFilenameToken($key); |
82
|
|
|
$fileKey = $this->getFTok($file); |
83
|
|
|
|
84
|
|
|
// Opened |
85
|
|
|
$shm_id = @shmop_open($fileKey, "a", 0, 0); |
86
|
|
|
if (!$shm_id) { |
87
|
|
|
$this->logger->info("[Shmop Cache] '$key' not exists"); |
88
|
|
|
return $default; |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
if (!$this->isValidAge($file)) { |
92
|
|
|
return $default; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
$this->logger->info("[Shmop Cache] Get '$key'"); |
96
|
|
|
|
97
|
|
|
$serialized = shmop_read($shm_id, 0, shmop_size($shm_id)); |
98
|
|
|
shmop_close($shm_id); |
99
|
|
|
|
100
|
|
|
return unserialize($serialized); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
protected function isValidAge($file) |
104
|
|
|
{ |
105
|
|
|
if (file_exists("$file.ttl")) { |
106
|
|
|
$fileTtl = intval(file_get_contents("$file.ttl")); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
View Code Duplication |
if (!empty($fileTtl) && time() >= $fileTtl) { |
|
|
|
|
110
|
|
|
$this->logger->info("[Shmop Cache] File too old. Ignoring"); |
111
|
|
|
$this->deleteFromFilenameToken($file); |
112
|
|
|
return false; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
return true; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. |
120
|
|
|
* |
121
|
|
|
* @param string $key The key of the item to store. |
122
|
|
|
* @param mixed $value The value of the item to store, must be serializable. |
123
|
|
|
* @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and |
124
|
|
|
* the driver supports TTL then the library may set a default value |
125
|
|
|
* for it or let the driver take care of that. |
126
|
|
|
* @return bool True on success and false on failure. |
127
|
|
|
* @throws InvalidArgumentException |
128
|
|
|
*/ |
129
|
|
|
public function set($key, $value, $ttl = null) |
130
|
|
|
{ |
131
|
|
|
$this->logger->info("[Shmop Cache] set '$key'"); |
132
|
|
|
|
133
|
|
|
$this->delete($key); |
134
|
|
|
|
135
|
|
|
$serialized = serialize($value); |
136
|
|
|
$size = strlen($serialized); |
137
|
|
|
|
138
|
|
|
if ($size > $this->getMaxSize()) { |
139
|
|
|
throw new \ByJG\Cache\InvalidArgumentException('Object is greater than the max size allowed: ' . $this->getMaxSize()); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
$file = $this->getFilenameToken($key); |
143
|
|
|
$shmKey = $this->getFTok($file); |
144
|
|
|
$shm_id = shmop_open($shmKey, "c", 0777, $size); |
145
|
|
|
if (!$shm_id) { |
146
|
|
|
$message = "Couldn't create shared memory segment"; |
147
|
|
|
$lastError = error_get_last(); |
148
|
|
|
if (isset($lastError['message'])) { |
149
|
|
|
$message = $lastError['message']; |
150
|
|
|
} |
151
|
|
|
throw new \ByJG\Cache\InvalidArgumentException($message); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
$shm_bytes_written = shmop_write($shm_id, $serialized, 0); |
155
|
|
|
$this->logger->info("[Shmop Cache] set '$key' confirmed write $shm_bytes_written bytes of $size bytes"); |
156
|
|
|
if ($shm_bytes_written != $size) { |
157
|
|
|
$this->logger->warning("Couldn't write the entire length of data"); |
158
|
|
|
} |
159
|
|
|
shmop_close($shm_id); |
160
|
|
|
|
161
|
|
|
$validUntil = $this->addToNow($ttl); |
162
|
|
|
if (!empty($validUntil)) { |
163
|
|
|
file_put_contents("$file.ttl", $validUntil); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
return true; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* @param string $key |
171
|
|
|
* @return bool |
172
|
|
|
*/ |
173
|
|
|
public function delete($key) |
174
|
|
|
{ |
175
|
|
|
$this->logger->info("[Shmop Cache] release '$key'"); |
176
|
|
|
|
177
|
|
|
if ($this->get($key) === false) { |
178
|
|
|
$this->logger->info("[Shmop Cache] release '$key' does not exists."); |
179
|
|
|
return false; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$file = $this->getFilenameToken($key); |
183
|
|
|
$this->deleteFromFilenameToken($file); |
184
|
|
|
return true; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
private function deleteFromFilenameToken($file) |
188
|
|
|
{ |
189
|
|
|
$filekey = $this->getFTok($file); |
190
|
|
|
$shm_id = @shmop_open($filekey, "w", 0, 0); |
191
|
|
|
|
192
|
|
|
if (file_exists($file)) { |
193
|
|
|
unlink($file); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
if (file_exists("$file.ttl")) { |
197
|
|
|
unlink("$file.ttl"); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
if ($shm_id) { |
201
|
|
|
shmop_delete($shm_id); |
202
|
|
|
shmop_close($shm_id); |
203
|
|
|
|
204
|
|
|
$this->logger->info("[Shmop Cache] release confirmed."); |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
public function clear() |
209
|
|
|
{ |
210
|
|
|
$patternKey = sys_get_temp_dir() . '/shmop-*.cache'; |
211
|
|
|
$list = glob($patternKey); |
212
|
|
|
foreach ($list as $file) { |
213
|
|
|
$this->deleteFromFilenameToken($file); |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
public function has($key) |
218
|
|
|
{ |
219
|
|
|
$file = $this->getFilenameToken($key); |
220
|
|
|
$fileKey = $this->getFTok($file); |
221
|
|
|
|
222
|
|
|
// Opened |
223
|
|
|
$shm_id = @shmop_open($fileKey, "a", 0, 0); |
224
|
|
|
|
225
|
|
|
$exists = !(!$shm_id); |
226
|
|
|
|
227
|
|
|
if ($exists) { |
228
|
|
|
shmop_close($shm_id); |
229
|
|
|
return $this->isValidAge($file); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
return $exists; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
|
236
|
|
|
public function isAvailable() |
237
|
|
|
{ |
238
|
|
|
return function_exists('shmop_open'); |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.