1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Cachearium; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Abstract class for caches |
7
|
|
|
* |
8
|
|
|
*/ |
9
|
|
|
abstract class CacheAbstract { |
10
|
|
|
/** |
11
|
|
|
* Controls debug on html page for all Cache backends. |
12
|
|
|
* @var boolean |
13
|
|
|
*/ |
14
|
|
|
public static $debugOnPage = false; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Controls debug to a file |
18
|
|
|
* @var string the file name, or null if file debug is off. |
19
|
|
|
*/ |
20
|
|
|
public static $debugLogFile = null; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Is this cache enabled? |
24
|
|
|
* @var boolean $enabled |
25
|
|
|
*/ |
26
|
|
|
protected $enabled = true; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Holds recursive data for start()/end(). Array of CacheData |
30
|
|
|
* |
31
|
|
|
* @var array |
32
|
|
|
*/ |
33
|
|
|
private $loopdata = array(); |
34
|
|
|
|
35
|
|
|
private $inloop = 0; |
36
|
|
|
protected $lifetime = 0; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* This is a namespace string to avoid clashes with other instances of this application. |
40
|
|
|
* Initialize it to a unique string. If you are not running multiple instances, ignore. |
41
|
|
|
* |
42
|
|
|
* @var string |
43
|
|
|
*/ |
44
|
|
|
protected $namespace = ""; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Array for basic cache profiling. Keys are CacheLogEnum, values are counters. |
48
|
|
|
* |
49
|
|
|
* @var array |
50
|
|
|
*/ |
51
|
|
|
static protected $summary = array( |
52
|
|
|
CacheLogEnum::ACCESSED => 0, |
53
|
|
|
CacheLogEnum::MISSED => 0, |
54
|
|
|
CacheLogEnum::DELETED => 0, |
55
|
|
|
CacheLogEnum::CLEANED => 0, |
56
|
|
|
CacheLogEnum::SAVED => 0, |
57
|
|
|
CacheLogEnum::PREFETCHED => 0, |
58
|
|
|
); |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Stores cache log for debugging. |
62
|
|
|
* @var array |
63
|
|
|
*/ |
64
|
|
|
protected $cache_log = array(); |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Is log enabled? Log can take a lot of RAM, so only turn this on when |
68
|
|
|
* profiling. |
69
|
|
|
* @var boolean $should_log |
70
|
|
|
*/ |
71
|
|
|
protected $should_log = false; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Returns basic cache statistics. See $summary. |
75
|
|
|
* |
76
|
|
|
* @return array() |
77
|
|
|
*/ |
78
|
|
|
public static function getLogSummary() { |
79
|
|
|
return static::$summary; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
public static function resetLogSummary() { |
83
|
|
|
static::$summary = array( |
84
|
|
|
CacheLogEnum::ACCESSED => 0, |
85
|
|
|
CacheLogEnum::MISSED => 0, |
86
|
|
|
CacheLogEnum::DELETED => 0, |
87
|
|
|
CacheLogEnum::CLEANED => 0, |
88
|
|
|
CacheLogEnum::SAVED => 0, |
89
|
|
|
CacheLogEnum::PREFETCHED => 0, |
90
|
|
|
); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* |
95
|
|
|
* @param boolean $b |
96
|
|
|
* @return CacheAbstract |
97
|
|
|
*/ |
98
|
|
|
public function setLog($b) { |
99
|
|
|
$this->should_log = $b; |
100
|
|
|
return $this; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Returns a cache |
105
|
|
|
* |
106
|
|
|
* @param string $backend |
107
|
|
|
* @throws Cachearium\Exceptions\CacheInvalidBackendException |
108
|
|
|
* @return CacheAbstract |
109
|
|
|
*/ |
110
|
|
|
public static function factory($backend) { |
111
|
|
|
$classname = '\Cachearium\Backend\Cache' . $backend; |
112
|
|
|
if (!class_exists($classname)) { |
113
|
|
|
throw new Exceptions\CacheInvalidBackendException("Class does not exist"); |
114
|
|
|
} |
115
|
|
|
return $classname::singleton(); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Clears all cache classes. |
120
|
|
|
* @codeCoverageIgnore |
121
|
|
|
*/ |
122
|
|
|
public static function clearAll() { |
123
|
|
|
$caches = [ |
124
|
|
|
\Cachearium\Backend\CacheRAM::singleton(), |
125
|
|
|
\Cachearium\Backend\CacheFilesystem::singleton(), |
126
|
|
|
\Cachearium\Backend\CacheMemcached::singleton(), |
127
|
|
|
// TODO cache apc is broken \Cachearium\Backend\CacheAPC::singleton() |
128
|
|
|
]; |
129
|
|
|
foreach($caches as $cacheInst) { |
130
|
|
|
if ($cacheInst->isEnabled()) { |
131
|
|
|
$cacheInst->clear(); |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Enable this cache |
138
|
|
|
* |
139
|
|
|
* @return CacheAbstract this |
140
|
|
|
*/ |
141
|
|
|
final public function setEnabled($b) { |
142
|
|
|
if ($b) { |
143
|
|
|
$this->enable(); |
144
|
|
|
} |
145
|
|
|
else { |
146
|
|
|
$this->disable(); |
147
|
|
|
} |
148
|
|
|
return $this; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Enable this cache |
153
|
|
|
* |
154
|
|
|
* @return CacheAbstract this |
155
|
|
|
*/ |
156
|
|
|
public function enable() { |
157
|
|
|
$this->enabled = true; |
158
|
|
|
return $this; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Disable this cache |
163
|
|
|
* |
164
|
|
|
* @return CacheAbstract |
165
|
|
|
*/ |
166
|
|
|
public function disable() { |
167
|
|
|
$this->enabled = false; |
168
|
|
|
return $this; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* @return True if cache is enabled, working and storing/retrieving data. |
173
|
|
|
*/ |
174
|
|
|
public function isEnabled() { |
175
|
|
|
return $this->enabled; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* |
180
|
|
|
* @param number $lifetime 0 for infinite |
181
|
|
|
*/ |
182
|
|
|
public function setDefaultLifetime($lifetime = 0) { |
183
|
|
|
$this->lifetime = $lifetime; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
public function getDefaultLifetime() { |
187
|
|
|
return $this->lifetime; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* @param string $name An optional namespace. |
192
|
|
|
*/ |
193
|
|
|
public function setNamespace($name) { |
194
|
|
|
$this->namespace = $name; |
195
|
|
|
return $this; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* @return string |
200
|
|
|
*/ |
201
|
|
|
public function getNamespace() { |
202
|
|
|
return $this->namespace; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Get cached entry. |
207
|
|
|
* |
208
|
|
|
* @param $k |
209
|
|
|
* @return mixed |
210
|
|
|
* @throws Cachearium\Exceptions\NotCachedException |
211
|
|
|
*/ |
212
|
|
|
abstract public function get(CacheKey $k); |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Same as get(), but expanded parameters. |
216
|
|
|
* |
217
|
|
|
* @param string $base |
218
|
|
|
* @param string $id |
219
|
|
|
* @param mixed $sub |
220
|
|
|
* @return mixed |
221
|
|
|
* @throws Cachearium\Exceptions\NotCachedException |
222
|
|
|
* @see getK |
223
|
|
|
*/ |
224
|
|
|
public function getP($base, $id, $sub = null) { |
225
|
|
|
return $this->get(new CacheKey($base, $id, $sub)); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* Same as get, but assumes data was stored with a CacheData object |
230
|
|
|
* and will treat it accordingly. |
231
|
|
|
* |
232
|
|
|
* @param CacheKey $k |
233
|
|
|
* @return CacheData |
234
|
|
|
* @throws Cachearium\Exceptions\NotCachedException |
235
|
|
|
*/ |
236
|
|
|
public function getData(CacheKey $k) { |
237
|
|
|
$cd = CacheData::unserialize($this->get($k)); |
238
|
|
|
if ($cd->checkUpdateToDate($this)) { |
239
|
|
|
return $cd; |
240
|
|
|
} |
241
|
|
|
throw new Exceptions\NotCachedException(); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Same as getData(), but expanded parameters. |
246
|
|
|
* |
247
|
|
|
* @see getData() |
248
|
|
|
* @param string $base |
249
|
|
|
* @param string $id |
250
|
|
|
* @param mixed $sub |
251
|
|
|
*/ |
252
|
|
|
public function getDataP($base, $id, $sub = null) { |
253
|
|
|
return $this->getData(new CacheKey($base, $id, $sub)); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* Gets data from multiple cache keys at once |
258
|
|
|
* |
259
|
|
|
* Backends may override this to provide an efficient implementation over multiple |
260
|
|
|
* calls to get(). |
261
|
|
|
* |
262
|
|
|
* @param array $cacheid List of cache keys |
263
|
|
|
* @param callable $callback if present will be called for any \NotCachedExceptions. |
264
|
|
|
* Callback should have this signature: (CacheAbstract $c, CacheKey $k) |
265
|
|
|
* @return array:mixed array with data, using same keys as cacheid. Keys not |
266
|
|
|
* found in cache won't be present, but no exception will be thrown |
267
|
|
|
*/ |
268
|
|
|
public function getMulti(array $cacheid, $callback = null) { |
269
|
|
|
$retval = []; |
270
|
|
|
foreach ($cacheid as $k => $c) { |
271
|
|
|
try { |
272
|
|
|
$retval[$k] = $this->get($c); |
273
|
|
|
} |
274
|
|
|
catch (Exceptions\NotCachedException $e) { |
275
|
|
|
// if there is a callback, call it |
276
|
|
|
if ($callback) { |
277
|
|
|
$retval[$k] = call_user_func($callback, $this, $c); |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
} |
281
|
|
|
return $retval; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* Increment a variable. Backend deals with this, but in general this is atomic. |
286
|
|
|
* Backend must only guarantee that the increment is made, but the final value |
287
|
|
|
* may not be current + $value due to concurrent accesses. |
288
|
|
|
* |
289
|
|
|
* @param integer $value |
290
|
|
|
* @param CacheKey $k |
291
|
|
|
* @param integer $default If key is not in cache, this value is returned. |
292
|
|
|
* @return integer |
293
|
|
|
*/ |
294
|
|
|
abstract public function increment($value, CacheKey $k, $default = 0); |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Invalidates a dependency index. If the index does not exist it is created. |
298
|
|
|
* @param CacheKey $k |
299
|
|
|
*/ |
300
|
|
|
public function invalidate(CacheKey $k) { |
301
|
|
|
return $this->increment(1, $k, 0); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Saves data in cache. |
306
|
|
|
* |
307
|
|
|
* @param mixed $data Data to save in cache |
308
|
|
|
* @param CacheKey $k |
309
|
|
|
* @param integer $lifetime The lifetime in sceonds, although it is up to the implementation whether |
310
|
|
|
* it is respected or not. |
311
|
|
|
* @return boolean true if no problem |
312
|
|
|
*/ |
313
|
|
|
abstract public function store($data, CacheKey $k, $lifetime = 0); |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Same as store() but expanded parameters |
317
|
|
|
* |
318
|
|
|
* @param mixed $data |
319
|
|
|
* @param string $base |
320
|
|
|
* @param string $sub |
321
|
|
|
* @param string $id |
322
|
|
|
* @param number $lifetime |
323
|
|
|
* @return boolean true if no problem |
324
|
|
|
* @see store() |
325
|
|
|
*/ |
326
|
|
|
public function storeP($data, $base, $id, $sub = null, $lifetime = 0) { |
327
|
|
|
return $this->store($data, new CacheKey($base, $id, $sub), $lifetime); |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* Same as store() but expanded parameters |
332
|
|
|
* |
333
|
|
|
* @param CacheData $data |
334
|
|
|
* @param number $lifetime |
335
|
|
|
* @return boolean true if no problem |
336
|
|
|
* @see store() |
337
|
|
|
*/ |
338
|
|
|
public function storeData(CacheData $data, $lifetime = 0) { |
339
|
|
|
return $this->store($data->updateDependenciesHash($this)->serialize(), $data->key, $lifetime); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* Deletes an entry from the cache |
344
|
|
|
* |
345
|
|
|
* @param CacheKey $k |
346
|
|
|
* @return boolean |
347
|
|
|
*/ |
348
|
|
|
abstract public function delete(CacheKey $k); |
349
|
|
|
|
350
|
|
|
/** |
351
|
|
|
* @see delete() |
352
|
|
|
* @param string $base |
353
|
|
|
* @param string $id |
354
|
|
|
* @param mixed $sub |
355
|
|
|
*/ |
356
|
|
|
public function deleteP($base, $id, $sub = null) { |
357
|
|
|
return $this->delete(new CacheKey($base, $id, $sub)); |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* Cleans cache: all entries with a certain $base and $id in the $key |
362
|
|
|
* are deleted. |
363
|
|
|
* |
364
|
|
|
* @param CacheKey $k |
365
|
|
|
* @return boolean true if no problem |
366
|
|
|
*/ |
367
|
|
|
public function clean(CacheKey $k) { |
368
|
|
|
return $this->cleanP($k->getBase(), $k->getId()); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Cleans cache: all entries with a certain $base and $id |
373
|
|
|
* |
374
|
|
|
* @return boolean true if no problem |
375
|
|
|
*/ |
376
|
|
|
abstract public function cleanP($base, $id); |
377
|
|
|
|
378
|
|
|
/** |
379
|
|
|
* Clears entire cache. Use sparingly. |
380
|
|
|
*/ |
381
|
|
|
abstract public function clear(); |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Prefetches data which will be used. This avoids multiple trips to the cache |
385
|
|
|
* server if they can be avoided. |
386
|
|
|
* |
387
|
|
|
* Backend may ignore this call and implement a noop. |
388
|
|
|
* |
389
|
|
|
* @param array $data array(0 => CacheKey, ...) |
390
|
|
|
*/ |
391
|
|
|
abstract public function prefetch($data); |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* Generates a report for this backend |
395
|
|
|
* |
396
|
|
|
* @codeCoverageIgnore |
397
|
|
|
*/ |
398
|
|
|
abstract public function report(); |
399
|
|
|
|
400
|
|
|
/** |
401
|
|
|
* Starts a cache if it doesn't exist, or outputs the data and returns true. |
402
|
|
|
* Calls extraSub(). |
403
|
|
|
* |
404
|
|
|
* @param CacheKey $k |
405
|
|
|
* @param string $lifetime The lifetime, in seconds |
406
|
|
|
* @param boolean $print if True echoes the data |
407
|
|
|
* @param boolean $fail if false throws an exception if something happens, such |
408
|
|
|
* as not cached |
409
|
|
|
* @return boolean|string True if cached |
410
|
|
|
* @review |
411
|
|
|
*/ |
412
|
|
|
public function start(CacheKey $k, $lifetime = null, $print = true, $fail = false) { |
413
|
|
|
$this->extraSub($k->sub); |
414
|
|
|
|
415
|
|
|
return $this->recursiveStart($k, $lifetime, $print, $fail); |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
/** |
419
|
|
|
* @see recursiveStart() |
420
|
|
|
*/ |
421
|
|
|
public function recursiveStartP($base, $id, $sub = null, $lifetime = null, $print = true, $fail = false) { |
422
|
|
|
return $this->recursivestart(new CacheKey($base, $id, $sub), $lifetime, $print, $fail); |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* @see start() |
427
|
|
|
*/ |
428
|
|
|
public function startP($base, $id, $sub = null, $lifetime = null, $print = true, $fail = false) { |
429
|
|
|
return $this->start(new CacheKey($base, $id, $sub), $lifetime, $print, $fail); |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
/** |
433
|
|
|
* start() using a callable. Same as start()/c()/end(). |
434
|
|
|
* |
435
|
|
|
* @param CacheKey $k |
436
|
|
|
* @param callable $c A callable. Whatever it prints will be cached. |
437
|
|
|
* @param array $cparams parameters for the callback, optional |
438
|
|
|
* @param integer $lifetime |
439
|
|
|
*/ |
440
|
|
|
public function startCallback(CacheKey $k, callable $c, array $cparams = [], $lifetime = null) { |
441
|
|
|
$data = $this->start($k, $lifetime); |
442
|
|
|
if ($data === false) { |
443
|
|
|
call_user_func_array($c, $cparams); |
444
|
|
|
$data = $this->end(false); |
445
|
|
|
} |
446
|
|
|
return $data; |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* Appends a callback to the current start()/end() cache |
451
|
|
|
* |
452
|
|
|
* Callbacks are always called at runtime, their result is never cached at |
453
|
|
|
* this level. You may cache it in the callback, of course. |
454
|
|
|
* |
455
|
|
|
* @param function $callback |
456
|
|
|
* @return boolean |
457
|
|
|
* @review |
458
|
|
|
*/ |
459
|
|
|
public function appendCallback(callable $callback) { |
460
|
|
|
// @codeCoverageIgnoreStart |
461
|
|
|
if (!$this->enabled) { |
462
|
|
|
return false; |
463
|
|
|
} |
464
|
|
|
// @codeCoverageIgnoreEnd |
465
|
|
|
|
466
|
|
|
if (!$this->inloop) { |
467
|
|
|
return false; |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
$data = ob_get_contents(); |
471
|
|
|
ob_clean(); |
472
|
|
|
$this->loopdata[$this->inloop]->appendData($data); |
473
|
|
|
$this->loopdata[$this->inloop]->appendCallback($callback); |
474
|
|
|
|
475
|
|
|
return true; |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
/** |
479
|
|
|
* Returns a key given parameters. This is up to storage and different |
480
|
|
|
* values may be returned for the same parameters, as storages are likely |
481
|
|
|
* to use key-based cache expiration. |
482
|
|
|
* |
483
|
|
|
* @param CacheKey $k |
484
|
|
|
*/ |
485
|
|
|
abstract protected function hashKey(CacheKey $k); |
486
|
|
|
|
487
|
|
|
protected function keyFromDeps(CacheKey $k, $deps) { |
488
|
|
|
$mainkey = $this->hashKey($k); |
489
|
|
|
foreach ($deps as $d) { // TODO: arrays are ugly |
490
|
|
|
$mainkey .= $this->hashKey($d); // TODO: one fetch for all |
491
|
|
|
} |
492
|
|
|
$mainkey = md5($mainkey); |
493
|
|
|
return $mainkey; |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* Get extra sub |
498
|
|
|
* @param unknown $sub |
499
|
|
|
*/ |
500
|
|
|
private function extraSub(&$sub) { |
501
|
|
|
if (!is_callable('application_cacheDependencies')) { |
502
|
|
|
return; |
503
|
|
|
} |
504
|
|
|
$extra = application_cacheDependencies(); |
505
|
|
|
if (is_array($sub)) { |
506
|
|
|
$sub['cacheExtraSubApplication'] = $extra; |
507
|
|
|
} |
508
|
|
|
else { |
509
|
|
|
$sub .= $extra; |
510
|
|
|
} |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
public function newstart(CacheKey $k, $lifetime = null, $fail = false) { |
514
|
|
|
// @codeCoverageIgnoreStart |
515
|
|
|
if (!$this->enabled) { |
516
|
|
|
return false; |
517
|
|
|
} |
518
|
|
|
// @codeCoverageIgnoreEnd |
519
|
|
|
|
520
|
|
|
// fetch cache |
521
|
|
|
try { |
522
|
|
|
$cachedata = $this->getData($k); |
523
|
|
|
} catch (Exceptions\NotCachedException $e) { |
524
|
|
|
// not cached |
525
|
|
|
if ($fail) { |
526
|
|
|
throw $e; |
527
|
|
|
} |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
$this->inloop++; |
531
|
|
|
$this->loopdata[$this->inloop] = new CacheData(); |
532
|
|
|
if ($this->inloop > 1) { |
533
|
|
|
// we are recursive. push whatever we have so far in the previous cache |
534
|
|
|
$data = ob_get_contents(); |
535
|
|
|
ob_clean(); |
536
|
|
|
$this->loopdata[$this->inloop - 1]->appendData($data); |
537
|
|
|
$this->loopdata[$this->inloop - 1]->appendRecursion($k); |
538
|
|
|
} |
539
|
|
|
else { |
|
|
|
|
540
|
|
|
// something was not cached below. We invalidated all cache |
541
|
|
|
// dependencies |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
$this->loopdata[$this->inloop]->setKey($k); |
545
|
|
|
$this->loopdata[$this->inloop]->lifetime = $lifetime ? $lifetime : $this->lifetime; |
546
|
|
|
|
547
|
|
|
ob_start(); |
548
|
|
|
ob_implicit_flush(false); |
549
|
|
|
|
550
|
|
|
return false; |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
public function newEnd($print = true) { |
554
|
|
|
// @codeCoverageIgnoreStart |
555
|
|
|
if (!$this->enabled) { |
556
|
|
|
return false; |
557
|
|
|
} |
558
|
|
|
// @codeCoverageIgnoreEnd |
559
|
|
|
|
560
|
|
|
$data = ob_get_clean(); |
561
|
|
|
|
562
|
|
|
/* @var $cachedata CacheData */ |
563
|
|
|
$cachedata = $this->loopdata[$this->inloop]; |
564
|
|
|
$cachedata->appendData($data); |
565
|
|
|
|
566
|
|
|
$cachedata->generateDependenciesHash($this); |
567
|
|
|
$mainkey = $this->keyFromDeps($cachedata->getKey(), $cachedata->dependencies); |
568
|
|
|
if (!$this->storeP($cachedata, 'cacherecursive', 0, $mainkey)) { |
569
|
|
|
throw new \Cachearium\Exceptions\CacheStoreFailure("Storing key"); |
570
|
|
|
} |
571
|
|
|
if (!$this->storeData($cachedata)) { |
572
|
|
|
throw new \Cachearium\Exceptions\CacheStoreFailure("Storing data"); |
573
|
|
|
} |
574
|
|
|
|
575
|
|
|
// if recursive |
576
|
|
|
$this->inloop--; |
577
|
|
|
if ($this->inloop > 0) { |
578
|
|
|
return false; |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
if ($print) { |
582
|
|
|
$key = "cache-" . rand(); |
583
|
|
|
// @codeCoverageIgnoreStart |
584
|
|
|
if (static::$debugOnPage) { |
585
|
|
|
echo '<span class="debug-probe-begin" |
586
|
|
|
data-key="' . $key . |
587
|
|
|
'" data-base="' . $cachedata->getKey()->base . |
588
|
|
|
'" data-id="' . $cachedata->getKey()->id . |
589
|
|
|
'" data-sub="' . print_r($cachedata->getKey()->sub, true) . |
590
|
|
|
'" data-lifetime="' . $cachedata->lifetime . |
591
|
|
|
'" data-backend="' . get_class($this) . |
592
|
|
|
'" data-type="save"></span>'; |
593
|
|
|
} |
594
|
|
|
// @codeCoverageIgnoreEnd |
595
|
|
|
|
596
|
|
|
echo $cachedata->stringify($this); |
597
|
|
|
|
598
|
|
|
// @codeCoverageIgnoreStart |
599
|
|
|
if (static::$debugOnPage) { |
600
|
|
|
echo '<span class="debug-probe-end" data-key="' . $key . '"></span>'; |
601
|
|
|
} |
602
|
|
|
// @codeCoverageIgnoreEnd |
603
|
|
|
return; |
604
|
|
|
} |
605
|
|
|
|
606
|
|
|
return $cachedata->stringify($this); |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
/** |
610
|
|
|
* Prints HTML for cache debug probes -> opens tag |
611
|
|
|
* |
612
|
|
|
* @param string $key |
613
|
|
|
* @param CacheData $cachedata |
614
|
|
|
* @param string $type |
615
|
|
|
* @codeCoverageIgnore |
616
|
|
|
*/ |
617
|
|
|
protected function printProbeStart($key, CacheData $cachedata, $type) { |
618
|
|
|
echo '<span class="cachearium-debug-probe-begin" |
619
|
|
|
data-key="' . $key . |
620
|
|
|
'" data-base="' . $cachedata->getKey()->base . |
621
|
|
|
'" data-id="' . $cachedata->getKey()->id . |
622
|
|
|
'" data-sub="' . print_r($cachedata->getKey()->sub, true) . |
623
|
|
|
'" data-lifetime="' . $cachedata->lifetime . |
624
|
|
|
'" data-backend="' . get_class($this) . |
625
|
|
|
'" data-type="' . $type . '"></span>'; |
626
|
|
|
} |
627
|
|
|
|
628
|
|
|
/** |
629
|
|
|
* Prints HTML for cache debug probes -> closes tag |
630
|
|
|
* |
631
|
|
|
* @param string $key |
632
|
|
|
* @param CacheData $cachedata |
633
|
|
|
* @SuppressWarnings(PHPMD.UnusedFormalParameter) |
634
|
|
|
* @codeCoverageIgnore |
635
|
|
|
*/ |
636
|
|
|
protected function printProbeEnd($key, CacheData $cachedata) { |
637
|
|
|
echo '<span class="cachearium-debug-probe-end" data-key="' . $key . '"></span>'; |
638
|
|
|
} |
639
|
|
|
|
640
|
|
|
/** |
641
|
|
|
* |
642
|
|
|
* @param CacheKey $k |
643
|
|
|
* @param integer $lifetime if null uses the class default |
644
|
|
|
* @param boolean $print |
645
|
|
|
* @param boolean $fail if true throws a NotCachedException if not cached. |
646
|
|
|
* @throws Cachearium\Exceptions\NotCachedException |
647
|
|
|
* @throws Cachearium\Exceptions\CacheKeyClashException |
648
|
|
|
* @return string The cached item as a string or false if not cached. |
649
|
|
|
*/ |
650
|
|
|
public function recursiveStart(CacheKey $k, $lifetime = null, $print = true, $fail = false) { |
651
|
|
|
// @codeCoverageIgnoreStart |
652
|
|
|
if (!$this->enabled) { |
653
|
|
|
return false; |
654
|
|
|
} |
655
|
|
|
// @codeCoverageIgnoreEnd |
656
|
|
|
|
657
|
|
|
foreach ($this->loopdata as $l) { |
658
|
|
|
/* @var $l CacheData */ |
659
|
|
|
if ($l->checkClash($k)) { |
660
|
|
|
throw new Exceptions\CacheKeyClashException(); |
661
|
|
|
} |
662
|
|
|
} |
663
|
|
|
|
664
|
|
|
// check if we are inside another cache for automatic dependencies. |
665
|
|
|
/* @var $cachedata CacheData */ |
666
|
|
|
$cachedata = null; |
667
|
|
|
try { |
668
|
|
|
$cachedata = $this->getData($k); |
669
|
|
|
|
670
|
|
|
if (!$cachedata->checkUpdateToDate($this)) { |
671
|
|
|
// stale |
672
|
|
|
$cachedata = null; |
673
|
|
|
} |
674
|
|
|
// TODO $this->prefetch($cachedata->getDependencies()); |
675
|
|
|
} |
676
|
|
|
catch (Exceptions\NotCachedException $e) { |
677
|
|
|
} |
678
|
|
|
|
679
|
|
|
// found. just return it. |
680
|
|
|
if ($cachedata) { |
681
|
|
|
try { |
682
|
|
|
$this->log( |
683
|
|
|
CacheLogEnum::ACCESSED, |
684
|
|
|
$cachedata->key, |
685
|
|
|
$cachedata->lifetime |
686
|
|
|
); |
687
|
|
|
$key = "cache-" . rand(); |
688
|
|
|
|
689
|
|
|
$retval = $cachedata->stringify($this); |
690
|
|
|
|
691
|
|
|
if ($print) { |
692
|
|
|
// @codeCoverageIgnoreStart |
693
|
|
|
if (static::$debugOnPage) { |
694
|
|
|
$this->printProbeStart($key, $cachedata, 'hit'); |
695
|
|
|
} |
696
|
|
|
// @codeCoverageIgnoreEnd |
697
|
|
|
|
698
|
|
|
echo $retval; |
699
|
|
|
|
700
|
|
|
// @codeCoverageIgnoreStart |
701
|
|
|
if (static::$debugOnPage) { |
702
|
|
|
$this->printProbeEnd($key, $cachedata); |
703
|
|
|
} |
704
|
|
|
// @codeCoverageIgnoreEnd |
705
|
|
|
} |
706
|
|
|
return $retval; |
707
|
|
|
} |
708
|
|
|
catch (Exceptions\NotCachedException $e) { |
709
|
|
|
$this->delete($k); // clear recursively |
710
|
|
|
if ($this->inloop) { |
711
|
|
|
throw $e; |
712
|
|
|
} |
713
|
|
|
} |
714
|
|
|
} |
715
|
|
|
if ($fail) { |
716
|
|
|
throw new Exceptions\NotCachedException(); |
717
|
|
|
} |
718
|
|
|
|
719
|
|
|
$this->inloop++; |
720
|
|
|
$cd = new CacheData($k); |
721
|
|
|
$cd->setLifetime($lifetime ? $lifetime : $this->lifetime); |
722
|
|
|
$this->loopdata[$this->inloop] = $cd; |
723
|
|
|
|
724
|
|
|
if ($this->inloop > 1) { |
725
|
|
|
// we are recursive. push whatever we have so far in the previous cache |
726
|
|
|
$data = ob_get_contents(); |
727
|
|
|
ob_clean(); |
728
|
|
|
|
729
|
|
|
foreach ($this->loopdata as $l) { |
730
|
|
|
if ($l == $cd) { // don't depend on itself |
731
|
|
|
continue; |
732
|
|
|
} |
733
|
|
|
/* @var $l CacheData */ |
734
|
|
|
$l->addDependency($k); |
735
|
|
|
} |
736
|
|
|
$this->loopdata[$this->inloop - 1]->appendData($data); |
737
|
|
|
$this->loopdata[$this->inloop - 1]->appendRecursionData($cd); |
738
|
|
|
} |
739
|
|
|
else { |
|
|
|
|
740
|
|
|
// something was not cached below. We invalidated all cache |
741
|
|
|
// dependencies |
742
|
|
|
} |
743
|
|
|
|
744
|
|
|
ob_start(); |
745
|
|
|
ob_implicit_flush(false); |
746
|
|
|
|
747
|
|
|
return false; |
748
|
|
|
} |
749
|
|
|
|
750
|
|
|
/** |
751
|
|
|
* |
752
|
|
|
* @param boolean $print |
753
|
|
|
* @throws \Cachearium\Exceptions\CacheStoreFailure |
754
|
|
|
* @return string The string. If $print == true the string is printed as well. |
755
|
|
|
*/ |
756
|
|
|
public function recursiveEnd($print = true) { |
757
|
|
|
// @codeCoverageIgnoreStart |
758
|
|
|
if (!$this->enabled) { |
759
|
|
|
return ''; |
760
|
|
|
} |
761
|
|
|
// @codeCoverageIgnoreEnd |
762
|
|
|
|
763
|
|
|
$data = ob_get_clean(); |
764
|
|
|
|
765
|
|
|
/* @var $cachedata CacheData */ |
766
|
|
|
$cachedata = $this->loopdata[$this->inloop]; |
767
|
|
|
$cachedata->appendData($data); |
768
|
|
|
|
769
|
|
|
try { |
770
|
|
|
$cachedata->generateDependenciesHash($this); |
771
|
|
|
} |
772
|
|
|
catch (\Cachearium\Exceptions\CacheUnsupportedOperation $e) { |
773
|
|
|
// not much we can do here, so just keep on going |
774
|
|
|
} |
775
|
|
|
$mainkey = $this->keyFromDeps($cachedata->getKey(), $cachedata->dependencies); |
776
|
|
|
if (!$this->storeP($cachedata, 'cacherecursive', 0, $mainkey)) { |
777
|
|
|
throw new \Cachearium\Exceptions\CacheStoreFailure("Storing key"); |
778
|
|
|
} |
779
|
|
|
if (!$this->storeData($cachedata)) { |
780
|
|
|
throw new \Cachearium\Exceptions\CacheStoreFailure("Storing data"); |
781
|
|
|
} |
782
|
|
|
|
783
|
|
|
// if recursive |
784
|
|
|
unset($this->loopdata[$this->inloop]); |
785
|
|
|
$this->inloop--; |
786
|
|
|
if ($this->inloop > 0) { |
787
|
|
|
return ''; |
788
|
|
|
} |
789
|
|
|
|
790
|
|
|
if ($print) { |
791
|
|
|
$key = "cache-" . rand(); |
792
|
|
|
// @codeCoverageIgnoreStart |
793
|
|
|
if (static::$debugOnPage) { |
794
|
|
|
$this->printProbeStart($key, $cachedata, 'save'); |
795
|
|
|
} |
796
|
|
|
// @codeCoverageIgnoreEnd |
797
|
|
|
|
798
|
|
|
$str = $cachedata->stringify($this); |
799
|
|
|
echo $str; |
800
|
|
|
|
801
|
|
|
// @codeCoverageIgnoreStart |
802
|
|
|
if (static::$debugOnPage) { |
803
|
|
|
$this->printProbeEnd($key, $cachedata); |
804
|
|
|
} |
805
|
|
|
// @codeCoverageIgnoreEnd |
806
|
|
|
return $str; |
807
|
|
|
} |
808
|
|
|
|
809
|
|
|
return $cachedata->stringify($this); |
810
|
|
|
} |
811
|
|
|
|
812
|
|
|
/** |
813
|
|
|
* Ends the cache start(). |
814
|
|
|
* @see recursiveEnd() |
815
|
|
|
*/ |
816
|
|
|
public function end($print = true) { |
817
|
|
|
return $this->recursiveEnd($print); |
818
|
|
|
} |
819
|
|
|
|
820
|
|
|
/* |
821
|
|
|
* DEBUG |
822
|
|
|
*/ |
823
|
|
|
|
824
|
|
|
/** |
825
|
|
|
* High level log for testing and debugging |
826
|
|
|
* |
827
|
|
|
* @codeCoverageIgnore |
828
|
|
|
*/ |
829
|
|
|
public static function logHigh($message) { |
830
|
|
|
if (static::$debugLogFile) { |
831
|
|
|
file_put_contents(static::$debugLogFile, $message, FILE_APPEND); |
832
|
|
|
} |
833
|
|
|
} |
834
|
|
|
|
835
|
|
|
/** |
836
|
|
|
* Logs cache accesses for debugging |
837
|
|
|
* |
838
|
|
|
* @param string $status CacheLogEnum constant |
839
|
|
|
* @param CacheKey $k The message to print. |
840
|
|
|
* @param integer $lifetime |
841
|
|
|
* @codeCoverageIgnore |
842
|
|
|
*/ |
843
|
|
|
protected function log($status, CacheKey $k, $lifetime = 0) { |
844
|
|
|
static::$summary[$status]++; |
845
|
|
|
|
846
|
|
|
if ($this->should_log == false) { |
847
|
|
|
return; |
848
|
|
|
} |
849
|
|
|
|
850
|
|
|
$bt = debug_backtrace(); |
851
|
|
|
foreach ($bt as $i => $d) { |
852
|
|
|
if (strpos($d['file'], '/Cache') === false) { |
853
|
|
|
// TODO: if() may not work well if user has a file called Cache |
854
|
|
|
$trace = $d['function'] . ' at ' . $d['file'] . ':' . $d['line']; |
855
|
|
|
$this->cache_log[] = array( |
856
|
|
|
'status' => $status, |
857
|
|
|
'message' => "(" . $k->debug() . ", $lifetime) by " . $trace |
858
|
|
|
); |
859
|
|
|
break; |
860
|
|
|
} |
861
|
|
|
} |
862
|
|
|
} |
863
|
|
|
|
864
|
|
|
/** |
865
|
|
|
* Dumps a short HTML summary of the cache hits/misses |
866
|
|
|
* @codeCoverageIgnore |
867
|
|
|
*/ |
868
|
|
|
public static function dumpSummary() { |
869
|
|
|
echo '<div id="cache-summary">Cache Summary (non-ajax): '; |
870
|
|
|
foreach (static::getLogSummary() as $key => $val) { |
871
|
|
|
echo $key . '=>' . $val . ' / '; |
872
|
|
|
} |
873
|
|
|
echo '</div>'; |
874
|
|
|
} |
875
|
|
|
|
876
|
|
|
/** |
877
|
|
|
* Renders CSS for live view debugging of cached data. |
878
|
|
|
* @codeCoverageIgnore |
879
|
|
|
*/ |
880
|
|
|
public static function cssDebug() { |
881
|
|
|
?> |
882
|
|
|
[class^="cachearium-debug-probe"] { |
883
|
|
|
width: 10px; |
884
|
|
|
height: 10px; |
885
|
|
|
background-color: #f00; |
886
|
|
|
display: inline; |
887
|
|
|
/*visibility: hidden; */ |
888
|
|
|
} |
889
|
|
|
.cachearium-debug-overview { |
890
|
|
|
position: absolute; |
891
|
|
|
left: 0; |
892
|
|
|
top: 0; |
893
|
|
|
background-color: rgba(255, 255, 255, 1); |
894
|
|
|
border: 1px solid grey; |
895
|
|
|
z-index: 5000; |
896
|
|
|
} |
897
|
|
|
.cachearium-debug-view { |
898
|
|
|
position: absolute; |
899
|
|
|
pointer-events: none; |
900
|
|
|
border: 1px solid black; |
901
|
|
|
} |
902
|
|
|
|
903
|
|
|
.cachearium-debug-view[data-type="hit"] { |
904
|
|
|
background-color: rgba(0, 255, 0, 0.1); |
905
|
|
|
} |
906
|
|
|
.cachearium-debug-view[data-type="save"] { |
907
|
|
|
background-color: rgba(255, 0, 0, 0.1); |
908
|
|
|
} |
909
|
|
|
.cachearium-debug-view .cachearium-debug-view-innerdata { |
910
|
|
|
float: right; |
911
|
|
|
color: #000; |
912
|
|
|
height: 10px; |
913
|
|
|
width: 10px; |
914
|
|
|
border: 1px solid grey; |
915
|
|
|
pointer-events: auto; |
916
|
|
|
overflow: hidden; |
917
|
|
|
background-color: rgba(255, 0, 0, 0.7); |
918
|
|
|
} |
919
|
|
|
.cachearium-debug-view .cachearium-debug-view-innerdata:hover { |
920
|
|
|
width: auto; |
921
|
|
|
height: auto; |
922
|
|
|
background-color: rgba(255, 255, 255, 0.9); |
923
|
|
|
border: 1px solid grey; |
924
|
|
|
} |
925
|
|
|
<?php |
926
|
|
|
} |
927
|
|
|
|
928
|
|
|
|
929
|
|
|
/** |
930
|
|
|
* Extensive footer debug code. Shows which parts of the HTML were |
931
|
|
|
* cached or missed visually. Great! |
932
|
|
|
* @codeCoverageIgnore |
933
|
|
|
*/ |
934
|
|
|
public static function footerDebug() { |
935
|
|
|
if (!static::$debugOnPage) { |
936
|
|
|
return; |
937
|
|
|
} |
938
|
|
|
?> |
939
|
|
|
<script> |
940
|
|
|
$(function() { |
941
|
|
|
var probes = $('.cachearium-debug-probe-begin'); |
942
|
|
|
if (probes.length != $('.cachearium-debug-probe-end').length) { |
943
|
|
|
alert('Woooooooh! Cache starts do not match cache ends!'); |
944
|
|
|
} |
945
|
|
|
|
946
|
|
|
for (var i = 0; i < probes.length; i++) { |
947
|
|
|
var p = $(probes[i]); |
948
|
|
|
var end = $('.cachearium-debug-probe-end[data-key="' + p.data('key') + '"]'); |
949
|
|
|
var between = p.nextUntil(end); |
950
|
|
|
var bbox = {'top': 100000, 'left': 10000000, 'bottom': 0, 'right': 0 }; |
951
|
|
|
|
952
|
|
|
for (var j = 0; j < between.length; j++) { |
953
|
|
|
var el = $(between[j]); |
954
|
|
|
var offset = el.offset(); |
955
|
|
|
if (!el.is(':visible')) { |
956
|
|
|
continue; |
957
|
|
|
} |
958
|
|
|
if (bbox.top > offset.top) { |
959
|
|
|
bbox.top = offset.top; |
960
|
|
|
} |
961
|
|
|
if (bbox.left > offset.left) { |
962
|
|
|
bbox.left = offset.left; |
963
|
|
|
} |
964
|
|
|
if (bbox.bottom < (offset.top + el.height())) { |
965
|
|
|
bbox.bottom = offset.top + el.height(); |
966
|
|
|
} |
967
|
|
|
if (bbox.right < (offset.left + el.width())) { |
968
|
|
|
bbox.right = offset.left + el.width(); |
969
|
|
|
} |
970
|
|
|
} |
971
|
|
|
|
972
|
|
|
var style = |
973
|
|
|
"z-index: " + (1000 + p.parents().length) + ";" + |
974
|
|
|
"left: " + bbox.left + "px;" + |
975
|
|
|
"top: " + bbox.top + "px;" + |
976
|
|
|
"width: " + (bbox.right - bbox.left) + "px;" + |
977
|
|
|
"height: " + (bbox.bottom - bbox.top) + "px;"; |
978
|
|
|
var debugel = $('<div class="cachearium-debug-view" style="' + style + |
979
|
|
|
'" data-key="' + p.data('key') + '"></div>'); |
980
|
|
|
var innerdata = '<span class="cachearium-debug-view-innerdata">'; |
981
|
|
|
$.each(p.data(), function (name, value) { |
982
|
|
|
debugel.attr("data-" + name, value); |
983
|
|
|
innerdata += name + ": " + value + "<br/>"; |
984
|
|
|
}); |
985
|
|
|
innerdata += '</span>'; |
986
|
|
|
debugel.append(innerdata); |
987
|
|
|
$('body').append(debugel); |
988
|
|
|
} |
989
|
|
|
$('body').append( |
990
|
|
|
'<div class="cachearium-debug-overview">' + |
991
|
|
|
'<span><b>Cachearium</b></span><br/>' + |
992
|
|
|
'<span>' + probes.length + ' probes</span><br/>' + |
993
|
|
|
'<a id="cachearium-debug-toggle" href="#">Toggle</a>' + |
994
|
|
|
'</div>' |
995
|
|
|
); |
996
|
|
|
$('#cachearium-debug-toggle').click(function() { |
997
|
|
|
$('.cachearium-debug-view').toggle(); |
998
|
|
|
}); |
999
|
|
|
}); |
1000
|
|
|
</script> |
1001
|
|
|
<?php |
1002
|
|
|
} |
1003
|
|
|
} |
1004
|
|
|
|
This check looks for the
else
branches ofif
statements 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
else
branches can be removed.could be turned into
This is much more concise to read.