|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/* |
|
4
|
|
|
* To change this license header, choose License Headers in Project Properties. |
|
5
|
|
|
* To change this template file, choose Tools | Templates |
|
6
|
|
|
* and open the template in the editor. |
|
7
|
|
|
*/ |
|
8
|
|
|
|
|
9
|
|
|
namespace Maslosoft\Addendum\Cache; |
|
10
|
|
|
|
|
11
|
|
|
use FilesystemIterator; |
|
12
|
|
|
use Maslosoft\Addendum\Addendum; |
|
13
|
|
|
use Maslosoft\Addendum\Helpers\SoftIncluder; |
|
14
|
|
|
use Maslosoft\Addendum\Interfaces\AnnotatedInterface; |
|
15
|
|
|
use Maslosoft\Addendum\Options\MetaOptions; |
|
16
|
|
|
use Maslosoft\Cli\Shared\ConfigDetector; |
|
17
|
|
|
use Maslosoft\Cli\Shared\Helpers\PhpExporter; |
|
18
|
|
|
use RecursiveDirectoryIterator; |
|
19
|
|
|
use RecursiveIteratorIterator; |
|
20
|
|
|
use ReflectionClass; |
|
21
|
|
|
use RuntimeException; |
|
22
|
|
|
use UnexpectedValueException; |
|
23
|
|
|
|
|
24
|
|
|
/** |
|
25
|
|
|
* PhpCache |
|
26
|
|
|
* |
|
27
|
|
|
* @author Piotr Maselkowski <pmaselkowski at gmail.com> |
|
28
|
|
|
*/ |
|
29
|
|
|
abstract class PhpCache |
|
30
|
|
|
{ |
|
31
|
|
|
|
|
32
|
|
|
private $metaClass = null; |
|
33
|
|
|
private $component = null; |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* Options |
|
37
|
|
|
* @var string |
|
38
|
|
|
*/ |
|
39
|
|
|
private $instanceId = null; |
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* Addendum runtime path |
|
43
|
|
|
* @var string |
|
44
|
|
|
*/ |
|
45
|
|
|
private $path = ''; |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* |
|
49
|
|
|
* @var NsCache |
|
50
|
|
|
*/ |
|
51
|
|
|
private $nsCache = null; |
|
52
|
|
|
|
|
53
|
|
|
/** |
|
54
|
|
|
* |
|
55
|
|
|
* @var Addendum |
|
56
|
|
|
*/ |
|
57
|
|
|
private $addendum = null; |
|
58
|
|
|
|
|
59
|
|
|
/** |
|
60
|
|
|
* Runtime path |
|
61
|
|
|
* @var string |
|
62
|
|
|
*/ |
|
63
|
|
|
private static $runtimePath = null; |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* Local cacheq |
|
67
|
|
|
* @var type |
|
68
|
|
|
*/ |
|
69
|
|
|
private static $cache = []; |
|
70
|
|
|
|
|
71
|
|
|
/** |
|
72
|
|
|
* Hash map of prepared directories |
|
73
|
|
|
* Key is directory, value is flag indicating if it's prepared. |
|
74
|
|
|
* @var bool[] |
|
75
|
|
|
*/ |
|
76
|
|
|
private static $prepared = []; |
|
77
|
|
|
private $fileName = null; |
|
78
|
|
|
|
|
79
|
|
|
/** |
|
80
|
|
|
* |
|
81
|
|
|
* @param string $metaClass |
|
82
|
|
|
* @param AnnotatedInterface|string $component |
|
83
|
|
|
* @param MetaOptions|Addendum $options |
|
84
|
|
|
*/ |
|
85
|
49 |
|
public function __construct($metaClass = null, $component = null, $options = null) |
|
86
|
|
|
{ |
|
87
|
49 |
|
if (null === self::$runtimePath) |
|
88
|
49 |
|
{ |
|
89
|
1 |
|
self::$runtimePath = (new ConfigDetector)->getRuntimePath(); |
|
90
|
1 |
|
} |
|
91
|
49 |
|
$this->path = self::$runtimePath . '/addendum'; |
|
92
|
49 |
|
$this->metaClass = $metaClass; |
|
93
|
49 |
|
$this->component = $component; |
|
94
|
49 |
|
if (empty($options)) |
|
95
|
49 |
|
{ |
|
96
|
3 |
|
$this->instanceId = Addendum::DefaultInstanceId; |
|
97
|
3 |
|
} |
|
98
|
47 |
|
elseif ($options instanceof Addendum) |
|
99
|
|
|
{ |
|
100
|
47 |
|
$this->instanceId = $options->getInstanceId(); |
|
101
|
47 |
|
} |
|
102
|
|
|
elseif ($options instanceof MetaOptions) |
|
103
|
|
|
{ |
|
104
|
|
|
$this->instanceId = $options->instanceId; |
|
105
|
|
|
} |
|
106
|
|
|
else |
|
107
|
|
|
{ |
|
108
|
|
|
throw new UnexpectedValueException('Unknown options'); |
|
109
|
|
|
} |
|
110
|
49 |
|
$this->prepare(); |
|
111
|
49 |
|
$this->addendum = Addendum::fly($this->instanceId); |
|
112
|
49 |
|
$this->nsCache = new NsCache(dirname($this->getFilename()), $this->addendum); |
|
113
|
49 |
|
} |
|
114
|
|
|
|
|
115
|
|
|
/** |
|
116
|
|
|
* Set working component |
|
117
|
|
|
* @param AnnotatedInterface|string $component |
|
118
|
|
|
*/ |
|
119
|
51 |
|
public function setComponent($component = null) |
|
120
|
|
|
{ |
|
121
|
|
|
// Reset filename as it depends on component |
|
122
|
51 |
|
$this->fileName = null; |
|
123
|
51 |
|
$this->component = $component; |
|
124
|
51 |
|
} |
|
125
|
|
|
|
|
126
|
33 |
|
public function setOptions(MetaOptions $options = null) |
|
127
|
|
|
{ |
|
128
|
33 |
|
$this->fileName = null; |
|
129
|
33 |
|
$this->nsCache->setOptions($options); |
|
130
|
33 |
|
} |
|
131
|
|
|
|
|
132
|
53 |
|
private function prepare() |
|
133
|
|
|
{ |
|
134
|
53 |
|
$fileDir = dirname($this->getFilename()); |
|
135
|
53 |
|
if (isset(self::$prepared[$fileDir]) && self::$prepared[$fileDir]) |
|
136
|
53 |
|
{ |
|
137
|
51 |
|
return true; |
|
138
|
|
|
} |
|
139
|
5 |
|
if (!file_exists($this->path)) |
|
140
|
5 |
|
{ |
|
141
|
1 |
|
if (!file_exists(self::$runtimePath)) |
|
142
|
1 |
|
{ |
|
143
|
|
|
|
|
144
|
1 |
|
if (is_writable(dirname(self::$runtimePath))) |
|
145
|
1 |
|
{ |
|
146
|
1 |
|
$this->mkdir(self::$runtimePath); |
|
147
|
1 |
|
} |
|
148
|
1 |
|
if (!is_writable(self::$runtimePath)) |
|
149
|
1 |
|
{ |
|
150
|
|
|
throw new RuntimeException(sprintf("Runtime path `%s` must exists and be writable", self::$runtimePath)); |
|
151
|
|
|
} |
|
152
|
1 |
|
} |
|
153
|
1 |
|
if (is_writable(self::$runtimePath)) |
|
154
|
1 |
|
{ |
|
155
|
1 |
|
$this->mkdir($this->path); |
|
156
|
1 |
|
} |
|
157
|
1 |
|
if (!is_writable($this->path)) |
|
158
|
1 |
|
{ |
|
159
|
|
|
throw new RuntimeException(sprintf("Addendum runtime path `%s` must exists and be writable", $this->path)); |
|
160
|
|
|
} |
|
161
|
1 |
|
} |
|
162
|
5 |
|
if (!file_exists($fileDir)) |
|
163
|
5 |
|
{ |
|
164
|
2 |
|
$this->mkdir($fileDir); |
|
165
|
2 |
|
} |
|
166
|
5 |
|
self::$prepared[$fileDir] = true; |
|
167
|
5 |
|
} |
|
168
|
|
|
|
|
169
|
51 |
|
public function get() |
|
170
|
|
|
{ |
|
171
|
51 |
|
$this->prepare(); |
|
172
|
51 |
|
$fileName = $this->getFilename(); |
|
173
|
|
|
|
|
174
|
51 |
|
if (!NsCache::$addeNs) |
|
|
|
|
|
|
175
|
51 |
|
{ |
|
176
|
|
|
// codecept_debug('Skip cache check'); |
|
177
|
|
|
// var_dump('Will skip cache check'); |
|
178
|
|
|
// exit; |
|
179
|
51 |
|
} |
|
180
|
51 |
|
if (NsCache::$addeNs && !$this->nsCache->valid()) |
|
181
|
51 |
|
{ |
|
182
|
1 |
|
$this->clearCurrentPath(); |
|
183
|
1 |
|
return false; |
|
184
|
|
|
} |
|
185
|
51 |
|
$key = $this->getCacheKey(); |
|
186
|
51 |
|
if (isset(self::$cache[$key])) |
|
187
|
51 |
|
{ |
|
188
|
51 |
|
return self::$cache[$key]; |
|
189
|
|
|
} |
|
190
|
|
|
|
|
191
|
46 |
|
$data = SoftIncluder::includeFile($fileName); |
|
192
|
|
|
|
|
193
|
|
|
// Only false means not existing cache. |
|
194
|
|
|
// NOTE: Cache might have valid `empty` value, ie. empty array. |
|
195
|
46 |
|
if (false === $data) |
|
196
|
46 |
|
{ |
|
197
|
46 |
|
return false; |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
// Purge file cache if checkMTime is enabled and file obsolete |
|
201
|
|
|
if ($this->addendum->checkMTime && file_exists($fileName)) |
|
202
|
|
|
{ |
|
203
|
|
|
$cacheTime = filemtime($fileName); |
|
204
|
|
|
|
|
205
|
|
|
// Partial component name, split by @ and take first argument |
|
206
|
|
|
if (is_string($this->component) && strstr($this->component, '@')) |
|
207
|
|
|
{ |
|
208
|
|
|
$parts = explode('@', $this->component); |
|
209
|
|
|
$componentClass = array_shift($parts); |
|
210
|
|
|
} |
|
211
|
|
|
else |
|
212
|
|
|
{ |
|
213
|
|
|
$componentClass = $this->component; |
|
214
|
|
|
} |
|
215
|
|
|
$componentTime = filemtime((new ReflectionClass($componentClass))->getFileName()); |
|
216
|
|
|
if ($componentTime > $cacheTime) |
|
217
|
|
|
{ |
|
218
|
|
|
$this->remove(); |
|
219
|
|
|
return false; |
|
220
|
|
|
} |
|
221
|
|
|
} |
|
222
|
|
|
self::$cache[$key] = $data; |
|
223
|
|
|
return $data; |
|
224
|
|
|
} |
|
225
|
|
|
|
|
226
|
46 |
|
public function set($data) |
|
227
|
|
|
{ |
|
228
|
46 |
|
$fileName = $this->getFilename(); |
|
229
|
46 |
|
$this->prepare(); |
|
230
|
46 |
|
$key = $this->getCacheKey(); |
|
231
|
46 |
|
self::$cache[$key] = $data; |
|
232
|
|
|
|
|
233
|
46 |
|
file_put_contents($fileName, PhpExporter::export($data)); |
|
234
|
46 |
|
@chmod($fileName, 0666); |
|
235
|
46 |
|
$this->nsCache->set(); |
|
236
|
46 |
|
return $data; |
|
237
|
|
|
} |
|
238
|
|
|
|
|
239
|
|
|
public function remove() |
|
240
|
|
|
{ |
|
241
|
|
|
$fileName = $this->getFilename(); |
|
242
|
|
|
$key = $this->getCacheKey(); |
|
243
|
|
|
unset(self::$cache[$key]); |
|
244
|
|
|
if (file_exists($fileName)) |
|
245
|
|
|
{ |
|
246
|
|
|
return unlink($fileName); |
|
247
|
|
|
} |
|
248
|
|
|
return false; |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
/** |
|
252
|
|
|
* Clear entire cache |
|
253
|
|
|
* @return boolean |
|
254
|
|
|
*/ |
|
255
|
3 |
|
public function clear() |
|
256
|
|
|
{ |
|
257
|
3 |
|
self::$prepared = []; |
|
258
|
3 |
|
return $this->clearPath($this->path); |
|
259
|
|
|
} |
|
260
|
|
|
|
|
261
|
1 |
|
private function clearCurrentPath() |
|
262
|
|
|
{ |
|
263
|
1 |
|
$path = dirname($this->getFilename()); |
|
264
|
1 |
|
return $this->clearPath($path); |
|
265
|
|
|
} |
|
266
|
|
|
|
|
267
|
4 |
|
private function clearPath($path) |
|
268
|
|
|
{ |
|
269
|
4 |
|
if (!file_exists($path)) |
|
270
|
4 |
|
{ |
|
271
|
|
|
return false; |
|
272
|
|
|
} |
|
273
|
4 |
|
foreach (new \DirectoryIterator($path) as $dir) |
|
274
|
|
|
{ |
|
275
|
4 |
|
if ($dir->isDot() || !$dir->isDir()) |
|
276
|
4 |
|
{ |
|
277
|
4 |
|
continue; |
|
278
|
|
|
} |
|
279
|
3 |
|
foreach (new \DirectoryIterator($dir->getPathname()) as $file) |
|
280
|
|
|
{ |
|
281
|
3 |
|
if (!$file->isFile()) |
|
282
|
3 |
|
{ |
|
283
|
3 |
|
continue; |
|
284
|
|
|
} |
|
285
|
|
|
|
|
286
|
|
|
// Skip ns cache file, or it might regenerate over and over |
|
287
|
|
|
// ns file cache is replaced when needed by NsCache |
|
288
|
2 |
|
if ($file->getBasename() === NsCache::FileName) |
|
289
|
2 |
|
{ |
|
290
|
2 |
|
continue; |
|
291
|
|
|
} |
|
292
|
1 |
|
unlink($file->getPathname()); |
|
293
|
3 |
|
} |
|
294
|
4 |
|
} |
|
295
|
4 |
|
self::$prepared[$path] = false; |
|
296
|
4 |
|
} |
|
297
|
|
|
|
|
298
|
53 |
|
private function getFilename() |
|
299
|
|
|
{ |
|
300
|
53 |
|
if (!empty($this->fileName)) |
|
301
|
53 |
|
{ |
|
302
|
53 |
|
return $this->fileName; |
|
303
|
|
|
} |
|
304
|
53 |
|
if (is_object($this->component)) |
|
305
|
53 |
|
{ |
|
306
|
33 |
|
$className = get_class($this->component); |
|
307
|
33 |
|
} |
|
308
|
|
|
else |
|
309
|
|
|
{ |
|
310
|
49 |
|
$className = $this->component; |
|
311
|
|
|
} |
|
312
|
53 |
|
$this->fileName = sprintf('%s/%s@%s/%s.php', $this->path, $this->classToFile($this->metaClass), $this->instanceId, str_replace('\\', '/', $this->classToFile($className))); |
|
313
|
53 |
|
return $this->fileName; |
|
314
|
|
|
} |
|
315
|
|
|
|
|
316
|
51 |
|
private function getCacheKey() |
|
317
|
|
|
{ |
|
318
|
51 |
|
if (is_object($this->component)) |
|
319
|
51 |
|
{ |
|
320
|
33 |
|
$className = get_class($this->component); |
|
321
|
33 |
|
} |
|
322
|
|
|
else |
|
323
|
|
|
{ |
|
324
|
47 |
|
$className = $this->component; |
|
325
|
|
|
} |
|
326
|
51 |
|
return sprintf('%s@%s@%s@%s.php', $this->classToFile(static::class), $this->classToFile($this->metaClass), $this->instanceId, str_replace('\\', '/', $this->classToFile($className))); |
|
327
|
|
|
} |
|
328
|
|
|
|
|
329
|
|
|
/** |
|
330
|
|
|
* Convert slash separated class name to dot separated name. |
|
331
|
|
|
* @param string $className |
|
332
|
|
|
* @return string |
|
333
|
|
|
*/ |
|
334
|
53 |
|
private function classToFile($className) |
|
335
|
|
|
{ |
|
336
|
53 |
|
return str_replace('\\', '.', $className); |
|
337
|
|
|
} |
|
338
|
|
|
|
|
339
|
|
|
/** |
|
340
|
|
|
* Recursively create dir with proper permissions. |
|
341
|
|
|
* |
|
342
|
|
|
* @param string $path |
|
343
|
|
|
*/ |
|
344
|
2 |
|
private function mkdir($path) |
|
345
|
|
|
{ |
|
346
|
2 |
|
$mask = umask(0000); |
|
347
|
2 |
|
mkdir($path, 0777, true); |
|
348
|
2 |
|
umask($mask); |
|
349
|
2 |
|
} |
|
350
|
|
|
|
|
351
|
|
|
} |
|
352
|
|
|
|
This check looks for the bodies of
ifstatements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.These
ifbodies can be removed. If you have an empty if but statements in theelsebranch, consider inverting the condition.could be turned into
This is much more concise to read.