PluginProbe
APCu Manager / trunk
APCu Manager vtrunk
4.6.0 4.6.1 4.5.4 4.5.2 4.5.3 4.5.1 trunk 1.0.0 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 2.0.0 2.1.0 2.2.0 2.3.0 2.4.0 2.4.1 2.5.0 2.6.0 3.0.0 3.0.1 3.1.0 All 49 releases
apcu-manager / includes / api / object-class.php

object-class.php in APCu Manager trunk, at includes/api/object-class.php

1,292 lines 38.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * APCu Objects Cache Object
4 *
5 * Handles all APCU caching operations.
6 *
7 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
8 * @since 3.0.0
9 */
10
11 use APCuManager\System\Option;
12 use APCuManager\System\Cache;
13 use APCuManager\System\Environment;
14
15 if ( ! defined( 'APCU_ITERATOR_MAX_CHUNCK_SIZE' ) ) {
16 define( 'APCU_ITERATOR_MAX_CHUNCK_SIZE', 2500 );
17 }
18
19 if ( ! defined( 'APCU_ITERATOR_MAX_LOOP' ) ) {
20 define( 'APCU_ITERATOR_MAX_LOOP', 20 );
21 }
22
23 /**
24 * Object cache class definition
25 */
26 class WP_Object_Cache {
27
28 /**
29 * Tracked metrics.
30 *
31 * @var array
32 * @since 3.0.0
33 */
34 private $metrics = [];
35
36 /**
37 * Local cache.
38 *
39 * @var array
40 * @since 3.0.0
41 */
42 private $local_cache = [];
43
44 /**
45 * Available metrics.
46 *
47 * @var array
48 * @since 3.0.0
49 */
50 private $available_metrics = [ 'add', 'dec', 'inc', 'set', 'replace', 'fetch', 'delete', 'flush' ];
51
52 /**
53 * List of global groups.
54 *
55 * @var array
56 * @since 3.0.0
57 */
58 private $global_groups = [];
59
60 /**
61 * List of non persistent groups.
62 *
63 * @var array
64 * @since 3.0.0
65 */
66 private $non_persistent_groups = [];
67
68 /**
69 * Non persistent cache.
70 *
71 * @var array
72 * @since 3.0.0
73 */
74 private $non_persistent_cache = [];
75
76 /**
77 * Prefix used for all groups.
78 *
79 * @var string
80 * @since 3.0.0
81 */
82 private $cache_prefix = '';
83
84 /**
85 * Prefix used for non-global groups.
86 *
87 * @var string
88 * @since 3.0.0
89 */
90 private $blog_prefix = 1;
91
92 /**
93 * Is APCu really available?
94 *
95 * @var bool
96 * @since 3.0.0
97 */
98 public $apcu_available = false;
99
100 /**
101 * Is it a multisite?
102 *
103 * @var bool
104 * @since 3.0.0
105 */
106 private $multi_site = false;
107
108 /**
109 * The self instance.
110 *
111 * @var \WP_Object_Cache
112 * @since 3.0.0
113 */
114 private static $instance;
115
116 /**
117 * The events logger instance.
118 *
119 * @var \DecaLog\EventsLogger
120 * @since 3.0.0
121 */
122 private static $events_logger = null;
123
124 /**
125 * Are we in debug mode?
126 *
127 * @var bool
128 * @since 3.0.0
129 */
130 private static $debug = false;
131
132 /**
133 * The events prefix.
134 *
135 * @var string
136 * @since 3.0.0
137 */
138 private static $events_prefix = '[WPObjectCache] ';
139
140 /**
141 * Available metrics.
142 *
143 * @var array
144 * @since 3.0.0
145 */
146 private static $metrics_definition = [
147 'add' => 'added',
148 'dec' => 'decremented',
149 'inc' => 'incremented',
150 'set' => 'set',
151 'replace' => 'replaced',
152 'fetch' => 'fetched',
153 'flush' => 'flushed',
154 'delete' => 'deleted',
155 ];
156
157 /**
158 * The traces logger instance.
159 *
160 * @var \DecaLog\TracesLogger
161 * @since 3.0.0
162 */
163 private static $traces_logger = null;
164
165 /**
166 * The metrics logger instance.
167 *
168 * @var \DecaLog\MetricsLogger
169 * @since 3.0.0
170 */
171 private static $metrics_logger = null;
172
173 /**
174 * Get instance of WP_Object_Cache.
175 *
176 * @return \WP_Object_Cache
177 * @since 3.0.0
178 */
179 public static function instance() {
180 if ( ! isset( self::$instance ) ) {
181 self::$instance = new static();
182 }
183 return self::$instance;
184 }
185
186 /**
187 * Get object manager id.
188 *
189 * @since 3.1.1
190 */
191 public static function get_manager_id() {
192 return 'apcm';
193 }
194
195 /**
196 * Set instance of \DecaLog\TracesLogger.
197 *
198 * @param \DecaLog\EventsLogger $logger The logger to attach.
199 *
200 * @since 3.0.0
201 */
202 public static function set_events_logger( $logger ) {
203 if ( $logger instanceof \DecaLog\EventsLogger ) {
204 self::$events_logger = $logger;
205 if ( isset( self::$events_logger ) && self::$debug ) {
206 self::$events_logger->debug( self::$events_prefix . 'Events logger attached.' );
207 }
208 }
209 }
210
211 /**
212 * Set instance of \DecaLog\TracesLogger.
213 *
214 * @param \DecaLog\TracesLogger $logger The logger to attach.
215 *
216 * @since 3.0.0
217 */
218 public static function set_traces_logger( $logger ) {
219 if ( $logger instanceof \DecaLog\TracesLogger ) {
220 self::$traces_logger = $logger;
221 if ( isset( self::$events_logger ) && self::$debug ) {
222 self::$events_logger->debug( self::$events_prefix . 'Traces logger attached.' );
223 }
224 }
225 }
226
227 /**
228 * Set instance of \DecaLog\TracesLogger.
229 *
230 * @param \DecaLog\MetricsLogger $logger The logger to attach.
231 *
232 * @since 3.0.0
233 */
234 public static function set_metrics_logger( $logger ) {
235 if ( $logger instanceof \DecaLog\MetricsLogger ) {
236 self::$metrics_logger = $logger;
237 if ( isset( self::$events_logger ) && self::$debug ) {
238 self::$events_logger->debug( self::$events_prefix . 'Metrics logger attached.' );
239 }
240 }
241 if ( Option::network_get( 'metrics' ) && isset( self::$metrics_logger ) && ! in_array( Environment::exec_mode(), [ 1, 3, 4 ], true ) ) {
242 add_action( 'shutdown', [ self::instance(), 'compute_metrics' ], DECALOG_MAX_SHUTDOWN_PRIORITY - 1, 0 );
243 self::$metrics_logger->createProdGauge( 'object_cache_all_hit_ratio', 0, 'Object cache hit ratio per request, 5 min average - [percent]' );
244 self::$metrics_logger->createProdGauge( 'object_cache_all_success_ratio', 0, 'Object cache success ratio per request, 5 min average - [percent]' );
245 self::$metrics_logger->createProdGauge( 'object_cache_all_time', 0, 'Object cache time per request, 5 min average - [second]' );
246 if ( self::$debug ) {
247 self::$metrics_logger->createDevGauge( 'object_cache_all_size', 0, 'Object cache size per request, 5 min average - [byte]' );
248 }
249 foreach ( self::$metrics_definition as $metric => $desc ) {
250 self::$metrics_logger->createProdGauge( 'object_cache_' . $metric . '_success', 0, sprintf( 'Number of successfully %s keys per request, 5 min average - [count]', $desc ) );
251 self::$metrics_logger->createProdGauge( 'object_cache_' . $metric . '_fail', 0, sprintf( 'Number of unsuccessfully %s keys per request, 5 min average - [count]', $desc ) );
252 self::$metrics_logger->createProdGauge( 'object_cache_' . $metric . '_time', 0, sprintf( 'Cache time for successfully %s keys per request, 5 min average - [second]', $desc ) );
253 if ( self::$debug ) {
254 self::$metrics_logger->createDevGauge( 'object_cache_' . $metric . '_size', 0, sprintf( 'Size of successfully %s keys per request, 5 min average - [byte]', $desc ) );
255 }
256 }
257 if ( self::$instance->apcu_available ) {
258 Cache::init();
259 self::instance()->collate_metrics();
260 } else {
261 add_action( 'shutdown', [ self::instance(), 'collate_metrics' ], DECALOG_MAX_SHUTDOWN_PRIORITY, 0 );
262 }
263 }
264 }
265
266 /**
267 * Initialize the instance and set its properties.
268 *
269 * @since 3.0.0
270 */
271 private function __construct() {
272 global $blog_id;
273 $this->cache_prefix = '_' . md5( ABSPATH ) . '_';
274 $this->apcu_available = function_exists( 'apcu_delete' ) && function_exists( 'apcu_fetch' ) && function_exists( 'apcu_store' ) && function_exists( 'apcu_add' ) && function_exists( 'apcu_dec' ) && function_exists( 'apcu_inc' );
275 $this->multi_site = is_multisite();
276 $this->blog_prefix = $this->multi_site ? $blog_id : 1;
277 foreach ( $this->available_metrics as $metric ) {
278 $this->metrics[ $metric ] = [];
279 foreach ( [ 'success', 'fail', 'time', 'size' ] as $kpi ) {
280 $this->metrics[ $metric ][ $kpi ] = 0;
281 }
282 }
283 }
284
285 /**
286 * Disable cloning.
287 *
288 * @since 3.0.0
289 */
290 private function __clone() {
291 }
292
293 /**
294 * Disable stats.
295 *
296 * @since 3.0.0
297 */
298 public function stats() {
299 }
300
301 /**
302 * Computes metrics.
303 *
304 * @since 3.0.0
305 */
306 public function compute_metrics() {
307 if ( isset( self::$traces_logger ) ) {
308 $span = self::$traces_logger->startSpan( 'Object caching metrics computation', DECALOG_SPAN_SHUTDOWN );
309 }
310 $metrics = Cache::get_global( 'metrics/data' );
311 $new = [];
312 $time = time();
313 if ( isset( $metrics ) && is_array( $metrics ) ) {
314 foreach ( $metrics as $ts => $metric ) {
315 if ( 300 >= $time - (int) $ts ) {
316 $new[ $ts ] = $metric;
317 }
318 }
319 }
320 $new[ time() ] = $this->metrics;
321 Cache::set_global( 'metrics/data', $new, 'infinite' );
322 if ( isset( self::$traces_logger ) ) {
323 self::$traces_logger->endSpan( $span );
324 }
325 }
326
327 /**
328 * Collates metrics.
329 *
330 * @since 3.0.0
331 */
332 public function collate_metrics() {
333 if ( isset( self::$traces_logger ) ) {
334 $span = self::$traces_logger->startSpan( 'Object caching metrics collation', $this->apcu_available ? DECALOG_SPAN_PLUGINS_LOAD : DECALOG_SPAN_SHUTDOWN );
335 }
336 $metrics = Cache::get_global( 'metrics/data' );
337 if ( isset( $metrics ) && is_array( $metrics ) && 0 < count( $metrics ) ) {
338 $m = [];
339 foreach ( $this->available_metrics as $metric ) {
340 foreach ( [ 'success', 'fail', 'time', 'size' ] as $kpi ) {
341 $m[ $metric . '_' . $kpi ] = [];
342 }
343 }
344 foreach ( $metrics as $bucket ) {
345 foreach ( $this->available_metrics as $metric ) {
346 if ( array_key_exists( $metric, $bucket ) ) {
347 foreach ( [ 'success', 'fail', 'time', 'size' ] as $kpi ) {
348 if ( array_key_exists( $kpi, $bucket[ $metric ] ) ) {
349 $m[ $metric . '_' . $kpi ][] = $bucket[ $metric ][ $kpi ];
350 }
351 }
352 }
353 }
354 }
355 $total = [];
356 foreach ( [ 'success', 'fail', 'time', 'size', 'hit', 'miss' ] as $kpi ) {
357 $total[ $kpi ] = 0;
358 }
359 foreach ( $this->available_metrics as $metric ) {
360 foreach ( [ 'success', 'fail', 'time' ] as $kpi ) {
361 if ( 0 < $m[ $metric . '_' . $kpi ] ) {
362 $val = array_sum( $m[ $metric . '_' . $kpi ] ) / count( $m[ $metric . '_' . $kpi ] );
363 self::$metrics_logger->setProdGauge( 'object_cache_' . $metric . '_' . $kpi, $val );
364 if ( 'fetch' === $metric ) {
365 if ( 'success' === $kpi ) {
366 $total['hit'] += $val;
367 } elseif ( 'fail' === $kpi ) {
368 $total['miss'] += $val;
369 } else {
370 $total[ $kpi ] += $val;
371 }
372 } else {
373 $total[ $kpi ] += $val;
374 }
375 }
376 }
377 if ( self::$debug && 0 < count( $m[ $metric . '_size' ] ) ) {
378 $val = array_sum( $m[ $metric . '_size' ] ) / count( $m[ $metric . '_size' ] );
379 self::$metrics_logger->setDevGauge( 'object_cache_' . $metric . '_size', $val );
380 $total['size'] += $val;
381 }
382 }
383 if ( 0 < $total['hit'] + $total['miss'] ) {
384 self::$metrics_logger->setProdGauge( 'object_cache_all_hit_ratio', $total['hit'] / ( $total['hit'] + $total['miss'] ) );
385 }
386 if ( 0 < $total['success'] + $total['fail'] ) {
387 self::$metrics_logger->setProdGauge( 'object_cache_all_success_ratio', $total['success'] / ( $total['success'] + $total['fail'] ) );
388 }
389 self::$metrics_logger->setProdGauge( 'object_cache_all_time', $total['time'] );
390 if ( self::$debug ) {
391 self::$metrics_logger->setDevGauge( 'object_cache_all_size', $total['size'] );
392 }
393 }
394 if ( isset( self::$traces_logger ) ) {
395 self::$traces_logger->endSpan( $span );
396 }
397 }
398
399 /**
400 * Compute a full cache key name.
401 *
402 * @param int|string $key The key.
403 * @param string $group The group.
404 * @param integer $forced_site Optional. Forces the site.
405 *
406 * @return string The full cache key name.
407 * @since 3.0.0
408 */
409 private function full_item_name( $key, $group, $forced_site = 0 ) {
410 if ( ! is_numeric( $forced_site ) ) {
411 $forced_site = 0;
412 }
413 if ( empty( $group ) ) {
414 $group = 'default';
415 }
416 $prefix = '';
417 if ( ! in_array( (string) $group, $this->global_groups, true ) ) {
418 if ( 0 === $forced_site ) {
419 $prefix = $this->blog_prefix . '_';
420 } else {
421 $prefix = $forced_site . '_';
422 }
423 }
424 $key = str_replace( ':', '_', $key );
425 return 'wordpress' . $this->cache_prefix . $prefix . $group . '_' . $key;
426 }
427
428 /**
429 * Compute the size of variable in APCu (so a serialized var).
430 *
431 * @param mixed $var The variable.
432 *
433 * @return integer The size in octets.
434 * @since 3.0.0
435 */
436 private function size_of( $var ) {
437 try {
438 $result = strlen( serialize( $var ) );
439 } catch ( \Throwable $t ) {
440 $result = 0;
441 }
442 return $result;
443 }
444
445 /**
446 * Switch the internal blog id.
447 *
448 * @param int $blog_id The blog ID.
449 *
450 * @since 3.0.0
451 */
452 public function switch_to_blog( $blog_id ) {
453 $old = $this->blog_prefix;
454 $this->blog_prefix = $this->multi_site ? $blog_id : 1;
455 if ( isset( self::$events_logger ) && self::$debug ) {
456 self::$events_logger->debug( self::$events_prefix . sprintf( 'Switching from site %d to site %d.', $old, $this->blog_prefix ) );
457 }
458 }
459
460 /**
461 * Gets the list of global groups.
462 *
463 * @return array The list of global groups.
464 * @since 3.0.0
465 */
466 public function get_global_groups() {
467 return $this->global_groups;
468 }
469
470 /**
471 * Adds a list of global groups.
472 *
473 * @param string|array $groups The list of groups to add.
474 *
475 * @since 3.0.0
476 */
477 public function add_global_groups( $groups ) {
478 $groups = (array) $groups;
479 foreach ( $groups as $group ) {
480 $this->global_groups[] = $group;
481 }
482 if ( isset( self::$events_logger ) && self::$debug ) {
483 self::$events_logger->debug( self::$events_prefix . sprintf( '%d global group(s) added.', count( $groups ) ) );
484 }
485 }
486
487 /**
488 * Gets the list of non persistent groups.
489 *
490 * @return array The list of groups to get.
491 * @since 3.0.0
492 */
493 public function get_non_persistent_groups() {
494 return $this->non_persistent_groups;
495 }
496
497 /**
498 * Adds a list of non persistent groups.
499 *
500 * @param string|array $groups The list of groups to add.
501 *
502 * @since 3.0.0
503 */
504 public function add_non_persistent_groups( $groups ) {
505 $groups = (array) $groups;
506 foreach ( $groups as $group ) {
507 $this->non_persistent_groups[] = $group;
508 }
509 if ( isset( self::$events_logger ) && self::$debug ) {
510 self::$events_logger->debug( self::$events_prefix . sprintf( '%d non-persistent group(s) added.', count( $groups ) ) );
511 }
512 }
513
514 /**
515 * Checks if the given group is a non persistent group.
516 *
517 * @param string $group The group to check.
518 *
519 * @return bool True if the group is a non persistent group, false otherwise.
520 * @since 3.0.0
521 */
522 private function is_non_persistent_group( $group ) {
523 return in_array( (string) $group, $this->non_persistent_groups, true );
524 }
525
526 /**
527 * Clears the object cache of all data.
528 *
529 * @return bool Always returns true.
530 */
531 public function flush() {
532 $this->non_persistent_cache = [];
533 $this->partial_flush_persistent( [ 'wordpress' . $this->cache_prefix ] );
534 if ( isset( self::$events_logger ) ) {
535 self::$events_logger->info( self::$events_prefix . 'Full cache successfully flushed.' );
536 }
537 return $this->flush_runtime();
538 }
539
540 /**
541 * Clears the internal object cache of all data.
542 *
543 * @return bool Always returns true.
544 */
545 public function flush_runtime() {
546 $this->local_cache = [];
547 return true;
548 }
549
550 /**
551 * Invalidate sites' object cache.
552 *
553 * @param string|array $sites Sites ID's that want flushing.
554 *
555 * @return bool
556 */
557 public function flush_sites( $sites ) {
558 $sites = (array) $sites;
559 if ( ! empty( $sites ) ) {
560 if ( ! in_array( 0, $sites, true ) ) {
561 $sites[] = 0;
562 }
563 $seeds = [];
564 $group = '$$$$$';
565 $key = '%%%%%';
566 $id = $group . '_' . $key;
567 foreach ( $sites as $site ) {
568 $seeds[] = str_replace( $id, '', $this->full_item_name( $key, $group, $site ) );
569 }
570 $this->partial_flush_non_persistent( $seeds );
571 $cpt = $this->partial_flush_persistent( $seeds );
572 if ( isset( self::$events_logger ) ) {
573 self::$events_logger->info( self::$events_prefix . 'Sites cache successfully flushed.' );
574 }
575 return 0 !== $cpt;
576 }
577 return false;
578 }
579
580 /**
581 * Invalidate a group cache.
582 *
583 * @param string|array $group Group to flush.
584 *
585 * @return bool
586 */
587 public function flush_group( $group ) {
588 $seeds = [ $this->full_item_name( '', $group ) ];
589 $this->partial_flush_non_persistent( $seeds );
590 $cpt = $this->partial_flush_persistent( $seeds );
591 if ( isset( self::$events_logger ) ) {
592 self::$events_logger->info( self::$events_prefix . 'Group cache successfully flushed.' );
593 }
594 return 0 !== $cpt;
595 }
596
597 /**
598 * Get a keys list.
599 *
600 * @param array $needles The fragment key name.
601 * @return array The list of full key names.
602 * @since 4.6.0
603 */
604 private function list( $needles = [] ) {
605 $result = [];
606 $regexps = [];
607 if ( [] === $needles ) {
608 $regexps = ['/.*/'];
609 } else {
610 foreach ( $needles as $needle ) {
611 $regexps[] = '/' . preg_quote( $needle, '/' ) . '/';
612 }
613 }
614 if ( ! class_exists( '\APCUIterator' ) ) {
615 return $result;
616 }
617 foreach ( $regexps as $regexp ) {
618 foreach (new \APCUIterator( $regexp, APC_ITER_KEY, APCU_ITERATOR_MAX_CHUNCK_SIZE, APC_LIST_ACTIVE) as $object ) {
619 $result[] = $object['key'];
620 }
621 }
622
623 return $result;
624 }
625
626 /**
627 * Remove specific data from persistent cache.
628 *
629 * @param array $seeds The seeds to remove.
630 *
631 * @return integer Number of removed keys.
632 */
633 private function partial_flush_persistent( $seeds ) {
634 $cpt = 0;
635 $chrono = microtime( true );
636 for ( $i = 1; $i <= APCU_ITERATOR_MAX_LOOP; $i++ ) {
637 $objects = $this->list( $seeds );
638 if ( 0 === count( $objects ) ) {
639 break;
640 }
641 foreach ( $objects as $object ) {
642 if ( $this->delete_persistent( $object, false ) ) {
643 $this->metrics['flush']['success'] += 1;
644 $cpt ++;
645 } else {
646 $this->metrics['flush']['fail'] += 1;
647 }
648 }
649 }
650 $this->metrics['flush']['time'] += microtime( true ) - $chrono;
651 if ( self::$debug ) {
652 self::$events_logger->debug( self::$events_prefix . sprintf( '%d keys removed in a flush operation.', $cpt ) );
653 }
654 return $cpt;
655 }
656
657 /**
658 * Remove specific data from non-persistent cache.
659 *
660 * @param array $seeds The seeds to remove.
661 *
662 * @return integer Number of removed keys.
663 */
664 private function partial_flush_non_persistent( $seeds ) {
665 $cpt = 0;
666 foreach ( $this->non_persistent_cache as $key => $object ) {
667 foreach ( $seeds as $prefix ) {
668 if ( 0 === strpos( $key, $prefix ) ) {
669 unset( $this->non_persistent_cache[ $key ] );
670 $cpt ++;
671 break;
672 }
673 }
674 }
675 return $cpt;
676 }
677
678 /**
679 * Checks if the cached non persistent key exists.
680 *
681 * @param string $key What the contents in the cache are called.
682 *
683 * @return bool True if cache key exists, false otherwise.
684 */
685 private function is_non_persistent_key( $key ) {
686 return array_key_exists( $key, $this->non_persistent_cache );
687 }
688
689 /**
690 * Adds data to the cache, if the cache key does not already exist.
691 *
692 * @param int|string $key The cache key to use for later retrieval.
693 * @param mixed $var The data to add to the cache store.
694 * @param string $group Optional. The group to add the cache to.
695 * @param int $ttl Optional. When the cache data should be expired.
696 *
697 * @return bool False if cache key and group already exist, true otherwise.
698 * @since 3.0.0
699 */
700 public function add( $key, $var, $group = 'default', $ttl = 0 ) {
701 if ( wp_suspend_cache_addition() ) {
702 return false;
703 }
704 $key = $this->full_item_name( $key, $group );
705 if ( ! $this->apcu_available || $this->is_non_persistent_group( $group ) ) {
706 return $this->add_non_persistent( $key, $var );
707 }
708 return $this->add_persistent( $key, $var, $ttl );
709 }
710
711 /**
712 * Adds data to APCu cache, if the cache key does not already exist.
713 *
714 * @param int|string $key The cache key to use for later retrieval.
715 * @param mixed $var The data to add to the cache store.
716 * @param int $ttl When the cache data should be expired.
717 *
718 * @return bool False if cache key and group already exist, true otherwise.
719 * @since 3.0.0
720 */
721 private function add_persistent( $key, $var, $ttl ) {
722 $chrono = microtime( true );
723 $result = true === apcu_add( $key, $var, max( (int) $ttl, 0 ) );
724 $this->metrics['add']['time'] += microtime( true ) - $chrono;
725 if ( $result ) {
726 $this->local_cache[ $key ] = is_object( $var ) ? clone $var : $var;
727 if ( $this::$debug ) {
728 $this->metrics['add']['size'] += $this->size_of( $var );
729 }
730 $this->metrics['add']['success'] += 1;
731 if ( isset( self::$events_logger ) && self::$debug ) {
732 self::$events_logger->debug( self::$events_prefix . sprintf( 'Key "%s" successfully added.', $key ) );
733 }
734 } else {
735 $this->metrics['add']['fail'] += 1;
736 if ( isset( self::$events_logger ) && self::$debug ) {
737 self::$events_logger->debug( self::$events_prefix . sprintf( 'Key "%s" unsuccessfully added.', $key ) );
738 }
739 }
740 return $result;
741 }
742
743 /**
744 * Adds data to non persistent cache, if the cache key does not already exist.
745 *
746 * @param int|string $key The cache key to use for later retrieval.
747 * @param mixed $var The data to add to the cache store.
748 *
749 * @return bool False if cache key and group already exist, true otherwise.
750 * @since 3.0.0
751 */
752 private function add_non_persistent( $key, $var ) {
753 if ( $this->is_non_persistent_key( $key ) ) {
754 return false;
755 }
756 return $this->set_non_persistent( $key, $var );
757 }
758
759 /**
760 * Decrement numeric cache item's value.
761 *
762 * @param int|string $key The cache key to increment
763 * @param int $offset Optional. The amount by which to decrement the item's value.
764 * @param string $group Optional. The group the key is in.
765 *
766 * @return false|int False on failure, the item's new value on success.
767 * @since 3.0.0
768 */
769 public function decr( $key, $offset = 1, $group = 'default' ) {
770 $key = $this->full_item_name( $key, $group );
771 if ( ! $this->apcu_available || $this->is_non_persistent_group( $group ) ) {
772 return $this->decr_non_persistent( $key, $offset );
773 }
774 return $this->decr_persistent( $key, $offset );
775 }
776
777 /**
778 * Decrement numeric APCu cache item's value.
779 *
780 * @param int|string $key The cache key to increment
781 * @param int $offset The amount by which to decrement the item's value.
782 *
783 * @return false|int False on failure, the item's new value on success.
784 * @since 3.0.0
785 */
786 private function decr_persistent( $key, $offset ) {
787 $this->get_persistent( $key, $success );
788 if ( ! $success ) {
789 $this->metrics['dec']['fail'] += 1;
790 return false;
791 }
792 $chrono = microtime( true );
793 $result = false !== apcu_dec( $key, max( (int) $offset, 0 ) );
794 $this->metrics['dec']['time'] += microtime( true ) - $chrono;
795 if ( $result ) {
796 $this->local_cache[ $key ] = $result;
797 if ( $this::$debug ) {
798 $this->metrics['dec']['size'] += $this->size_of( $offset );
799 }
800 $this->metrics['dec']['success'] += 1;
801 if ( isset( self::$events_logger ) && self::$debug ) {
802 self::$events_logger->debug( self::$events_prefix . sprintf( 'Key "%s" successfully decremented.', $key ) );
803 }
804 } else {
805 $this->metrics['dec']['fail'] += 1;
806 if ( isset( self::$events_logger ) && self::$debug ) {
807 self::$events_logger->debug( self::$events_prefix . sprintf( 'Key "%s" unsuccessfully decremented.', $key ) );
808 }
809 }
810 return $result;
811 }
812
813 /**
814 * Decrement numeric non persistent cache item's value.
815 *
816 * @param int|string $key The cache key to increment
817 * @param int $offset The amount by which to decrement the item's value.
818 *
819 * @return false|int False on failure, the item's new value on success.
820 * @since 3.0.0
821 */
822 private function decr_non_persistent( $key, $offset ) {
823 if ( ! $this->is_non_persistent_key( $key ) ) {
824 return false;
825 }
826 $offset = max( (int) $offset, 0 );
827 $var = $this->get_non_persitent( $key );
828 $var = is_numeric( $var ) ? $var : 0;
829 $var -= $offset;
830 return $this->set_non_persistent( $key, $var );
831 }
832
833 /**
834 * Increment numeric cache item's value.
835 *
836 * @param int|string $key The cache key to increment
837 * @param int $offset Optional. The amount by which to increment the item's value.
838 * @param string $group Optional. The group the key is in.
839 *
840 * @return false|int False on failure, the item's new value on success.
841 * @since 3.0.0
842 */
843 public function incr( $key, $offset = 1, $group = 'default' ) {
844 $key = $this->full_item_name( $key, $group );
845 if ( ! $this->apcu_available || $this->is_non_persistent_group( $group ) ) {
846 return $this->incr_non_persistent( $key, $offset );
847 }
848 return $this->incr_persistent( $key, $offset );
849 }
850
851 /**
852 * Increment numeric APCu cache item's value.
853 *
854 * @param int|string $key The cache key to increment
855 * @param int $offset The amount by which to increment the item's value.
856 *
857 * @return false|int False on failure, the item's new value on success.
858 * @since 3.0.0
859 */
860 private function incr_persistent( $key, $offset ) {
861 $this->get_persistent( $key, $success );
862 if ( ! $success ) {
863 $this->metrics['inc']['fail'] += 1;
864 return false;
865 }
866 $chrono = microtime( true );
867 $result = false !== apcu_inc( $key, max( (int) $offset, 0 ) );
868 $this->metrics['inc']['time'] += microtime( true ) - $chrono;
869 if ( $result ) {
870 $this->local_cache[ $key ] = $result;
871 if ( $this::$debug ) {
872 $this->metrics['inc']['size'] += $this->size_of( $offset );
873 }
874 $this->metrics['inc']['success'] += 1;
875 if ( isset( self::$events_logger ) && self::$debug ) {
876 self::$events_logger->debug( self::$events_prefix . sprintf( 'Key "%s" successfully decremented.', $key ) );
877 }
878 } else {
879 $this->metrics['inc']['fail'] += 1;
880 if ( isset( self::$events_logger ) && self::$debug ) {
881 self::$events_logger->debug( self::$events_prefix . sprintf( 'Key "%s" unsuccessfully decremented.', $key ) );
882 }
883 }
884 return $result;
885 }
886
887 /**
888 * Increment numeric non persistent cache item's value.
889 *
890 * @param int|string $key The cache key to increment
891 * @param int $offset The amount by which to increment the item's value.
892 *
893 * @return false|int False on failure, the item's new value on success.
894 * @since 3.0.0
895 */
896 private function incr_non_persistent( $key, $offset ) {
897 if ( ! $this->is_non_persistent_key( $key ) ) {
898 return false;
899 }
900 $offset = max( (int) $offset, 0 );
901 $var = $this->get_non_persitent( $key );
902 $var = is_numeric( $var ) ? $var : 0;
903 $var += $offset;
904 return $this->set_non_persistent( $key, $var );
905 }
906
907 /**
908 * Remove the contents of the cache key in the group.
909 *
910 * @param int|string $key What the contents in the cache are called.
911 * @param string $group Optional. Where the cache contents are grouped.
912 *
913 * @return bool True on success, false otherwise.
914 * @since 3.0.0
915 */
916 public function delete( $key, $group = 'default' ) {
917 $key = $this->full_item_name( $key, $group );
918 if ( ! $this->apcu_available || $this->is_non_persistent_group( $group ) ) {
919 return $this->delete_non_persistent( $key );
920 }
921 return $this->delete_persistent( $key );
922 }
923
924 /**
925 * Remove the contents of the APCu cache key in the group.
926 *
927 * @param int|string $key What the contents in the cache are called.
928 * @param bool $single Optional. If false, it is a flush.
929 *
930 * @return bool True on success, false otherwise.
931 * @since 3.0.0
932 */
933 private function delete_persistent( $key, $single = true ) {
934 if ( $single ) {
935 $chrono = microtime( true );
936 }
937 $result = true === apcu_delete( $key );
938 if ( $single ) {
939 $this->metrics['delete']['time'] += microtime( true ) - $chrono;
940 }
941 unset( $this->local_cache[ $key ] );
942 if ( $result ) {
943 if ( $single ) {
944 $this->metrics['delete']['success'] += 1;
945 if ( isset( self::$events_logger ) && self::$debug ) {
946 self::$events_logger->debug( self::$events_prefix . sprintf( 'Key "%s" successfully deleted.', $key ) );
947 }
948 }
949 } else {
950 if ( $single ) {
951 $this->metrics['delete']['fail'] += 1;
952 if ( isset( self::$events_logger ) && self::$debug ) {
953 self::$events_logger->debug( self::$events_prefix . sprintf( 'Key "%s" unsuccessfully deleted.', $key ) );
954 }
955 }
956 }
957 return $result;
958 }
959
960 /**
961 * Remove the contents of the non persistent cache key in the group.
962 *
963 * @param int|string $key What the contents in the cache are called.
964 *
965 * @return bool True on success, false otherwise.
966 * @since 3.0.0
967 */
968 private function delete_non_persistent( $key ) {
969 if ( array_key_exists( $key, $this->non_persistent_cache ) ) {
970 unset( $this->non_persistent_cache[ $key ] );
971 return true;
972 }
973 return false;
974 }
975
976 /**
977 * Retrieves the current network ID.
978 *
979 * @return int The ID of the current network.
980 * @since 3.7.2
981 */
982 private function get_current_network_id() {
983 global $current_site;
984 if ( is_multisite() && isset( $current_site ) && $current_site instanceof WP_Network && isset( $current_site->id ) ) {
985 return absint( $current_site->id );
986 }
987 return 1;
988 }
989
990 /**
991 * Retrieves the cache contents, if it exists.
992 *
993 * @param int|string $key What the contents in the cache are called.
994 * @param string $group Optional. Where the cache contents are grouped.
995 * @param bool $force Optional. Whether to force an update of the local cache
996 * from the persistent cache. Default false.
997 * @param bool &$success Optional. Return success - or not.
998 * @param bool $is_option Optional. Is it surely a WP option.
999 *
1000 * @return bool|mixed False on failure to retrieve contents or the cache contents on success.
1001 * @since 3.0.0
1002 */
1003 public function get( $key, $group = 'default', $force = false, &$success = null, $is_option = false ) {
1004 if ( ! $is_option ) {
1005 // Prevent non-existent options or site-options from triggering any queries if in notoptions!
1006 if ( 'options' === $group || 'site-options' === $group ) {
1007 $notoptions = $this->get( 'site-options' === $group ? $this->get_current_network_id() . ':notoptions' : 'notoptions', $group, false, $success, true );
1008 if ( is_array( $notoptions ) && isset( $notoptions[ $key ] ) ) {
1009 return false;
1010 }
1011 }
1012 }
1013 $key = $this->full_item_name( $key, $group );
1014 if ( ! $this->apcu_available || $this->is_non_persistent_group( $group ) ) {
1015 $var = $this->get_non_persitent( $key, $success );
1016 } else {
1017 $var = $this->get_persistent( $key, $success, $force );
1018 }
1019 return $var;
1020 }
1021
1022 /**
1023 * Retrieves the APCu cache contents, if it exists.
1024 *
1025 * @param int|string $key What the contents in the cache are called.
1026 * @param bool &$success Optional. Return success - or not.
1027 * @param bool $force Optional. Whether to force an update of the local cache
1028 * from the persistent cache. Default false.
1029 *
1030 * @return bool|mixed False on failure to retrieve contents or the cache contents on success.
1031 * @since 3.0.0
1032 */
1033 private function get_persistent( $key, &$success = null, $force = false ) {
1034 if ( ! $force && array_key_exists( $key, $this->local_cache ) ) {
1035 $success = true;
1036 $var = $this->local_cache[ $key ];
1037 } else {
1038 $chrono = microtime( true );
1039 $var = apcu_fetch( $key, $success );
1040 $this->metrics['fetch']['time'] += microtime( true ) - $chrono;
1041 if ( $success ) {
1042 $this->local_cache[ $key ] = $var;
1043 if ( $this::$debug ) {
1044 $this->metrics['fetch']['size'] += $this->size_of( $var );
1045 }
1046 $this->metrics['fetch']['success'] += 1;
1047 /*if ( ! is_array( $this->metrics['fetch']['succeeded'] ) ) {
1048 $this->metrics['fetch']['succeeded'] = [];
1049 }
1050 $this->metrics['fetch']['succeeded'][] = $key;*/
1051 if ( isset( self::$events_logger ) && self::$debug ) {
1052 self::$events_logger->debug( self::$events_prefix . sprintf( 'Key "%s" successfully fetched.', $key ) );
1053 }
1054 } else {
1055 /*$this->metrics['fetch']['fail'] += 1;
1056 if ( ! is_array( $this->metrics['fetch']['failed'] ) ) {
1057 $this->metrics['fetch']['failed'] = [];
1058 }
1059 $this->metrics['fetch']['failed'][] = $key . ' -> ' . microtime();*/
1060 if ( isset( self::$events_logger ) && self::$debug ) {
1061 self::$events_logger->debug( self::$events_prefix . sprintf( 'Key "%s" unsuccessfully fetched.', $key ) );
1062 }
1063 }
1064 }
1065 if ( is_object( $var ) ) {
1066 $var = clone $var;
1067 }
1068 return $var;
1069 }
1070
1071 /**
1072 * Retrieves the non persistent cache contents, if it exists
1073 *
1074 * @param int|string $key What the contents in the cache are called.
1075 * @param bool &$success Optional. Return success - or not.
1076 *
1077 * @return bool|mixed False on failure to retrieve contents or the cache contents on success.
1078 * @since 3.0.0
1079 */
1080 private function get_non_persitent( $key, &$success = null ) {
1081 if ( array_key_exists( $key, $this->non_persistent_cache ) ) {
1082 $success = true;
1083 return $this->non_persistent_cache[ $key ];
1084 }
1085 $success = false;
1086 return false;
1087 }
1088
1089 /**
1090 * Replace the contents in the cache, if contents already exist.
1091 *
1092 * @param int|string $key What to call the contents in the cache.
1093 * @param mixed $var The contents to store in the cache.
1094 * @param string $group Optional. Where to group the cache contents.
1095 * @param int $ttl Optional. When to expire the cache contents.
1096 *
1097 * @return bool True if contents were replaced, false otherwise.
1098 * @since 3.0.0
1099 */
1100 public function replace( $key, $var, $group = 'default', $ttl = 0 ) {
1101 $key = $this->full_item_name( $key, $group );
1102 if ( ! $this->apcu_available || $this->is_non_persistent_group( $group ) ) {
1103 return $this->replace_non_persistent( $key, $var );
1104 }
1105 return $this->replace_persistent( $key, $var, $ttl );
1106 }
1107
1108 /**
1109 * Replace the contents in the APCu cache, if contents already exist.
1110 *
1111 * @param int|string $key What to call the contents in the cache.
1112 * @param mixed $var The contents to store in the cache.
1113 * @param int $ttl When to expire the cache contents.
1114 *
1115 * @return bool True if contents were replaced, false otherwise.
1116 * @since 3.0.0
1117 */
1118 private function replace_persistent( $key, $var, $ttl ) {
1119 $this->get_persistent( $key, $success );
1120 if ( ! $success ) {
1121 $this->metrics['replace']['fail'] += 1;
1122 return false;
1123 }
1124 return $this->set_persistent( $key, $var, $ttl, true );
1125 }
1126
1127 /**
1128 * Replace the contents in the non persistent cache, if contents already exist.
1129 *
1130 * @param int|string $key What to call the contents in the cache.
1131 * @param mixed $var The contents to store in the cache.
1132 *
1133 * @return bool True if contents were replaced, false otherwise.
1134 * @since 3.0.0
1135 */
1136 private function replace_non_persistent( $key, $var ) {
1137 if ( ! $this->is_non_persistent_key( $key ) ) {
1138 return false;
1139 }
1140 return $this->set_non_persistent( $key, $var );
1141 }
1142
1143 /**
1144 * Sets the data contents into the cache.
1145 *
1146 * @param int|string $key What to call the contents in the cache.
1147 * @param mixed $var The contents to store in the cache.
1148 * @param int $ttl When to expire the cache contents.
1149 *
1150 * @return bool True if contents were set, false otherwise.
1151 * @since 3.0.0
1152 */
1153 public function set( $key, $var, $group = 'default', $ttl = 0 ) {
1154 $key = $this->full_item_name( $key, $group );
1155 if ( ! $this->apcu_available || $this->is_non_persistent_group( $group ) ) {
1156 return $this->set_non_persistent( $key, $var );
1157 }
1158 return $this->set_persistent( $key, $var, $ttl );
1159 }
1160
1161 /**
1162 * Sets the data contents into the APCu cache.
1163 *
1164 * @param int|string $key What to call the contents in the cache.
1165 * @param mixed $var The contents to store in the cache.
1166 * @param int $ttl When to expire the cache contents.
1167 * @param bool $replace Optional. It is a replace operation.
1168 *
1169 * @return bool True if contents were set, false otherwise.
1170 * @since 3.0.0
1171 */
1172 private function set_persistent( $key, $var, $ttl, $replace = false ) {
1173 if ( is_object( $var ) ) {
1174 $var = clone $var;
1175 }
1176 $op = $replace ? 'replace' : 'set';
1177 $op_name = $replace ? 'replaced' : 'set';
1178 $chrono = microtime( true );
1179 $success = apcu_store( $key, $var, max( (int) $ttl, 0 ) );
1180 $this->metrics[ $op ]['time'] += microtime( true ) - $chrono;
1181 if ( $success ) {
1182 $this->local_cache[ $key ] = $var;
1183 if ( $this::$debug ) {
1184 $this->metrics[ $op ]['size'] += $this->size_of( $var );
1185 }
1186 $this->metrics[ $op ]['success'] += 1;
1187 if ( isset( self::$events_logger ) && self::$debug ) {
1188 self::$events_logger->debug( self::$events_prefix . sprintf( 'Key "%s" successfully %s.', $key, $op_name ) );
1189 }
1190 } else {
1191 $this->metrics[ $op ]['fail'] += 1;
1192 if ( isset( self::$events_logger ) && self::$debug ) {
1193 self::$events_logger->debug( self::$events_prefix . sprintf( 'Key "%s" unsuccessfully %s.', $key, $op_name ) );
1194 }
1195 }
1196 return $success;
1197 }
1198
1199 /**
1200 * Sets the data contents into the non persistent cache.
1201 *
1202 * @param int|string $key What to call the contents in the cache.
1203 * @param mixed $var The contents to store in the cache.
1204 *
1205 * @return bool True if contents were replaced, false otherwise.
1206 * @since 3.0.0
1207 */
1208 private function set_non_persistent( $key, $var ) {
1209 if ( is_object( $var ) ) {
1210 $var = clone $var;
1211 }
1212 $this->non_persistent_cache[ $key ] = $var;
1213 return true;
1214 }
1215
1216 /**
1217 * Adds multiple values to the cache in one call.
1218 *
1219 * @param array $data Array of keys and values to be set.
1220 * @param string $group Optional. Where the cache contents are grouped. Default empty.
1221 * @param int $expire Optional. When to expire the cache contents, in seconds.
1222 * Default 0 (no expiration).
1223 *
1224 * @return bool[] Array of return values, grouped by key. Each value is either
1225 * true on success, or false if cache key and group already exist.
1226 * @since 3.1.0
1227 */
1228 public function add_multiple( $data, $group = 'default', $expire = 0 ) {
1229 $values = [];
1230 foreach ( $data as $key => $value ) {
1231 $values[ $key ] = $this->add( $key, $value, $group, $expire );
1232 }
1233 return $values;
1234 }
1235
1236 /**
1237 * Sets multiple values to the cache in one call.
1238 *
1239 * @param array $data Array of keys and values to be set.
1240 * @param string $group Optional. Where the cache contents are grouped. Default empty.
1241 * @param int $expire Optional. When to expire the cache contents, in seconds.
1242 * Default 0 (no expiration).
1243 *
1244 * @return bool[] Array of return values, grouped by key. Each value is either
1245 * true on success, or false on failure.
1246 * @since 3.1.0
1247 */
1248 public function set_multiple( $data, $group = 'default', $expire = 0 ) {
1249 $values = [];
1250 foreach ( $data as $key => $value ) {
1251 $values[ $key ] = $this->set( $key, $value, $group, $expire );
1252 }
1253 return $values;
1254 }
1255
1256 /**
1257 * Retrieves multiple values from the cache in one call.
1258 *
1259 * @param array $keys Array of keys under which the cache contents are stored.
1260 * @param string $group Optional. Where the cache contents are grouped. Default empty.
1261 * @param bool $force Optional. Whether to force an update of the local cache
1262 * from the persistent cache. Default false.
1263 * @return array Array of return values, grouped by key. Each value is either
1264 * the cache contents on success, or false on failure.
1265 * @since 3.1.0
1266 */
1267 public function get_multiple( $keys, $group = 'default', $force = false ) {
1268 $values = [];
1269 foreach ( $keys as $key ) {
1270 $values[ $key ] = $this->get( $key, $group, $force );
1271 }
1272 return $values;
1273 }
1274
1275 /**
1276 * Deletes multiple values from the cache in one call.
1277 *
1278 * @param array $keys Array of keys under which the cache to deleted.
1279 * @param string $group Optional. Where the cache contents are grouped. Default empty.
1280 * @return bool[] Array of return values, grouped by key. Each value is either
1281 * true on success, or false if the contents were not deleted.
1282 * @since 3.1.0
1283 */
1284 public function delete_multiple( $keys, $group = 'default' ) {
1285 $values = [];
1286 foreach ( $keys as $key ) {
1287 $values[ $key ] = $this->delete( $key, $group );
1288 }
1289 return $values;
1290 }
1291
1292 }