Completed
Push — master ( 9632cd...9db2a1 )
by Bruno
02:34
created

CacheAbstract::recursiveStartP()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 6
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 {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if 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.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
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 {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if 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.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
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