1
|
|
|
<?php
|
2
|
|
|
namespace Mouf\Utils\Cache;
|
3
|
|
|
|
4
|
|
|
use Mouf\Utils\Log\LogInterface;
|
5
|
|
|
use Psr\Log\LoggerInterface;
|
6
|
|
|
|
7
|
|
|
/**
|
8
|
|
|
* This package contains a cache mechanism that relies on temporary files.
|
9
|
|
|
* It is very close to the "classic" FileCache, but it will create a subdirectory system in order
|
10
|
|
|
* to avoid having all files inside the same folders.<br/>
|
11
|
|
|
*<br/>
|
12
|
|
|
* One important property is $hashDept. It sets the length of the subdirectory.
|
13
|
|
|
* The higher it is, the more subfolders will be created, but the lesser cache file they will contain.
|
14
|
|
|
* Max count of subfolders is 16^$hashDepth, so you cannot set a depth higher then 4 as it could create
|
15
|
|
|
* more than 1 000 000 subfolders.<br/>
|
16
|
|
|
*<br/>
|
17
|
|
|
* As an example :<br/>
|
18
|
|
|
* <ul>
|
19
|
|
|
* <li>1 000 000 items are stored into 256 subfolders for $hashDepth == 2, witch means around 4 000 files per folder</li>
|
20
|
|
|
* <li>1 000 000 items are stored into 4096 subfolders for $hashDepth == 3, witch means around 250 files per folder</li>
|
21
|
|
|
*</ul>
|
22
|
|
|
*<br/>
|
23
|
|
|
* If you consider caching more than 1 000 000 items, you should consider another cache service
|
24
|
|
|
*<br/>
|
25
|
|
|
*<br/>
|
26
|
|
|
* <b><u>WARNING:</u> YOU CANNOT SHARE HIS FOLDER WITH OTHER CACHE SYSTEMS<b>, or even put any other file
|
27
|
|
|
* into the $cacheDirectory, as it will remove the whole folder when purged.
|
28
|
|
|
*/
|
29
|
|
|
class BigFileCache implements CacheInterface {
|
30
|
|
|
|
31
|
|
|
/**
|
32
|
|
|
* The default time to live of elements stored in the session (in seconds).
|
33
|
|
|
* Please note that if the session is flushed, all the elements of the cache will disapear anyway.
|
34
|
|
|
* If empty, the time to live will be the time of the session.
|
35
|
|
|
*
|
36
|
|
|
* @Property
|
37
|
|
|
* @var int
|
38
|
|
|
*/
|
39
|
|
|
private $defaultTimeToLive;
|
40
|
|
|
|
41
|
|
|
/**
|
42
|
|
|
* The logger used to trace the cache activity.
|
43
|
|
|
* Supports both PSR3 compatible logger and old Mouf logger for compatibility reasons.
|
44
|
|
|
*
|
45
|
|
|
* @var LoggerInterface|LogInterface
|
46
|
|
|
*/
|
47
|
|
|
private $log;
|
48
|
|
|
|
49
|
|
|
/**
|
50
|
|
|
* The directory the files are stored in.
|
51
|
|
|
* If none is specified, they are stored in the "filecache" directory.
|
52
|
|
|
* The directory must end with a trailing "/".
|
53
|
|
|
*
|
54
|
|
|
* @Property
|
55
|
|
|
* @var string
|
56
|
|
|
*/
|
57
|
|
|
private $cacheDirectory;
|
58
|
|
|
|
59
|
|
|
/**
|
60
|
|
|
* Whether the directory is relative to the system temp directory or not.
|
61
|
|
|
*
|
62
|
|
|
* @Property
|
63
|
|
|
* @var boolean
|
64
|
|
|
*/
|
65
|
|
|
private $relativeToSystemTempDirectory = true;
|
66
|
|
|
|
67
|
|
|
private $hashDepth = 2;
|
68
|
|
|
|
69
|
|
|
/**
|
70
|
|
|
* @param int $defaultTimeToLive
|
71
|
|
|
* @param LoggerInterface|LogInterface $log
|
72
|
|
|
* @param string $cacheDirectory
|
73
|
|
|
* @param boolean $relativeToSystemTempDirectory
|
74
|
|
|
* @param int $hashDepth
|
|
|
|
|
75
|
|
|
*/
|
76
|
|
|
function __construct($defaultTimeToLive, $log, $cacheDirectory, $relativeToSystemTempDirectory)
|
|
|
|
|
77
|
|
|
{
|
78
|
|
|
$this->defaultTimeToLive = $defaultTimeToLive;
|
79
|
|
|
$this->log = $log;
|
80
|
|
|
$this->cacheDirectory = $cacheDirectory;
|
81
|
|
|
$this->relativeToSystemTempDirectory = $relativeToSystemTempDirectory;
|
82
|
|
|
}
|
83
|
|
|
|
84
|
|
|
/**
|
85
|
|
|
* Sets the length of the subdirectory.
|
86
|
|
|
* See Class documentation for more details
|
87
|
|
|
*
|
88
|
|
|
* @param int $hashDepth
|
89
|
|
|
*/
|
90
|
|
|
public function setHashDepth($hashDepth)
|
91
|
|
|
{
|
92
|
|
|
if ($hashDepth > 4 || $hashDepth < 1){
|
93
|
|
|
throw new \Exception("hashDepth property should be betwwen 1 and 4");
|
94
|
|
|
}
|
95
|
|
|
$this->hashDepth = $hashDepth;
|
96
|
|
|
}
|
97
|
|
|
|
98
|
|
|
|
99
|
|
|
|
100
|
|
|
|
101
|
|
|
/**
|
102
|
|
|
* Returns the cached value for the key passed in parameter.
|
103
|
|
|
*
|
104
|
|
|
* @param string $key
|
105
|
|
|
* @return mixed
|
106
|
|
|
*/
|
107
|
|
|
public function get($key) {
|
108
|
|
|
$filename = $this->getFileName($key);
|
109
|
|
|
|
110
|
|
|
if (is_readable($filename)) {
|
111
|
|
|
$fp = fopen($filename, "r");
|
112
|
|
|
if ($fp === false) {//File may have been deleted between is_readable and fopen
|
113
|
|
|
return null;
|
114
|
|
|
}
|
115
|
|
|
$timeout = fgets($fp);
|
116
|
|
|
|
117
|
|
|
if ($timeout > time() || $timeout==0) {
|
118
|
|
|
$contents = "";
|
119
|
|
|
while (!feof($fp)) {
|
120
|
|
|
$contents .= fread($fp, 65536);
|
121
|
|
|
}
|
122
|
|
|
fclose($fp);
|
123
|
|
|
$value = unserialize($contents);
|
124
|
|
|
//$this->log->trace("Retrieving key '$key' from file cache: value returned:".var_export($value, true));
|
|
|
|
|
125
|
|
|
if ($this->log) {
|
126
|
|
|
if ($this->log instanceof LoggerInterface) {
|
127
|
|
|
$this->log->info("Retrieving key '{key}' from file cache.", array('key'=>$key));
|
128
|
|
|
} else {
|
129
|
|
|
$this->log->trace("Retrieving key '$key' from file cache.");
|
130
|
|
|
}
|
131
|
|
|
}
|
132
|
|
|
return $value;
|
133
|
|
View Code Duplication |
} else {
|
|
|
|
|
134
|
|
|
fclose($fp);
|
135
|
|
|
unlink($filename);
|
136
|
|
|
if ($this->log) {
|
137
|
|
|
if ($this->log instanceof LoggerInterface) {
|
138
|
|
|
$this->log->info("Retrieving key '{key}' from file cache: key outdated, cache miss.", array('key'=>$key));
|
139
|
|
|
} else {
|
140
|
|
|
$this->log->trace("Retrieving key '$key' from file cache: key outdated, cache miss.");
|
141
|
|
|
}
|
142
|
|
|
}
|
143
|
|
|
return null;
|
144
|
|
|
}
|
145
|
|
|
} else {
|
146
|
|
|
if ($this->log) {
|
147
|
|
|
if ($this->log instanceof LoggerInterface) {
|
148
|
|
|
$this->log->info("Retrieving key '{key}' from file cache: cache miss.", array('key'=>$key));
|
149
|
|
|
} else {
|
150
|
|
|
$this->log->trace("Retrieving key '$key' from file cache: cache miss.");
|
151
|
|
|
}
|
152
|
|
|
}
|
153
|
|
|
return null;
|
154
|
|
|
}
|
155
|
|
|
}
|
156
|
|
|
|
157
|
|
|
/**
|
158
|
|
|
* Sets the value in the cache.
|
159
|
|
|
*
|
160
|
|
|
* @param string $key The key of the value to store
|
161
|
|
|
* @param mixed $value The value to store
|
162
|
|
|
* @param float $timeToLive The time to live of the cache, in seconds.
|
163
|
|
|
*/
|
164
|
|
|
public function set($key, $value, $timeToLive = null) {
|
165
|
|
|
$filename = $this->getFileName($key);
|
166
|
|
|
//$this->log->trace("Storing value in cache: key '$key', value '".var_export($value, true)."'");
|
|
|
|
|
167
|
|
|
if ($this->log) {
|
168
|
|
|
if ($this->log instanceof LoggerInterface) {
|
169
|
|
|
$this->log->info("Storing value in cache: key '{key}'", array('key'=>$key));
|
170
|
|
|
} else {
|
171
|
|
|
$this->log->trace("Storing value in cache: key '$key'");
|
172
|
|
|
}
|
173
|
|
|
}
|
174
|
|
|
|
175
|
|
|
$oldUmask = umask(0);
|
176
|
|
|
$subfolder = $this->getDirectory($key);
|
177
|
|
|
if (!is_writable($filename)) {
|
178
|
|
|
if (!file_exists($subfolder)) {
|
179
|
|
|
mkdir($subfolder, 0777, true);
|
180
|
|
|
}
|
181
|
|
|
}
|
182
|
|
|
|
183
|
|
View Code Duplication |
if ($timeToLive == null) {
|
|
|
|
|
184
|
|
|
if (empty($this->defaultTimeToLive)) {
|
185
|
|
|
$timeOut = 0;
|
186
|
|
|
} else {
|
187
|
|
|
$timeOut = time() + $this->defaultTimeToLive;
|
188
|
|
|
}
|
189
|
|
|
} else {
|
190
|
|
|
$timeOut = time() + $timeToLive;
|
191
|
|
|
}
|
192
|
|
|
|
193
|
|
|
$fp = fopen($filename, "w");
|
194
|
|
|
fwrite($fp, $timeOut."\n");
|
195
|
|
|
fwrite($fp, serialize($value));
|
196
|
|
|
fclose($fp);
|
197
|
|
|
// Cache is shared with group, not with the rest of the world.
|
198
|
|
|
chmod($filename, 0660);
|
199
|
|
|
|
200
|
|
|
umask($oldUmask);
|
201
|
|
|
}
|
202
|
|
|
|
203
|
|
|
/**
|
204
|
|
|
* Removes the object whose key is $key from the cache.
|
205
|
|
|
*
|
206
|
|
|
* @param string $key The key of the object
|
207
|
|
|
*/
|
208
|
|
View Code Duplication |
public function purge($key) {
|
|
|
|
|
209
|
|
|
if ($this->log) {
|
210
|
|
|
if ($this->log instanceof LoggerInterface) {
|
211
|
|
|
$this->log->info("Purging key '{key}' from file cache.", array('key'=>$key));
|
212
|
|
|
} else {
|
213
|
|
|
$this->log->trace("Purging key '$key' from file cache.");
|
214
|
|
|
}
|
215
|
|
|
}
|
216
|
|
|
$filename = $this->getFileName($key);
|
217
|
|
|
if (file_exists($filename)) {
|
218
|
|
|
unlink($filename);
|
219
|
|
|
}
|
220
|
|
|
}
|
221
|
|
|
|
222
|
|
|
/**
|
223
|
|
|
* Removes all the objects from the cache.
|
224
|
|
|
*
|
225
|
|
|
*/
|
226
|
|
|
public function purgeAll() {
|
227
|
|
|
if ($this->log) {
|
228
|
|
|
if ($this->log instanceof LoggerInterface) {
|
229
|
|
|
$this->log->info("Purging the whole file cache.");
|
230
|
|
|
} else {
|
231
|
|
|
$this->log->trace("Purging the whole file cache.");
|
232
|
|
|
}
|
233
|
|
|
}
|
234
|
|
|
self::rrmdir($this->getDirectory());
|
235
|
|
|
}
|
236
|
|
|
|
237
|
|
|
/**
|
238
|
|
|
* @param mixed $key : if set, this function will return the cache directory WITH subfolder
|
239
|
|
|
* @return string
|
240
|
|
|
*/
|
241
|
|
|
protected function getDirectory($key = null) {
|
242
|
|
|
|
243
|
|
|
$dir = "";
|
244
|
|
|
if ($this->relativeToSystemTempDirectory) {
|
245
|
|
|
$dir .= sys_get_temp_dir()."/";
|
246
|
|
|
}
|
247
|
|
|
if (!empty($this->cacheDirectory)) {
|
248
|
|
|
$dir .= $this->cacheDirectory;
|
249
|
|
|
} else {
|
250
|
|
|
$dir .= "filecache/";
|
251
|
|
|
}
|
252
|
|
|
|
253
|
|
|
if ($key){
|
254
|
|
|
$subFolder = substr(hash("sha256", $key), 0, $this->hashDepth) . "/";
|
255
|
|
|
}else{
|
256
|
|
|
$subFolder = "";
|
257
|
|
|
}
|
258
|
|
|
|
259
|
|
|
return $dir.$subFolder;
|
260
|
|
|
}
|
261
|
|
|
|
262
|
|
|
protected function getFileName($key) {
|
263
|
|
|
$subFolder = $this->getDirectory($key);
|
264
|
|
|
// Remove any "/" and ":" from the name, and replace those with "_" ...
|
265
|
|
|
$key = str_replace(array("_", "/", "\\", ":"), array("___", "_s_", "_b_", "_d_"), $key);
|
266
|
|
|
|
267
|
|
|
// Windows full path need to be less than 260 characters. We need to limit the size of the filename
|
268
|
|
|
$fullPath = $subFolder.$key.".cache";
|
269
|
|
|
|
270
|
|
|
// Approximative value due to NTFS short file names (e.g. PROGRA~1) that get longer when evaluated by Windows
|
271
|
|
|
if (strlen($fullPath)<160) {
|
272
|
|
|
return $fullPath;
|
273
|
|
|
}
|
274
|
|
|
|
275
|
|
|
|
276
|
|
|
// If we go above 160 characters, let's transform the key into a md5
|
277
|
|
|
return $subFolder.md5($key).'.cache';
|
278
|
|
|
}
|
279
|
|
|
|
280
|
|
|
private static function rrmdir($dir) {
|
281
|
|
|
if (is_dir($dir)) {
|
282
|
|
|
$objects = scandir($dir);
|
283
|
|
|
foreach ($objects as $object) {
|
284
|
|
|
if ($object != "." && $object != "..") {
|
285
|
|
|
if (filetype($dir."/".$object) == "dir")
|
286
|
|
|
self::rrmdir($dir."/".$object);
|
287
|
|
|
else unlink ($dir."/".$object);
|
288
|
|
|
}
|
289
|
|
|
}
|
290
|
|
|
reset($objects);
|
291
|
|
|
rmdir($dir);
|
292
|
|
|
}
|
293
|
|
|
}
|
294
|
|
|
}
|
295
|
|
|
|
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.
Consider the following example. The parameter
$italy
is not defined by the methodfinale(...)
.The most likely cause is that the parameter was removed, but the annotation was not.