1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace ByJG\Cache\Psr16; |
4
|
|
|
|
5
|
|
|
use ByJG\Cache\CacheLockInterface; |
6
|
|
|
use Exception; |
7
|
|
|
use Psr\Log\NullLogger; |
8
|
|
|
|
9
|
|
|
class FileSystemCacheEngine extends BaseCacheEngine implements CacheLockInterface |
10
|
|
|
{ |
11
|
|
|
|
12
|
|
|
protected $logger = null; |
13
|
|
|
|
14
|
|
|
protected $prefix = null; |
15
|
|
|
|
16
|
|
|
public function __construct($prefix = 'cache', $logger = null) |
17
|
|
|
{ |
18
|
|
|
$this->prefix = $prefix; |
19
|
|
|
|
20
|
|
|
$this->logger = $logger; |
21
|
|
|
if (is_null($logger)) { |
22
|
|
|
$this->logger = new NullLogger(); |
23
|
|
|
} |
24
|
|
|
} |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @param string $key The object KEY |
28
|
|
|
* @param mixed $default IGNORED IN MEMCACHED. |
29
|
|
|
* @return mixed Description |
30
|
|
|
*/ |
31
|
|
|
public function get($key, $default = null) |
32
|
|
|
{ |
33
|
|
|
// Check if file is Locked |
34
|
|
|
$fileKey = $this->fixKey($key); |
35
|
|
|
$lockFile = $fileKey . ".lock"; |
36
|
|
|
if (file_exists($lockFile)) { |
37
|
|
|
$this->logger->info("[Filesystem cache] Locked! $key. Waiting..."); |
38
|
|
|
$lockTime = filemtime($lockFile); |
39
|
|
|
|
40
|
|
|
while (true) { |
41
|
|
|
if (!file_exists($lockFile)) { |
42
|
|
|
$this->logger->info("[Filesystem cache] Lock released for '$key'"); |
43
|
|
|
break; |
44
|
|
|
} |
45
|
|
|
if (intval(time() - $lockTime) > 20) { // Wait for 10 seconds |
46
|
|
|
$this->logger->info("[Filesystem cache] Gave up to wait unlock. Release lock for '$key'"); |
47
|
|
|
$this->unlock($key); |
48
|
|
|
return $default; |
49
|
|
|
} |
50
|
|
|
sleep(1); // 1 second |
51
|
|
|
} |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
// Check if file exists |
55
|
|
|
if ($this->has($key)) { |
56
|
|
|
$this->logger->info("[Filesystem cache] Get '$key'"); |
57
|
|
|
return unserialize(file_get_contents($fileKey)); |
58
|
|
|
} else { |
59
|
|
|
$this->logger->info("[Filesystem cache] Not found '$key'"); |
60
|
|
|
return $default; |
61
|
|
|
} |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. |
66
|
|
|
* |
67
|
|
|
* @param string $key The key of the item to store. |
68
|
|
|
* @param mixed $value The value of the item to store, must be serializable. |
69
|
|
|
* @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and |
70
|
|
|
* the driver supports TTL then the library may set a default value |
71
|
|
|
* for it or let the driver take care of that. |
72
|
|
|
* |
73
|
|
|
* @return bool True on success and false on failure. |
74
|
|
|
* |
75
|
|
|
* @throws \Psr\SimpleCache\InvalidArgumentException |
76
|
|
|
* MUST be thrown if the $key string is not a legal value. |
77
|
|
|
*/ |
78
|
|
|
public function set($key, $value, $ttl = null) |
79
|
|
|
{ |
80
|
|
|
$fileKey = $this->fixKey($key); |
81
|
|
|
|
82
|
|
|
$this->logger->info("[Filesystem cache] Set '$key' in FileSystem"); |
83
|
|
|
|
84
|
|
|
try { |
85
|
|
|
if (file_exists($fileKey)) { |
86
|
|
|
unlink($fileKey); |
87
|
|
|
unlink("$fileKey.ttl"); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
if (is_null($value)) { |
91
|
|
|
return false; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
if (is_string($value) && (strlen($value) === 0)) { |
95
|
|
|
touch($fileKey); |
96
|
|
|
} else { |
97
|
|
|
file_put_contents($fileKey, serialize($value)); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
$validUntil = $this->addToNow($ttl); |
101
|
|
|
if (!empty($validUntil)) { |
102
|
|
|
file_put_contents($fileKey . ".ttl", $validUntil); |
103
|
|
|
} |
104
|
|
|
} catch (Exception $ex) { |
105
|
|
|
$this->logger->warning("[Filesystem cache] I could not write to cache on file '" . basename($key) . "'. Switching to nocache=true mode."); |
106
|
|
|
return false; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
return true; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* @param string $key |
114
|
|
|
* @return bool |
115
|
|
|
*/ |
116
|
|
|
public function delete($key) |
117
|
|
|
{ |
118
|
|
|
$this->set($key, null); |
119
|
|
|
return true; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Lock resource before set it. |
124
|
|
|
* @param string $key |
125
|
|
|
*/ |
126
|
|
|
public function lock($key) |
127
|
|
|
{ |
128
|
|
|
$this->logger->info("[Filesystem cache] Lock '$key'"); |
129
|
|
|
|
130
|
|
|
$lockFile = $this->fixKey($key) . ".lock"; |
131
|
|
|
|
132
|
|
|
try { |
133
|
|
|
file_put_contents($lockFile, date('c')); |
134
|
|
|
} catch (Exception $ex) { |
135
|
|
|
// Ignoring... Set will cause an error |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* UnLock resource after set it. |
141
|
|
|
* @param string $key |
142
|
|
|
*/ |
143
|
|
|
public function unlock($key) |
144
|
|
|
{ |
145
|
|
|
|
146
|
|
|
$this->logger->info("[Filesystem cache] Unlock '$key'"); |
147
|
|
|
|
148
|
|
|
$lockFile = $this->fixKey($key) . ".lock"; |
149
|
|
|
|
150
|
|
|
if (file_exists($lockFile)) { |
151
|
|
|
unlink($lockFile); |
152
|
|
|
} |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
public function isAvailable() |
156
|
|
|
{ |
157
|
|
|
return is_writable(dirname($this->fixKey('test'))); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
protected function fixKey($key) |
161
|
|
|
{ |
162
|
|
|
return sys_get_temp_dir() . '/' |
163
|
|
|
. $this->prefix |
164
|
|
|
. '-' . preg_replace("/[\/\\\]/", "#", $key) |
165
|
|
|
. '.cache'; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Wipes clean the entire cache's keys. |
170
|
|
|
* |
171
|
|
|
* @return bool True on success and false on failure. |
172
|
|
|
*/ |
173
|
|
|
public function clear() |
174
|
|
|
{ |
175
|
|
|
$patternKey = $this->fixKey('*'); |
176
|
|
|
$list = glob($patternKey); |
177
|
|
|
foreach ($list as $file) { |
178
|
|
|
unlink($file); |
179
|
|
|
} |
180
|
|
|
return true; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Determines whether an item is present in the cache. |
185
|
|
|
* NOTE: It is recommended that has() is only to be used for cache warming type purposes |
186
|
|
|
* and not to be used within your live applications operations for get/set, as this method |
187
|
|
|
* is subject to a race condition where your has() will return true and immediately after, |
188
|
|
|
* another script can remove it making the state of your app out of date. |
189
|
|
|
* |
190
|
|
|
* @param string $key The cache item key. |
191
|
|
|
* @return bool |
192
|
|
|
* @throws \Psr\SimpleCache\InvalidArgumentException |
193
|
|
|
* MUST be thrown if the $key string is not a legal value. |
194
|
|
|
*/ |
195
|
|
|
public function has($key) |
196
|
|
|
{ |
197
|
|
|
$fileKey = $this->fixKey($key); |
198
|
|
|
if (file_exists($fileKey)) { |
199
|
|
|
if (file_exists("$fileKey.ttl")) { |
200
|
|
|
$fileTtl = intval(file_get_contents("$fileKey.ttl")); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
View Code Duplication |
if (!empty($fileTtl) && time() >= $fileTtl) { |
|
|
|
|
204
|
|
|
$this->logger->info("[Filesystem cache] File too old. Ignoring '$key'"); |
205
|
|
|
$this->delete($key); |
206
|
|
|
|
207
|
|
|
return false; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
return true; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
return false; |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
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.