PluginProbe
OPcache Manager / trunk
OPcache Manager vtrunk
trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.2.0 1.3.0 1.3.1 1.3.2 2.0.0 2.1.0 2.10.0 2.11.0 2.12.0 2.13.0 2.13.1 2.14.0 2.2.0 2.3.0 2.3.1 2.3.2 2.4.0 2.5.0 2.6.0 All 36 releases
opcache-manager / includes / system / class-cache.php

class-cache.php in OPcache Manager trunk, at includes/system/class-cache.php

555 lines 17.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin cache handling.
4 *
5 * @package System
6 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
7 * @since 1.0.0
8 * @noinspection PhpCSValidationInspection
9 */
10
11 namespace OPcacheManager\System;
12
13 use OPcacheManager\System\Conversion;
14 use OPcacheManager\System\Option;
15
16 /**
17 * The class responsible to handle cache management.
18 *
19 * @package System
20 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
21 * @since 1.0.0
22 */
23 class Cache {
24
25 /**
26 * The pool's name, specific to the calling plugin.
27 *
28 * @since 1.0.0
29 * @var string $pool_name The pool's name.
30 */
31 private static $pool_name = OPCM_SLUG;
32
33 /**
34 * The apcu pool's prefix, specific to the WordPress instance.
35 *
36 * @since 1.0.0
37 * @var string $apcu_pool_prefix The pool's name.
38 */
39 private static $apcu_pool_prefix = '';
40
41 /**
42 * Available TTLs.
43 *
44 * @since 1.0.0
45 * @var array $ttls The TTLs array.
46 */
47 private static $ttls = [];
48
49 /**
50 * Default TTL.
51 *
52 * @since 1.0.0
53 * @var integer $default_ttl The default TTL in seconds.
54 */
55 private static $default_ttl = 3600;
56
57 /**
58 * Is APCu available.
59 *
60 * @since 1.0.0
61 * @var boolean $apcu_available Is APCu available.
62 */
63 private static $apcu_available = false;
64
65 /**
66 * Initializes the class and set its properties.
67 *
68 * @since 1.0.0
69 */
70 public function __construct() {
71 self::init();
72 }
73
74 /**
75 * Verify if cache is in memory.
76 *
77 * @since 1.0.0
78 */
79 public static function is_memory() {
80 return /*wp_using_ext_object_cache() ||*/ self::$apcu_available;
81 }
82
83 /**
84 * Get cache analytics.
85 *
86 * @return array The cache analytics.
87 * @since 1.0.0
88 */
89 public static function get_analytics() {
90 $result = [];
91 if ( wp_using_ext_object_cache() ) {
92 $result['type'] = 'object_cache';
93 } elseif ( self::$apcu_available ) {
94 $result['type'] = 'apcu';
95 } else {
96 $result['type'] = 'db_transient';
97 }
98 return $result;
99 }
100
101 /**
102 * Initializes properties.
103 *
104 * @since 1.0.0
105 */
106 public static function init() {
107 self::$ttls = [
108 'ephemeral' => 1 * MINUTE_IN_SECONDS,
109 'infinite' => 10 * YEAR_IN_SECONDS,
110 'diagnosis' => HOUR_IN_SECONDS,
111 'metrics' => 1 * MINUTE_IN_SECONDS,
112 'plugin-statistics' => DAY_IN_SECONDS,
113 ];
114 /*if ( wp_using_ext_object_cache() ) {
115 wp_cache_add_global_groups( self::$pool_name );
116 }*/
117 if ( ! defined( 'APCU_CACHE_PREFIX' ) ) {
118 define( 'APCU_CACHE_PREFIX', '_' . md5( ABSPATH ) . '_' );
119 }
120 self::$apcu_pool_prefix = APCU_CACHE_PREFIX;
121 self::$apcu_available = function_exists( 'apcu_delete' ) && function_exists( 'apcu_fetch' ) && function_exists( 'apcu_store' );
122 }
123
124 /**
125 * Get an ID for caching.
126 *
127 * @since 1.0.0
128 */
129 public static function id( $args, $path = 'data/' ) {
130 if ( '/' === $path[0] ) {
131 $path = substr( $path, 1 );
132 }
133 if ( '/' !== $path[ strlen( $path ) - 1 ] ) {
134 $path = $path . '/';
135 }
136 return $path . md5( (string) $args );
137 }
138
139 /**
140 * Full item name.
141 *
142 * @param string $item_name Item name. Expected to not be SQL-escaped.
143 * @param boolean $blog_aware Optional. Has the name must take care of blog.
144 * @param boolean $locale_aware Optional. Has the name must take care of locale.
145 * @param boolean $user_aware Optional. Has the name must take care of user.
146 * @return string The full item name.
147 * @since 1.0.0
148 */
149 private static function full_item_name( $item_name, $blog_aware = false, $locale_aware = false, $user_aware = false ) {
150 $name = '';
151 if ( $blog_aware ) {
152 $name .= (string) get_current_blog_id() . '/';
153 }
154 if ( $locale_aware ) {
155 $name .= (string) L10n::get_display_locale() . '/';
156 }
157 if ( $user_aware ) {
158 $name .= (string) User::get_current_user_id() . '/';
159 }
160 $name .= $item_name;
161 return substr( trim( $name ), 0, 172 - strlen( self::$apcu_pool_prefix . self::$pool_name ) );
162 }
163
164 /**
165 * Normalized item name.
166 *
167 * @param string $item_name Item name. Expected to not be SQL-escaped.
168 * @return string The normalized item name.
169 * @since 1.0.0
170 */
171 private static function normalized_item_name( $item_name ) {
172 if ( '/' === $item_name[0] ) {
173 $item_name = substr( $item_name, 1 );
174 }
175 while ( 0 !== substr_count( $item_name, '//' ) ) {
176 $item_name = str_replace( '//', '/', $item_name );
177 }
178 $item_name = str_replace( '/', '_', $item_name );
179 return strtolower( $item_name );
180 }
181
182 /**
183 * Get the value of a fully named cache item.
184 *
185 * If the item does not exist, does not have a value, or has expired,
186 * then the return value will be false.
187 *
188 * @param string $item_name Item name. Expected to not be SQL-escaped.
189 * @return mixed Value of item.
190 * @since 1.0.0
191 */
192 private static function get_for_full_name( $item_name ) {
193 $item_name = self::normalized_item_name( $item_name );
194 $found = false;
195 if ( self::$apcu_available && Option::network_get( 'use_apcu', true ) ) {
196 $result = apcu_fetch( self::$pool_name . self::$apcu_pool_prefix . $item_name, $found );
197 } elseif ( wp_using_ext_object_cache() ) {
198 $result = wp_cache_get( $item_name, self::$pool_name, false, $found );
199 } else {
200 $result = get_transient( self::$pool_name . '_' . $item_name );
201 $found = false !== $result;
202 }
203 if ( $found ) {
204 return $result;
205 } else {
206 return null;
207 }
208 }
209
210 /**
211 * Get the value of a global cache item.
212 *
213 * If the item does not exist, does not have a value, or has expired,
214 * then the return value will be false.
215 *
216 * @param string $item_name Item name. Expected to not be SQL-escaped.
217 * @return mixed Value of item.
218 * @since 1.0.0
219 */
220 public static function get_global( $item_name ) {
221 return self::get_for_full_name( self::full_item_name( $item_name ) );
222 }
223
224 /**
225 * Get the value of a standard cache item.
226 *
227 * If the item does not exist, does not have a value, or has expired,
228 * then the return value will be false.
229 *
230 * @param string $item_name Item name. Expected to not be SQL-escaped.
231 * @param boolean $blog_aware Optional. Has the name must take care of blog.
232 * @param boolean $locale_aware Optional. Has the name must take care of locale.
233 * @param boolean $user_aware Optional. Has the name must take care of user.
234 * @return mixed Value of item.
235 * @since 1.0.0
236 */
237 public static function get( $item_name, $blog_aware = false, $locale_aware = false, $user_aware = false ) {
238 return self::get_for_full_name( self::full_item_name( $item_name, $blog_aware, $locale_aware, $user_aware ) );
239 }
240
241 /**
242 * Set the value of a fully named cache item.
243 *
244 * You do not need to serialize values. If the value needs to be serialized, then
245 * it will be serialized before it is set.
246 *
247 * @param string $item_name Item name. Expected to not be SQL-escaped.
248 * @param mixed $value Item value. Must be serializable if non-scalar.
249 * Expected to not be SQL-escaped.
250 * @param int|string $ttl Optional. The previously defined ttl @see self::init() if it's a string.
251 * The ttl value in seconds if it's and integer.
252 * @return bool False if value was not set and true if value was set.
253 * @since 1.0.0
254 */
255 private static function set_for_full_name( $item_name, $value, $ttl = 'default' ) {
256 $item_name = self::normalized_item_name( $item_name );
257 $expiration = self::$default_ttl;
258 if ( is_string( $ttl ) && array_key_exists( $ttl, self::$ttls ) ) {
259 $expiration = self::$ttls[ $ttl ];
260 }
261 if ( is_integer( $ttl ) && 0 < (int) $ttl ) {
262 $expiration = (int) $ttl;
263 }
264 if ( $expiration > 0 ) {
265 if ( self::$apcu_available && Option::network_get( 'use_apcu', true ) ) {
266 $result = apcu_store( self::$pool_name . self::$apcu_pool_prefix . $item_name, $value, $expiration );
267 } elseif ( wp_using_ext_object_cache() ) {
268 $result = wp_cache_set( $item_name, $value, self::$pool_name, $expiration );
269 } else {
270 $result = set_transient( self::$pool_name . '_' . $item_name, $value, $expiration );
271 }
272 } else {
273 $result = false;
274 }
275 return $result;
276 }
277
278 /**
279 * Set the value of a global cache item.
280 *
281 * You do not need to serialize values. If the value needs to be serialized, then
282 * it will be serialized before it is set.
283 *
284 * @param string $item_name Item name. Expected to not be SQL-escaped.
285 * @param mixed $value Item value. Must be serializable if non-scalar.
286 * Expected to not be SQL-escaped.
287 * @param int|string $ttl Optional. The previously defined ttl @see self::init() if it's a string.
288 * The ttl value in seconds if it's and integer.
289 * @return bool False if value was not set and true if value was set.
290 * @since 1.0.0
291 */
292 public static function set_global( $item_name, $value, $ttl = 'default' ) {
293 return self::set_for_full_name( self::full_item_name( $item_name ), $value, $ttl );
294 }
295
296 /**
297 * Set the value of a standard cache item.
298 *
299 * You do not need to serialize values. If the value needs to be serialized, then
300 * it will be serialized before it is set.
301 *
302 * @param string $item_name Item name. Expected to not be SQL-escaped.
303 * @param mixed $value Item value. Must be serializable if non-scalar.
304 * Expected to not be SQL-escaped.
305 * @param int|string $ttl Optional. The previously defined ttl @see self::init() if it's a string.
306 * The ttl value in seconds if it's and integer.
307 * @param boolean $blog_aware Optional. Has the name must take care of blog.
308 * @param boolean $locale_aware Optional. Has the name must take care of locale.
309 * @param boolean $user_aware Optional. Has the name must take care of user.
310 * @return bool False if value was not set and true if value was set.
311 * @since 1.0.0
312 */
313 public static function set( $item_name, $value, $ttl = 'default', $blog_aware = false, $locale_aware = false, $user_aware = false ) {
314 return self::set_for_full_name( self::full_item_name( $item_name, $blog_aware, $locale_aware, $user_aware ), $value, $ttl );
315 }
316
317 /**
318 * Delete the value of a fully named cache item.
319 *
320 * This function accepts generic car "*" for transients.
321 *
322 * @param string $item_name Item name. Expected to not be SQL-escaped.
323 * @return integer Number of deleted items.
324 * @since 1.0.0
325 */
326 private static function delete_for_ful_name( $item_name ) {
327 $item_name = self::normalized_item_name( $item_name );
328 $result = 0;
329 if ( self::$apcu_available && Option::network_get( 'use_apcu', true ) ) {
330 if ( strlen( $item_name ) - 1 === strpos( $item_name, '_*' ) ) {
331 return false;
332 } else {
333 return apcu_delete( self::$pool_name . self::$apcu_pool_prefix . $item_name );
334 }
335 }
336 if ( wp_using_ext_object_cache() ) {
337 if ( strlen( $item_name ) - 1 === strpos( $item_name, '_*' ) ) {
338 return false;
339 } else {
340 return wp_cache_delete( $item_name, self::$pool_name );
341 }
342 }
343 global $wpdb;
344 $item_name = self::$pool_name . '_' . $item_name;
345 if ( strlen( $item_name ) - 1 === strpos( $item_name, '_*' ) ) {
346 // phpcs:ignore
347 $delete = $wpdb->get_col( "SELECT option_name FROM {$wpdb->options} WHERE option_name = '_transient_timeout_" . str_replace( '_*', '', $item_name ) . "' OR option_name LIKE '_transient_timeout_" . str_replace( '_*', '_%', $item_name ) . "';" );
348 } else {
349 // phpcs:ignore
350 $delete = $wpdb->get_col( "SELECT option_name FROM {$wpdb->options} WHERE option_name = '_transient_timeout_" . $item_name . "';" );
351 }
352 foreach ( $delete as $transient ) {
353 $key = str_replace( '_transient_timeout_', '', $transient );
354 if ( delete_transient( $key ) ) {
355 ++$result;
356 }
357 }
358 return $result;
359 }
360
361 /**
362 * Delete the full pool.
363 *
364 * @return integer Number of deleted items.
365 * @since 1.0.0
366 */
367 public static function delete_pool() {
368 $result = 0;
369 if ( self::$apcu_available ) {
370 if ( function_exists( 'apcu_cache_info' ) && function_exists( 'apcu_delete' ) ) {
371 try {
372 $infos = apcu_cache_info( false );
373 if ( array_key_exists( 'cache_list', $infos ) && is_array( $infos['cache_list'] ) ) {
374 foreach ( $infos['cache_list'] as $script ) {
375 if ( 0 === strpos( $script['info'], self::$pool_name . self::$apcu_pool_prefix ) ) {
376 apcu_delete( $script['info'] );
377 $result++;
378 }
379 }
380 }
381 } catch ( \Throwable $e ) {
382 \DecaLog\Engine::eventsLogger( OPCM_SLUG )->error( sprintf( 'Unable to query APCu status: %s.', $e->getMessage() ), [ 'code' => $e->getCode() ] );
383 $result = 0;
384 }
385 }
386 } elseif ( wp_using_ext_object_cache() ) {
387 $result = 0;
388 } else {
389 $result = self::delete_global( '/*' );
390 }
391 return $result;
392 }
393
394 /**
395 * Delete the value of a global cache item.
396 *
397 * This function accepts generic car "*" for transients.
398 *
399 * @param string $item_name Item name. Expected to not be SQL-escaped.
400 * @return integer Number of deleted items.
401 * @since 1.0.0
402 */
403 public static function delete_global( $item_name ) {
404 return self::delete_for_ful_name( self::full_item_name( $item_name ) );
405 }
406
407 /**
408 * Delete the value of a standard cache item.
409 *
410 * This function accepts generic car "*" for transients.
411 *
412 * @param string $item_name Item name. Expected to not be SQL-escaped.
413 * @param boolean $blog_aware Optional. Has the name must take care of blog.
414 * @param boolean $locale_aware Optional. Has the name must take care of locale.
415 * @param boolean $user_aware Optional. Has the name must take care of user.
416 * @return integer Number of deleted items.
417 * @since 1.0.0
418 */
419 public static function delete( $item_name, $blog_aware = false, $locale_aware = false, $user_aware = false ) {
420 return self::delete_for_ful_name( self::full_item_name( $item_name, $blog_aware, $locale_aware, $user_aware ) );
421 }
422
423 /**
424 * Get the minimum value of a ttl time range.
425 *
426 * @param string $ttl_range The time range in seconds. May be something like '0', '200' or '15-600:15'.
427 * @return integer The ttl in seconds.
428 * @since 1.0.0
429 */
430 public static function get_min( $ttl_range ) {
431 if ( ! is_string( $ttl_range) ) {
432 return 0;
433 }
434 $ttls = explode( '-', $ttl_range );
435 if ( 1 === count( $ttls ) ) {
436 return (int) $ttls[0];
437 }
438 if ( false !== strpos( $ttls[1], ':' ) ) {
439 $steps = explode( ':', $ttls[1] );
440 $ttls[1] = $steps[0];
441 }
442 return (int) min( (int) $ttls[0], (int) $ttls[1] );
443 }
444
445 /**
446 * Get the maximum value of a ttl time range.
447 *
448 * @param string $ttl_range The time range in seconds. May be something like '0', '200' or '15-600:15'.
449 * @return integer The ttl in seconds.
450 * @since 1.0.0
451 */
452 public static function get_max( $ttl_range ) {
453 if ( ! is_string( $ttl_range) ) {
454 return 0;
455 }
456 $ttls = explode( '-', $ttl_range );
457 if ( 1 === count( $ttls ) ) {
458 return (int) $ttls[0];
459 }
460 if ( false !== strpos( $ttls[1], ':' ) ) {
461 $steps = explode( ':', $ttls[1] );
462 $ttls[1] = $steps[0];
463 }
464 return (int) max( (int) $ttls[0], (int) $ttls[1] );
465 }
466
467 /**
468 * Get the step of a ttl time range.
469 *
470 * @param string $ttl_range The time range in seconds. May be something like '0', '200' or '15-600:15'.
471 * @return integer The ttl in seconds.
472 * @since 1.0.0
473 */
474 public static function get_step( $ttl_range ) {
475 if ( ! is_string( $ttl_range) ) {
476 return 0;
477 }
478 $ttls = explode( '-', $ttl_range );
479 if ( 1 === count( $ttls ) ) {
480 return 0;
481 }
482 if ( false !== strpos( $ttls[1], ':' ) ) {
483 $steps = explode( ':', $ttls[1] );
484 if ( 2 === count( $ttls ) ) {
485 return $steps[1];
486 }
487 }
488 return 1;
489 }
490
491 /**
492 * Get the medium value of a ttl time range.
493 *
494 * This function accepts generic car "*" for transients.
495 *
496 * @param string $ttl_range The time range in seconds. May be something like '5-600' or '200'.
497 * @return integer The ttl in seconds.
498 * @since 1.0.0
499 */
500 public static function get_med( $ttl_range ) {
501 $min = self::get_min( $ttl_range );
502 $max = self::get_max( $ttl_range );
503 $step = self::get_step( $ttl_range );
504 $factor = $step * (int) round( ( $max - $min ) / ( 2 * $step ) );
505 return $min + (int) round( $factor );
506 }
507
508 /**
509 * Get the options infos for Site Health "info" tab.
510 *
511 * @since 1.0.0
512 */
513 public static function debug_info() {
514 if ( self::$apcu_available ) {
515 $result['product'] = [
516 'label' => 'Product',
517 'value' => 'APCu',
518 ];
519 foreach ( [ 'enabled', 'shm_segments', 'shm_size', 'entries_hint', 'ttl', 'gc_ttl', 'mmap_file_mask', 'slam_defense', 'enable_cli', 'use_request_time', 'serializer', 'coredump_unmap', 'preload_path' ] as $key ) {
520 $result[ 'directive_' . $key ] = [
521 'label' => '[Directive] ' . $key,
522 'value' => ini_get( 'apc.' . $key ),
523 ];
524 }
525 if ( function_exists( 'apcu_sma_info' ) && function_exists( 'apcu_cache_info' ) ) {
526 $raw = apcu_sma_info();
527 foreach ( $raw as $key => $status ) {
528 if ( ! is_array( $status ) ) {
529 $result[ 'status_' . $key ] = [
530 'label' => '[Status] ' . $key,
531 'value' => $status,
532 ];
533 }
534 }
535 $raw = apcu_cache_info();
536 foreach ( $raw as $key => $status ) {
537 if ( ! is_array( $status ) ) {
538 $result[ 'status_' . $key ] = [
539 'label' => '[Status] ' . $key,
540 'value' => $status,
541 ];
542 }
543 }
544 }
545 } else {
546 $result['product'] = [
547 'label' => 'Product',
548 'value' => 'Database transients',
549 ];
550 }
551 return $result;
552 }
553
554 }
555