├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── README.md
├── composer.json
├── object-cache.php
├── phpunit.xml.dist
└── tests
├── bin
└── install-wp-tests.sh
├── bootstrap.php
├── ms-mock.php
├── redis-spy.php
└── test-object-cache.php
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | composer.lock
3 | vendor
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | dist: trusty
3 |
4 | language: php
5 |
6 | notifications:
7 | email:
8 | on_success: never
9 | on_failure: change
10 |
11 | service:
12 | - redis-server
13 |
14 | branches:
15 | only:
16 | - master
17 |
18 | cache:
19 | directories:
20 | - $HOME/.composer/cache
21 |
22 | matrix:
23 | include:
24 | - php: 7.2
25 | env: WP_VERSION=latest
26 | - php: 7.0
27 | env: WP_VERSION=latest
28 | - php: 5.6
29 | env: WP_VERSION=latest
30 |
31 | before_script:
32 | - export PATH="$HOME/.composer/vendor/bin:$PATH"
33 | - echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
34 | - |
35 | if [ -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini ]; then
36 | phpenv config-rm xdebug.ini
37 | else
38 | echo "xdebug.ini does not exist"
39 | fi
40 | - |
41 | if [[ ! -z "$WP_VERSION" ]] ; then
42 | bash tests/bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION
43 | composer global require "phpunit/phpunit=4.8.*|5.7.*"
44 | fi
45 |
46 | script:
47 | - |
48 | if [[ ! -z "$WP_VERSION" ]] ; then
49 | phpunit --no-coverage
50 | fi
51 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | 1. Any fix PR should initially contain two commits: one with unit tests that fail, one with a fix.
2 | 1. Discussion in a PR may yield further fixes ontop. We'll squeeze them when merging.
3 | 1. Any feature PR should first be discussed in an Issue.
4 | 1. Any one commit should address one specific problem, do not clutter a commit with unrelated fixes or features.
5 | 1. Complete file changes are not going to be reviewed at all in 99% of cases.
6 | 1. In merge conflict cases we'll ask you to rebase off of a specific branch and force push once more.
7 | 1. We value your time as much as we value ours.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Overview
2 |
3 | A highly efficient, predictive and unit tested WordPress object cache backend that implements all available methods using the Redis PECL library.
4 |
5 | # Why is this fork better?
6 |
7 | - Preloads known cache keys via a single `mget()` call with lazy unserialization
8 | - Further microoptimized routines makes this the **fastest** Redis object cache implementation out there
9 | - Unit-tested with 100% effective target coverage
10 |
11 | For more information check out https://pressjitsu.com/blog/redis-object-cache-wordpress/
12 |
13 | ## Authors
14 |
15 | * Pressjitsu, Inc.
16 | * Gennady Kovshenin
17 | * Eric Mann
18 | * Erick Hitter
19 |
20 | ## Installation
21 | 1. Install and configure Redis. There is a good tutorial [here](https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-redis-on-debian-9).
22 | 2. Install the [Redis PECL module](http://pecl.php.net/package/redis) or compile from [source](https://github.com/phpredis/phpredis).
23 | 3. Add `object-cache.php` to the wp-content directory. It is a drop-in file, not a plugin, so it belongs in the wp-content directory, not the plugins directory.
24 | 4. By default, the script will connect to Redis at 127.0.0.1:6379. See the *Connecting to Redis* section for further options.
25 |
26 | ### Connecting to Redis ###
27 |
28 | By default, the plugin uses `127.0.0.1` and `6379` as the default host and port when creating a new client instance; the default database of `0` is also used. Three constants are provided to override these default values.
29 |
30 | Specify `WP_REDIS_BACKEND_HOST`, `WP_REDIS_BACKEND_PORT`, and `WP_REDIS_BACKEND_DB` to set the necessary, non-default connection values for your Redis instance.
31 |
32 | ### Prefixing Cache Keys ###
33 |
34 | The constant `WP_CACHE_KEY_SALT` is provided to add a prefix to all cache keys used by the plugin. If running two single instances of WordPress from the same Redis instance, this constant could be used to avoid overlap in cache keys. Note that special handling is not needed for WordPress Multisite.
35 |
36 | ## Support
37 |
38 | Support for this plugin can be had over at support@pressjitsu.com
39 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pressjitsu/pj-object-cache-red",
3 | "type": "wordpress-plugin",
4 | "description": "The fastest known WordPress Redis-based Object Cache.",
5 | "keywords": ["object cache", "redis", "performance"],
6 | "homepage": "https://pressjitsu.com/blog/redis-object-cache-wordpress/",
7 | "license": "GPL-3.0+",
8 |
9 | "require": {
10 | "php": ">=5.3",
11 | "ext-json": "*",
12 | "ext-redis": "*"
13 | },
14 |
15 | "require-dev": {
16 | "phpunit/phpunit": ">4.0 <7"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/object-cache.php:
--------------------------------------------------------------------------------
1 | add( $key, $value, $group, $expiration );
38 | }
39 |
40 | /**
41 | * Closes the cache.
42 | *
43 | * This function has ceased to do anything since WordPress 2.5. The
44 | * functionality was removed along with the rest of the persistent cache. This
45 | * does not mean that plugins can't implement this function when they need to
46 | * make sure that the cache is cleaned up after WordPress no longer needs it.
47 | *
48 | * @return bool Always returns True
49 | */
50 | function wp_cache_close() {
51 | return true;
52 | }
53 |
54 | /**
55 | * Decrement a numeric item's value.
56 | *
57 | * @param string $key The key under which to store the value.
58 | * @param int $offset The amount by which to decrement the item's value.
59 | * @param string $group The group value appended to the $key.
60 | *
61 | * @global WP_Object_Cache $wp_object_cache
62 | *
63 | * @return int|bool Returns item's new value on success or FALSE on failure.
64 | */
65 | function wp_cache_decr( $key, $offset = 1, $group = 'default' ) {
66 | global $wp_object_cache;
67 | return $wp_object_cache->decr( $key, $offset, $group );
68 | }
69 |
70 | /**
71 | * Remove the item from the cache.
72 | *
73 | * @param string $key The key under which to store the value.
74 | * @param string $group The group value appended to the $key.
75 | * @param int $time The amount of time the server will wait to delete the item in seconds.
76 | *
77 | * @global WP_Object_Cache $wp_object_cache
78 | *
79 | * @return bool Returns TRUE on success or FALSE on failure.
80 | */
81 | function wp_cache_delete( $key, $group = 'default', $time = 0 ) {
82 | global $wp_object_cache;
83 | return $wp_object_cache->delete( $key, $group, $time );
84 | }
85 |
86 | /**
87 | * Invalidate all items in the cache.
88 | *
89 | * @param int $delay Number of seconds to wait before invalidating the items.
90 | *
91 | * @global WP_Object_Cache $wp_object_cache
92 | *
93 | * @return bool Returns TRUE on success or FALSE on failure.
94 | */
95 | function wp_cache_flush( $delay = 0 ) {
96 | global $wp_object_cache;
97 | return $wp_object_cache->flush( $delay );
98 | }
99 |
100 | /**
101 | * Retrieve object from cache.
102 | *
103 | * Gets an object from cache based on $key and $group.
104 | *
105 | * @param string $key The key under which to store the value.
106 | * @param string $group The group value appended to the $key.
107 | *
108 | * @global WP_Object_Cache $wp_object_cache
109 | *
110 | * @return bool|mixed Cached object value.
111 | */
112 | function wp_cache_get( $key, $group = 'default', $force = false, &$found = null ) {
113 | global $wp_object_cache;
114 | return $wp_object_cache->get( $key, $group, $force, $found );
115 | }
116 |
117 | /**
118 | * Retrieve multiple values from cache.
119 | *
120 | * Gets multiple values from cache, including across multiple groups
121 | *
122 | * Usage: array( 'group0' => array( 'key0', 'key1', 'key2', ), 'group1' => array( 'key0' ) )
123 | *
124 | * Mirrors the Memcached Object Cache plugin's argument and return-value formats
125 | *
126 | * @param array $groups Array of groups and keys to retrieve
127 | *
128 | * @global WP_Object_Cache $wp_object_cache
129 | *
130 | * @return bool|mixed Array of cached values, keys in the format $group:$key. Non-existent keys false
131 | */
132 | function wp_cache_get_multi( $groups, $unserialize = true ) {
133 | global $wp_object_cache;
134 | return $wp_object_cache->get_multi( $groups, $unserialize );
135 | }
136 |
137 | /**
138 | * Increment a numeric item's value.
139 | *
140 | * @param string $key The key under which to store the value.
141 | * @param int $offset The amount by which to increment the item's value.
142 | * @param string $group The group value appended to the $key.
143 | *
144 | * @global WP_Object_Cache $wp_object_cache
145 | *
146 | * @return int|bool Returns item's new value on success or FALSE on failure.
147 | */
148 | function wp_cache_incr( $key, $offset = 1, $group = 'default' ) {
149 | global $wp_object_cache;
150 | return $wp_object_cache->incr( $key, $offset, $group );
151 | }
152 |
153 | /**
154 | * Sets up Object Cache Global and assigns it.
155 | *
156 | * @global WP_Object_Cache $wp_object_cache WordPress Object Cache
157 | *
158 | * @return void
159 | */
160 | function wp_cache_init() {
161 | global $wp_object_cache;
162 | $wp_object_cache = new WP_Object_Cache();
163 | }
164 |
165 | /**
166 | * Replaces a value in cache.
167 | *
168 | * This method is similar to "add"; however, is does not successfully set a value if
169 | * the object's key is not already set in cache.
170 | *
171 | * @param string $key The key under which to store the value.
172 | * @param mixed $value The value to store.
173 | * @param string $group The group value appended to the $key.
174 | * @param int $expiration The expiration time, defaults to 0.
175 | *
176 | * @global WP_Object_Cache $wp_object_cache
177 | *
178 | * @return bool Returns TRUE on success or FALSE on failure.
179 | */
180 | function wp_cache_replace( $key, $value, $group = 'default', $expiration = 0 ) {
181 | global $wp_object_cache;
182 | return $wp_object_cache->replace( $key, $value, $group, $expiration );
183 | }
184 |
185 | /**
186 | * Sets a value in cache.
187 | *
188 | * The value is set whether or not this key already exists in Redis.
189 | *
190 | * @param string $key The key under which to store the value.
191 | * @param mixed $value The value to store.
192 | * @param string $group The group value appended to the $key.
193 | * @param int $expiration The expiration time, defaults to 0.
194 | *
195 | * @global WP_Object_Cache $wp_object_cache
196 | *
197 | * @return bool Returns TRUE on success or FALSE on failure.
198 | */
199 | function wp_cache_set( $key, $value, $group = 'default', $expiration = 0 ) {
200 | global $wp_object_cache;
201 | return $wp_object_cache->set( $key, $value, $group, $expiration );
202 | }
203 |
204 | /**
205 | * Switch the interal blog id.
206 | *
207 | * This changes the blog id used to create keys in blog specific groups.
208 | *
209 | * @param int $_blog_id Blog ID
210 | *
211 | * @global WP_Object_Cache $wp_object_cache
212 | *
213 | * @return bool
214 | */
215 | function wp_cache_switch_to_blog( $_blog_id ) {
216 | global $wp_object_cache;
217 | return $wp_object_cache->switch_to_blog( $_blog_id );
218 | }
219 |
220 | /**
221 | * Adds a group or set of groups to the list of Redis groups.
222 | *
223 | * @param string|array $groups A group or an array of groups to add.
224 | *
225 | * @global WP_Object_Cache $wp_object_cache
226 | *
227 | * @return void
228 | */
229 | function wp_cache_add_global_groups( $groups ) {
230 | global $wp_object_cache;
231 | $wp_object_cache->add_global_groups( $groups );
232 | }
233 |
234 | /**
235 | * Adds a group or set of groups to the list of non-Redis groups.
236 | *
237 | * @param string|array $groups A group or an array of groups to add.
238 | *
239 | * @global WP_Object_Cache $wp_object_cache
240 | *
241 | * @return void
242 | */
243 | function wp_cache_add_non_persistent_groups( $groups ) {
244 | global $wp_object_cache;
245 | $wp_object_cache->add_non_persistent_groups( $groups );
246 | }
247 |
248 | class WP_Object_Cache {
249 |
250 | /**
251 | * Holds the Redis client.
252 | *
253 | * @var Redis
254 | */
255 | private $redis;
256 |
257 | /**
258 | * Track if Redis is available
259 | *
260 | * @var bool
261 | */
262 | private $redis_connected = false;
263 |
264 | /**
265 | * Local cache
266 | *
267 | * @var array
268 | */
269 | public $cache = array();
270 |
271 | private $to_unserialize = array();
272 |
273 | public $to_preload = array();
274 |
275 | /**
276 | * List of global groups.
277 | *
278 | * @var array
279 | */
280 | public $global_groups = array( 'users', 'userlogins', 'usermeta', 'site-options', 'site-lookup', 'blog-lookup', 'blog-details', 'rss' );
281 |
282 | private $_global_groups;
283 |
284 | /**
285 | * List of groups not saved to Redis.
286 | *
287 | * @var array
288 | */
289 | public $no_redis_groups = array( 'comment', 'counts' );
290 |
291 | /**
292 | * Prefix used for global groups.
293 | *
294 | * @var string
295 | */
296 | public $global_prefix = '';
297 |
298 | /**
299 | * Prefix used for non-global groups.
300 | *
301 | * @var string
302 | */
303 | public $blog_prefix = '';
304 |
305 | /**
306 | * Track how many requests were found in cache
307 | *
308 | * @var int
309 | */
310 | public $cache_hits = 0;
311 |
312 | /**
313 | * Track how may requests were not cached
314 | *
315 | * @var int
316 | */
317 | public $cache_misses = 0;
318 |
319 | private $multisite;
320 |
321 | public $stats = array();
322 |
323 | /**
324 | * Instantiate the Redis class.
325 | *
326 | * Instantiates the Redis class.
327 | *
328 | * @param null $persistent_id To create an instance that persists between requests, use persistent_id to specify a unique ID for the instance.
329 | */
330 | public function __construct( $redis_instance = null ) {
331 | // General Redis settings
332 | $redis = array(
333 | 'host' => '127.0.0.1',
334 | 'port' => 6379,
335 | );
336 |
337 | if ( defined( 'WP_REDIS_BACKEND_HOST' ) && WP_REDIS_BACKEND_HOST ) {
338 | $redis['host'] = WP_REDIS_BACKEND_HOST;
339 | }
340 | if ( defined( 'WP_REDIS_BACKEND_PORT' ) && WP_REDIS_BACKEND_PORT ) {
341 | $redis['port'] = WP_REDIS_BACKEND_PORT;
342 | }
343 | if ( defined( 'WP_REDIS_BACKEND_AUTH' ) && WP_REDIS_BACKEND_AUTH ) {
344 | $redis['auth'] = WP_REDIS_BACKEND_AUTH;
345 | }
346 | if ( defined( 'WP_REDIS_BACKEND_DB' ) && WP_REDIS_BACKEND_DB ) {
347 | $redis['database'] = WP_REDIS_BACKEND_DB;
348 | }
349 |
350 | // Use Redis PECL library.
351 | try {
352 | if ( is_null( $redis_instance ) ) {
353 | $redis_instance = new Redis();
354 | }
355 | $this->redis = $redis_instance;
356 | $this->redis->connect( $redis['host'], $redis['port'] );
357 | $this->redis->setOption( Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE );
358 |
359 | if ( isset( $redis['auth'] ) ) {
360 | $this->redis->auth( $redis['auth'] );
361 | }
362 |
363 | if ( isset( $redis['database'] ) ) {
364 | $this->redis->select( $redis['database'] );
365 | }
366 |
367 | $this->redis_connected = true;
368 | } catch ( RedisException $e ) {
369 | // When Redis is unavailable, fall back to the internal back by forcing all groups to be "no redis" groups
370 | $this->no_redis_groups = array_unique( array_merge( $this->no_redis_groups, $this->global_groups ) );
371 | $this->redis_connected = false;
372 | }
373 |
374 | /**
375 | * This approach is borrowed from Sivel and Boren. Use the salt for easy cache invalidation and for
376 | * multi single WP installs on the same server.
377 | */
378 | if ( ! defined( 'WP_CACHE_KEY_SALT' ) ) {
379 | define( 'WP_CACHE_KEY_SALT', '' );
380 | }
381 |
382 | $this->multisite = is_multisite();
383 | $this->blog_prefix = $this->multisite ? get_current_blog_id() . ':' : '';
384 | $this->_global_groups = array_flip( $this->global_groups );
385 |
386 | $this->maybe_preload();
387 | }
388 |
389 | public function maybe_preload() {
390 | if ( ! $this->can_redis() || empty( $_SERVER['REQUEST_URI'] ) ) {
391 | return;
392 | }
393 |
394 | if ( defined( 'WP_CLI' ) && WP_CLI ) {
395 | return;
396 | }
397 |
398 | $request_hash = md5( json_encode( array(
399 | $_SERVER['HTTP_HOST'],
400 | $_SERVER['REQUEST_URI'],
401 | ) ) );
402 |
403 | $this->preload( $request_hash );
404 |
405 | if ( defined( 'DOING_TESTS' ) && DOING_TESTS ) {
406 | return $request_hash;
407 | }
408 |
409 | register_shutdown_function( array( $this, 'save_preloads' ), $request_hash );
410 | }
411 |
412 | public function preload( $hash ) {
413 | $keys = $this->get( $hash, 'pj-preload' );
414 | if ( is_array( $keys ) ) {
415 | $this->get_multi( $keys, false );
416 | }
417 | }
418 |
419 | public function save_preloads( $hash ) {
420 | $keys = array();
421 |
422 | foreach ( $this->to_preload as $group => $_keys ) {
423 | if ( $group === 'pj-preload' ) {
424 | continue;
425 | }
426 |
427 | if ( in_array( $group, $this->no_redis_groups ) ) {
428 | continue;
429 | }
430 |
431 | $_keys = array_keys( $_keys );
432 | $keys[ $group ] = $_keys;
433 | }
434 |
435 | $this->set( $hash, $keys, 'pj-preload' );
436 | }
437 |
438 | /**
439 | * Is Redis available?
440 | *
441 | * @return bool
442 | */
443 | protected function can_redis() {
444 | return $this->redis_connected;
445 | }
446 |
447 | /**
448 | * Adds a value to cache.
449 | *
450 | * If the specified key already exists, the value is not stored and the function
451 | * returns false.
452 | *
453 | * @param string $key The key under which to store the value.
454 | * @param mixed $value The value to store.
455 | * @param string $group The group value appended to the $key.
456 | * @param int $expiration The expiration time, defaults to 0.
457 | * @return bool Returns TRUE on success or FALSE on failure.
458 | */
459 | public function add( $_key, $value, $group, $expiration = 0 ) {
460 | if ( wp_suspend_cache_addition() ) {
461 | return false;
462 | }
463 |
464 | list( $key, $redis_key ) = $this->build_key( $_key, $group );
465 |
466 | if ( isset( $this->cache[ $group ], $this->cache[ $group ][ $key ] ) && false !== $this->cache[ $group ][ $key ] ) {
467 | return false;
468 | }
469 |
470 | return $this->set( $_key, $value, $group, $expiration );
471 | }
472 |
473 | /**
474 | * Replace a value in the cache.
475 | *
476 | * If the specified key doesn't exist, the value is not stored and the function
477 | * returns false.
478 | *
479 | * @param string $key The key under which to store the value.
480 | * @param mixed $value The value to store.
481 | * @param string $group The group value appended to the $key.
482 | * @param int $expiration The expiration time, defaults to 0.
483 | * @return bool Returns TRUE on success or FALSE on failure.
484 | */
485 | public function replace( $_key, $value, $group, $expiration = 0 ) {
486 | list( $key, $redis_key ) = $this->build_key( $_key, $group );
487 |
488 | // If group is a non-Redis group, save to internal cache, not Redis
489 | if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) {
490 | if ( ! isset( $this->cache[ $group ], $this->cache[ $group ][ $key ] ) ) {
491 | return false;
492 | }
493 | } else {
494 | if ( ! $this->redis->exists( $redis_key ) ) {
495 | return false;
496 | }
497 | }
498 |
499 | return $this->set( $_key, $value, $group, $expiration );
500 | }
501 |
502 | /**
503 | * Remove the item from the cache.
504 | *
505 | * @param string $key The key under which to store the value.
506 | * @param string $group The group value appended to the $key.
507 | * @return bool Returns TRUE on success or FALSE on failure.
508 | */
509 | public function delete( $_key, $group ) {
510 | list( $key, $redis_key ) = $this->build_key( $_key, $group );
511 |
512 | if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) {
513 | if ( ! isset( $this->cache[ $group ], $this->cache[ $group ][ $key ] ) ) {
514 | return false;
515 | }
516 |
517 | unset( $this->cache[ $group ][ $key ] );
518 | unset( $this->to_preload[ $group ][ $key ] );
519 | unset( $this->to_unserialize[ $redis_key ] );
520 | return true;
521 | }
522 |
523 | unset( $this->cache[ $group ][ $key ] );
524 | unset( $this->to_preload[ $group ][ $key ] );
525 | unset( $this->to_unserialize[ $redis_key ] );
526 |
527 | return (bool) $this->redis->del( $redis_key );
528 | }
529 |
530 | /**
531 | * Invalidate all items in the cache.
532 | *
533 | * @return bool
534 | */
535 | public function flush() {
536 | $this->cache = array();
537 | $this->to_preload = array();
538 | $this->to_unserialize = array();
539 |
540 | if ( $this->can_redis() ) {
541 | $this->redis->flushDb();
542 | }
543 |
544 | return true;
545 | }
546 |
547 | /**
548 | * Retrieve object from cache.
549 | *
550 | * Gets an object from cache based on $key and $group.
551 | *
552 | * @param string $key The key under which to store the value.
553 | * @param string $group The group value appended to the $key.
554 | * @return bool|mixed Cached object value.
555 | */
556 | public function get( $_key, $group = 'default', $force = false, &$found = null ) {
557 | list( $key, $redis_key ) = $this->build_key( $_key, $group );
558 |
559 | $this->to_preload[ $group ][ $_key ] = true;
560 |
561 | if ( ! $force && isset( $this->cache[ $group ][ $key ] ) ) {
562 | $value = $this->cache[ $group ][ $key ];
563 |
564 | if ( isset( $this->to_unserialize[ $redis_key ] ) ) {
565 | unset( $this->to_unserialize[ $redis_key ] );
566 | $value = unserialize( $value );
567 | $this->cache[ $group ][ $key ] = $value;
568 | }
569 |
570 | $found = true;
571 | $this->cache_hits += 1;
572 |
573 | return is_object( $value ) ? clone $value : $value;
574 | }
575 |
576 | if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) {
577 | $found = false;
578 | $this->cache_misses += 1;
579 | return false;
580 | }
581 |
582 | // Fetch from Redis
583 | $value = $this->redis->get( $redis_key );
584 |
585 | if ( ! is_string( $value ) ) {
586 | $found = false;
587 | $this->cache[ $group ][ $key ] = false;
588 | $this->cache_misses += 1;
589 | return false;
590 | }
591 |
592 | $found = true;
593 |
594 | $value = is_numeric( $value ) ? $value : unserialize( $value );
595 | $this->cache[ $group ][ $key ] = $value;
596 | $this->cache_hits += 1;
597 | return $value;
598 | }
599 |
600 | /**
601 | * Retrieve multiple values from cache.
602 | *
603 | * Gets multiple values from cache, including across multiple groups
604 | *
605 | * Usage: array( 'group0' => array( 'key0', 'key1', 'key2', ), 'group1' => array( 'key0' ) )
606 | *
607 | * @param array $groups Array of groups and keys to retrieve
608 | * @return bool|mixed Array of cached values, keys in the format $group:$key. Non-existent keys null.
609 | */
610 | public function get_multi( $groups, $unserialize = true ) {
611 | if ( empty( $groups ) || ! is_array( $groups ) ) {
612 | return false;
613 | }
614 |
615 | // Retrieve requested caches and reformat results to mimic Memcached Object Cache's output
616 | $cache = array();
617 | $fetch_keys = array();
618 | $map = array();
619 |
620 | foreach ( $groups as $group => $keys ) {
621 | if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) {
622 | foreach ( $keys as $_key ) {
623 | list( $key, $redis_key ) = $this->build_key( $_key, $group );
624 | $cache[ $group ][ $key ] = $this->get( $_key, $group );
625 | }
626 |
627 | continue;
628 | }
629 |
630 | if ( empty( $cache[ $group ] ) ) {
631 | $cache[ $group ] = array();
632 | }
633 |
634 | foreach ( $keys as $_key ) {
635 | list( $key, $redis_key ) = $this->build_key( $_key, $group );
636 |
637 | if ( isset( $this->cache[ $group ][ $key ] ) ) {
638 | $cache[ $group ][ $key ] = $this->cache[ $group ][ $key ];
639 | continue;
640 | }
641 |
642 | // Fetch these from Redis
643 | $map[ $redis_key ] = array( $group, $key );
644 | $fetch_keys[] = $redis_key;
645 | }
646 | }
647 |
648 | // Nothing else to fetch
649 | if ( empty( $fetch_keys ) ) {
650 | return $cache;
651 | }
652 |
653 | $results = $this->redis->mget( $fetch_keys );
654 | foreach( array_combine( $fetch_keys, $results ) as $redis_key => $value ) {
655 | list( $group, $key ) = $map[ $redis_key ];
656 |
657 | if ( is_string( $value ) ) {
658 | if ( ! $unserialize && ! is_numeric( $value ) ) {
659 | $this->to_unserialize[ $redis_key ] = true;
660 | } elseif ( $unserialize ) {
661 | $this->to_preload[ $group ][ $key ] = true;
662 | $value = is_numeric( $value ) ? $value : unserialize( $value );
663 | }
664 | } else {
665 | $value = false;
666 | }
667 |
668 | $this->cache[ $group ][ $key ] = $cache[ $group ][ $key ] = $value;
669 | }
670 |
671 | return $cache;
672 | }
673 |
674 | /**
675 | * Sets a value in cache.
676 | *
677 | * The value is set whether or not this key already exists in Redis.
678 | *
679 | * @param string $key The key under which to store the value.
680 | * @param mixed $value The value to store.
681 | * @param string $group The group value appended to the $key.
682 | * @param int $expiration The expiration time, defaults to 0.
683 | * @return bool Returns TRUE on success or FALSE on failure.
684 | */
685 | public function set( $_key, $value, $group = 'default', $expiration = 0 ) {
686 | list( $key, $redis_key ) = $this->build_key( $_key, $group );
687 |
688 | if ( is_object( $value ) ) {
689 | $value = clone $value;
690 | }
691 |
692 | $this->cache[ $group ][ $key ] = $value;
693 |
694 | if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) {
695 | return true;
696 | }
697 |
698 | $value = is_numeric( $value ) ? $value : serialize( $value );
699 |
700 | // Save to Redis
701 | if ( $expiration ) {
702 | $this->redis->setex( $redis_key, $expiration, $value );
703 | } else {
704 | $this->redis->set( $redis_key, $value );
705 | }
706 |
707 | return true;
708 | }
709 |
710 | /**
711 | * Increment a Redis counter by the amount specified
712 | *
713 | * @param string $key
714 | * @param int $offset
715 | * @param string $group
716 | * @return bool
717 | */
718 | public function incr( $_key, $offset = 1, $group ) {
719 | list( $key, $redis_key ) = $this->build_key( $_key, $group );
720 |
721 | if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) {
722 | // Consistent with the Redis behavior (start from 0 if not exists)
723 | if ( ! isset( $this->cache[ $group ][ $key ] ) ) {
724 | $this->cache[ $group ][ $key ] = 0;
725 | }
726 |
727 | $this->cache[ $group ][ $key ] += $offset;
728 | return true;
729 | }
730 |
731 | // Save to Redis
732 | $value = $this->redis->incrBy( $redis_key, $offset );
733 | $this->cache[ $group ][ $key ] = $value;
734 | return $value;
735 | }
736 |
737 | /**
738 | * Decrement a Redis counter by the amount specified
739 | *
740 | * @param string $key
741 | * @param int $offset
742 | * @param string $group
743 | * @return bool
744 | */
745 | public function decr( $key, $offset = 1, $group = 'default' ) {
746 | return $this->incr( $key, $offset * -1, $group );
747 | }
748 |
749 | /**
750 | * Builds a key for the cached object using the blog_id, key, and group values.
751 | *
752 | * @author Ryan Boren This function is inspired by the original WP Memcached Object cache.
753 | * @link http://wordpress.org/extend/plugins/memcached/
754 | *
755 | * @param string $key The key under which to store the value.
756 | * @param string $group The group value appended to the $key.
757 | *
758 | * @return array
759 | */
760 | public function build_key( $key, $group = 'default' ) {
761 | $prefix = '';
762 | if ( ! isset( $this->_global_groups[ $group ] ) ) {
763 | $prefix = $this->blog_prefix;
764 | }
765 |
766 | $local_key = $prefix . $key;
767 | return array( $local_key, WP_CACHE_KEY_SALT . "$prefix$group:$key" );
768 | }
769 |
770 | /**
771 | * In multisite, switch blog prefix when switching blogs
772 | *
773 | * @param int $_blog_id
774 | * @return bool
775 | */
776 | public function switch_to_blog( $blog_id ) {
777 | $this->blog_prefix = $this->multisite ? $blog_id . ':' : '';
778 | }
779 |
780 | /**
781 | * Sets the list of global groups.
782 | *
783 | * @param array $groups List of groups that are global.
784 | */
785 | public function add_global_groups( $groups ) {
786 | $groups = (array) $groups;
787 |
788 | if ( $this->can_redis() ) {
789 | $this->global_groups = array_unique( array_merge( $this->global_groups, $groups ) );
790 | } else {
791 | $this->no_redis_groups = array_unique( array_merge( $this->no_redis_groups, $groups ) );
792 | }
793 |
794 | $this->_global_groups = array_flip( $this->global_groups );
795 | }
796 |
797 | /**
798 | * Sets the list of groups not to be cached by Redis.
799 | *
800 | * @param array $groups List of groups that are to be ignored.
801 | */
802 | public function add_non_persistent_groups( $groups ) {
803 | $groups = (array) $groups;
804 |
805 | $this->no_redis_groups = array_unique( array_merge( $this->no_redis_groups, $groups ) );
806 | }
807 | }
808 |
809 | endif;
810 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
15 |
16 | tests
17 |
18 |
19 |
20 |
21 | .
22 |
23 | tests
24 | vendor
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/tests/bin/install-wp-tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if [ $# -lt 3 ]; then
4 | echo "usage: $0 [db-host] [wp-version] [skip-database-creation]"
5 | exit 1
6 | fi
7 |
8 | DB_NAME=$1
9 | DB_USER=$2
10 | DB_PASS=$3
11 | DB_HOST=${4-localhost}
12 | WP_VERSION=${5-latest}
13 | SKIP_DB_CREATE=${6-false}
14 |
15 | TMPDIR=${TMPDIR-/tmp}
16 | TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//")
17 | WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib}
18 | WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/}
19 |
20 | download() {
21 | if [ `which curl` ]; then
22 | curl -s "$1" > "$2";
23 | elif [ `which wget` ]; then
24 | wget -nv -O "$2" "$1"
25 | fi
26 | }
27 |
28 | if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
29 | WP_TESTS_TAG="branches/$WP_VERSION"
30 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
31 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
32 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
33 | WP_TESTS_TAG="tags/${WP_VERSION%??}"
34 | else
35 | WP_TESTS_TAG="tags/$WP_VERSION"
36 | fi
37 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
38 | WP_TESTS_TAG="trunk"
39 | else
40 | # http serves a single offer, whereas https serves multiple. we only want one
41 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
42 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
43 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
44 | if [[ -z "$LATEST_VERSION" ]]; then
45 | echo "Latest WordPress version could not be found"
46 | exit 1
47 | fi
48 | WP_TESTS_TAG="tags/$LATEST_VERSION"
49 | fi
50 |
51 | set -ex
52 |
53 | install_wp() {
54 |
55 | if [ -d $WP_CORE_DIR ]; then
56 | return;
57 | fi
58 |
59 | mkdir -p $WP_CORE_DIR
60 |
61 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
62 | mkdir -p $TMPDIR/wordpress-nightly
63 | download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip
64 | unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/
65 | mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR
66 | else
67 | if [ $WP_VERSION == 'latest' ]; then
68 | local ARCHIVE_NAME='latest'
69 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then
70 | # https serves multiple offers, whereas http serves single.
71 | download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json
72 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
73 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
74 | LATEST_VERSION=${WP_VERSION%??}
75 | else
76 | # otherwise, scan the releases and get the most up to date minor version of the major release
77 | local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'`
78 | LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1)
79 | fi
80 | if [[ -z "$LATEST_VERSION" ]]; then
81 | local ARCHIVE_NAME="wordpress-$WP_VERSION"
82 | else
83 | local ARCHIVE_NAME="wordpress-$LATEST_VERSION"
84 | fi
85 | else
86 | local ARCHIVE_NAME="wordpress-$WP_VERSION"
87 | fi
88 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz
89 | tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR
90 | fi
91 |
92 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
93 | }
94 |
95 | install_test_suite() {
96 | # portable in-place argument for both GNU sed and Mac OSX sed
97 | if [[ $(uname -s) == 'Darwin' ]]; then
98 | local ioption='-i.bak'
99 | else
100 | local ioption='-i'
101 | fi
102 |
103 | # set up testing suite if it doesn't yet exist
104 | if [ ! -d $WP_TESTS_DIR ]; then
105 | # set up testing suite
106 | mkdir -p $WP_TESTS_DIR
107 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
108 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data
109 | fi
110 |
111 | if [ ! -f wp-tests-config.php ]; then
112 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
113 | # remove all forward slashes in the end
114 | WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
115 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
116 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
117 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
118 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
119 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
120 | fi
121 |
122 | }
123 |
124 | install_db() {
125 |
126 | if [ ${SKIP_DB_CREATE} = "true" ]; then
127 | return 0
128 | fi
129 |
130 | # parse DB_HOST for port or socket references
131 | local PARTS=(${DB_HOST//\:/ })
132 | local DB_HOSTNAME=${PARTS[0]};
133 | local DB_SOCK_OR_PORT=${PARTS[1]};
134 | local EXTRA=""
135 |
136 | if ! [ -z $DB_HOSTNAME ] ; then
137 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then
138 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
139 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then
140 | EXTRA=" --socket=$DB_SOCK_OR_PORT"
141 | elif ! [ -z $DB_HOSTNAME ] ; then
142 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
143 | fi
144 | fi
145 |
146 | # create database
147 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
148 | }
149 |
150 | install_wp
151 | install_test_suite
152 | install_db
153 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | b = $b; }
20 | public function add_global_groups() {}
21 | public function add_non_persistent_groups() {}
22 | public function add( $k, $v, $g='' ) { isset( $this->c[ "{$this->b}:$k:$g" ] ) || $this->set( $k, $v ); }
23 | public function set( $k, $v, $g='' ) { $this->c[ "{$this->b}:$k:$g" ] = $v; }
24 | public function get( $k, $g='' ) { return isset( $this->c[ "{$this->b}:$k:$g" ] ) ? $this->c[ "{$this->b}:$k:$g" ] : false; }
25 | };
26 |
27 | global $_wp_using_ext_object_cache;
28 | $_wp_using_ext_object_cache = true;
29 |
30 | // Load test function so tests_add_filter() is available.
31 | require_once $_wp_tests_dir . '/includes/functions.php';
32 |
33 | // Load and install the plugins.
34 | tests_add_filter( 'muplugins_loaded', function() use ( $_pj_ocr_tests_dir ) {
35 | wp_cache_init();
36 | } );
37 |
38 | register_shutdown_function( function() {
39 | global $wpdb;
40 | $wpdb->query( "SET foreign_key_checks = 0" );
41 | foreach ( get_sites() as $site ) {
42 | switch_to_blog( $site->blog_id );
43 | foreach ( $wpdb->tables() as $table => $prefixed_table ) {
44 | $wpdb->query( "DROP TABLE IF EXISTS $prefixed_table" );
45 | }
46 | }
47 | } );
48 |
49 | // Load the WP testing environment.
50 | require_once $_wp_tests_dir . '/includes/bootstrap.php';
51 |
--------------------------------------------------------------------------------
/tests/ms-mock.php:
--------------------------------------------------------------------------------
1 | redis = new Redis();
9 | }
10 |
11 | public function _get( $method ) {
12 | return isset( $this->calls[ $method ] ) ? $this->calls[ $method ] : array();
13 | }
14 |
15 | public function _reset() {
16 | $this->calls = array();
17 | }
18 |
19 | public function __call( $method, $arguments ) {
20 | if ( ! isset( $this->calls[ $method ] ) ) {
21 | $this->calls[ $method ] = array();
22 | }
23 |
24 | $return = call_user_func_array( array( $this->redis, $method ), $arguments );
25 |
26 | $this->calls[ $method ][] = array(
27 | 'return' => $return,
28 | 'arguments' => $arguments,
29 | );
30 |
31 | return $return;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/test-object-cache.php:
--------------------------------------------------------------------------------
1 | redis_spy = new Redis_Spy() );
6 | wp_cache_flush();
7 | }
8 |
9 | private function assertRedisCalls( $method, $count ) {
10 | $this->assertEquals( $count, $actual = count( $this->redis_spy->_get( $method ) ), "Redis::$method called $actual times" );
11 | }
12 |
13 | public function test_simple() {
14 | $this->assertFalse( wp_cache_get( 'miss' ) );
15 | $this->assertFalse( wp_cache_get( 'miss', 'group' ) );
16 |
17 | $this->assertTrue( wp_cache_set( 'miss', '1', 'group' ) );
18 | $this->assertFalse( wp_cache_get( 'miss' ) );
19 | $this->assertEquals( '1', wp_cache_get( 'miss', 'group' ) );
20 | }
21 |
22 | public function test_spaces_in_keys() {
23 | global $wp_object_cache;
24 |
25 | wp_cache_set( 'hello world', '1' );
26 | $this->assertEquals( '1', wp_cache_get( 'hello world' ) );
27 |
28 | $wp_object_cache->cache = array();
29 |
30 | $this->assertEquals( '1', wp_cache_get( 'hello world' ) );
31 |
32 | wp_cache_set( 'helloworld', '2' );
33 | $this->assertEquals( '1', wp_cache_get( 'hello world') );
34 | $this->assertEquals( '2', wp_cache_get( 'helloworld' ) );
35 |
36 | $wp_object_cache->cache = array();
37 |
38 | $this->assertEquals( '1', wp_cache_get( 'hello world') );
39 | $this->assertEquals( '2', wp_cache_get( 'helloworld' ) );
40 | }
41 |
42 | public function test_internal_cache_miss() {
43 | wp_cache_get( 'miss' );
44 | wp_cache_get( 'miss' );
45 | wp_cache_get( 'miss', 'default', true );
46 | $this->assertFalse( wp_cache_get( 'miss' ) );
47 |
48 | $this->assertRedisCalls( 'get', 2 );
49 | }
50 |
51 | public function test_internal_cache_hit() {
52 | wp_cache_set( 'hit', '1' );
53 |
54 | wp_cache_get( 'hit' );
55 | wp_cache_get( 'hit' );
56 | $this->assertEquals( '1', wp_cache_get( 'hit', 'default', true ) );
57 | $this->assertEquals( '1', wp_cache_get( 'hit' ) );
58 |
59 | $this->assertRedisCalls( 'get', 1 );
60 | }
61 |
62 | public function test_incr_decr() {
63 | wp_cache_incr( 'incr' );
64 | wp_cache_decr( 'decr' );
65 |
66 | $this->assertEquals( 1, wp_cache_get( 'incr' ) );
67 | $this->assertEquals( -1, wp_cache_get( 'decr' ) );
68 |
69 | $this->assertRedisCalls( 'incrBy', 2 );
70 | $this->assertRedisCalls( 'get', 0 );
71 | }
72 |
73 | public function test_multi_get() {
74 | wp_cache_set( 'hit', '1' );
75 | wp_cache_set( 'hit', '2', 'group2' );
76 |
77 | global $wp_object_cache;
78 | $wp_object_cache->cache = array();
79 |
80 | $result = wp_cache_get_multi( array(
81 | 'group2' => array( 'hit' ),
82 | 'default' => array( 'hit' ),
83 | ) );
84 |
85 | $this->assertEquals( array(
86 | 'group2' => array(
87 | 'hit' => '2',
88 | ),
89 | 'default' => array(
90 | 'hit' => '1',
91 | )
92 | ), $result );
93 |
94 | wp_cache_get( 'hit' );
95 | wp_cache_get( 'hit', 'group2' );
96 |
97 | $this->assertRedisCalls( 'get', 0 );
98 | $this->assertRedisCalls( 'mget', 1 );
99 | }
100 |
101 | public function test_preload() {
102 | wp_cache_set( 'hit', '1' );
103 | wp_cache_set( 'hit', '2', 'group2' );
104 |
105 | $this->assertEquals( '1', wp_cache_get( 'hit' ) );
106 | $this->assertEquals( '2', wp_cache_get( 'hit', 'group2' ) );
107 |
108 | $this->redis_spy->_reset();
109 |
110 | global $wp_object_cache;
111 |
112 | $wp_object_cache->save_preloads( 'hash' );
113 | $wp_object_cache->cache = array();
114 | $wp_object_cache->preload( 'hash' );
115 |
116 | $this->assertRedisCalls( 'get', 1 );
117 | $this->assertRedisCalls( 'mget', 1 );
118 |
119 | $this->assertEquals( '1', wp_cache_get( 'hit' ) );
120 | $this->assertEquals( '2', wp_cache_get( 'hit', 'group2' ) );
121 |
122 | $result = wp_cache_get_multi( array(
123 | 'group2' => array( 'hit' ),
124 | 'default' => array( 'hit' ),
125 | ) );
126 |
127 | $this->assertEquals( array(
128 | 'group2' => array(
129 | 'hit' => '2',
130 | ),
131 | 'default' => array(
132 | 'hit' => '1',
133 | )
134 | ), $result );
135 |
136 | $this->assertRedisCalls( 'mget', 1 );
137 | $this->assertRedisCalls( 'get', 1 );
138 | }
139 |
140 | public function test_request_preload() {
141 | global $wp_object_cache;
142 |
143 | /**
144 | * Setup.
145 | */
146 | $_SERVER['REQUEST_URI'] = '/home/';
147 | $request_hash = $wp_object_cache->maybe_preload();
148 |
149 | wp_cache_set( 'home', '1' );
150 | wp_cache_get( 'home' );
151 |
152 | $wp_object_cache->save_preloads( $request_hash );
153 |
154 | $wp_object_cache->cache = array();
155 | $wp_object_cache->to_preload = array();
156 |
157 | $_SERVER['REQUEST_URI'] = '/about/';
158 | $request_hash = $wp_object_cache->maybe_preload();
159 |
160 | wp_cache_set( 'about', '1' );
161 | wp_cache_get( 'about' );
162 |
163 | $wp_object_cache->save_preloads( $request_hash );
164 |
165 | $wp_object_cache->cache = array();
166 | $wp_object_cache->to_preload = array();
167 |
168 | /**
169 | * Test.
170 | */
171 | $_SERVER['REQUEST_URI'] = '/home/';
172 | $wp_object_cache->maybe_preload();
173 |
174 | $this->redis_spy->_reset();
175 |
176 | wp_cache_get( 'about' );
177 | $this->assertRedisCalls( 'get', 1 );
178 | wp_cache_get( 'home' );
179 | $this->assertRedisCalls( 'get', 1 );
180 |
181 | $wp_object_cache->cache = array();
182 | $wp_object_cache->to_preload = array();
183 |
184 | $_SERVER['REQUEST_URI'] = '/about/';
185 | $wp_object_cache->maybe_preload();
186 |
187 | $this->redis_spy->_reset();
188 |
189 | wp_cache_get( 'about' );
190 | $this->assertRedisCalls( 'get', 0 );
191 | wp_cache_get( 'home' );
192 | $this->assertRedisCalls( 'get', 1 );
193 |
194 | $_SERVER['REQUEST_URI'] = '';
195 | }
196 |
197 | public function test_preload_before_flush() {
198 | wp_cache_set( 'hit', '1' );
199 |
200 | global $wp_object_cache;
201 |
202 | $wp_object_cache->save_preloads( 'hash' );
203 | $wp_object_cache->cache = array();
204 | $wp_object_cache->preload( 'hash' );
205 |
206 | $this->redis_spy->_reset();
207 |
208 | wp_cache_flush();
209 |
210 | $this->assertFalse( wp_cache_get( 'hit' ) );
211 | $this->assertRedisCalls( 'get', 1 );
212 | }
213 |
214 | public function test_preload_before_set() {
215 | wp_cache_set( 'hit', '1' );
216 |
217 | global $wp_object_cache;
218 |
219 | $wp_object_cache->save_preloads( 'hash' );
220 | $wp_object_cache->cache = array();
221 | $wp_object_cache->preload( 'hash' );
222 |
223 | $this->redis_spy->_reset();
224 |
225 | wp_cache_set( 'hit', '2' );
226 |
227 | $this->assertEquals( '2', wp_cache_get( 'hit' ) );
228 |
229 | $this->assertRedisCalls( 'get', 0 );
230 | }
231 |
232 | public function test_close() {
233 | $this->assertTrue( wp_cache_close() );
234 | }
235 |
236 | public function test_delete() {
237 | $this->assertFalse( wp_cache_delete( 'miss' ) );
238 |
239 | $this->assertFalse( wp_cache_get( 'hit' ) );
240 |
241 | wp_cache_add( 'hit', '1' );
242 |
243 | $this->assertTrue( wp_cache_delete( 'hit' ) );
244 | $this->assertFalse( wp_cache_get( 'hit' ) );
245 |
246 | $this->assertRedisCalls( 'get', 2 );
247 | }
248 |
249 | public function test_flush() {
250 | wp_cache_add( 'hit', '1' );
251 | wp_cache_flush();
252 | $this->assertFalse( wp_cache_get( 'hit' ) );
253 | $this->assertFalse( wp_cache_get( 'hit' ) );
254 |
255 | $this->assertRedisCalls( 'get', 1 );
256 | }
257 |
258 | public function test_init() {
259 | $this->assertNull( wp_cache_init() );
260 | }
261 |
262 | public function test_replace() {
263 | wp_cache_replace( 'hit', '1' );
264 | $this->assertFalse( wp_cache_get( 'hit' ) );
265 |
266 | wp_cache_add( 'hit', '1' );
267 | wp_cache_replace( 'hit', '2' );
268 |
269 | $this->assertEquals( '2', wp_cache_get( 'hit' ) );
270 |
271 | $this->assertRedisCalls( 'exists', 2 );
272 | $this->assertRedisCalls( 'get', 1 );
273 | }
274 |
275 | public function test_suspend() {
276 | wp_suspend_cache_addition( true );
277 |
278 | wp_cache_add( 'hit', '1' );
279 |
280 | $this->assertRedisCalls( 'set', 0 );
281 |
282 | wp_suspend_cache_addition( false );
283 | }
284 |
285 | public function test_non_persistent() {
286 | wp_cache_add_non_persistent_groups( 'this' );
287 |
288 | wp_cache_add( 'hit', '1', 'this' );
289 | wp_cache_incr( 'incr', 1, 'this' );
290 | wp_cache_decr( 'decr', 1, 'this' );
291 |
292 | $this->assertEquals( '1', wp_cache_get( 'hit', 'this' ) );
293 | $this->assertEquals( 1, wp_cache_get( 'incr', 'this' ) );
294 | $this->assertEquals( -1, wp_cache_get( 'decr', 'this' ) );
295 | $this->assertEquals( array(
296 | 'this' => array(
297 | 'hit' => '1',
298 | )
299 | ), wp_cache_get_multi( array( 'this' => array( 'hit' ) ) ) );
300 |
301 | wp_cache_replace( 'hit', '2', 'this' );
302 | wp_cache_delete( 'hit', 'this' );
303 |
304 | $this->assertRedisCalls( 'set', 0 );
305 | $this->assertRedisCalls( 'incrBy', 0 );
306 | $this->assertRedisCalls( 'get', 0 );
307 | $this->assertRedisCalls( 'mget', 0 );
308 | $this->assertRedisCalls( 'delete', 0 );
309 |
310 | global $wp_object_cache;
311 | $wp_object_cache->save_preloads( 'hash' );
312 |
313 | $this->assertEmpty( wp_cache_get( 'hash', 'pj-preload' ) );
314 | }
315 |
316 | public function test_multisite() {
317 | global $wp_object_cache;
318 |
319 | wp_cache_add_global_groups( 'global' );
320 |
321 | wp_cache_add( 'hit', 'global', 'global' );
322 |
323 | $site_1 = get_current_site()->blog_id;
324 | $site_2 = wpmu_create_blog( wp_generate_password( 12, false ), '/', 'Site 2', 1 );
325 | $site_3 = wpmu_create_blog( wp_generate_password( 12, false ), '/', 'Site 3', 1 );
326 |
327 | foreach ( array( $site_1, $site_2, $site_3 ) as $site ) {
328 | switch_to_blog( $site );
329 |
330 | $this->redis_spy->_reset();
331 |
332 | wp_cache_add( 'hit', "_$site" );
333 | wp_cache_add( 'hit', $site, 'this' );
334 |
335 | // Preheated
336 | $this->assertEquals( 'global', wp_cache_get( 'hit', 'global' ) );
337 | $this->assertEquals( "_$site", wp_cache_get( 'hit' ) );
338 | $this->assertEquals( $site, wp_cache_get( 'hit', 'this' ) );
339 |
340 | $this->assertRedisCalls( 'get', 0 );
341 |
342 | $wp_object_cache->cache = array();
343 |
344 | // Fetched
345 | $this->assertEquals( 'global', wp_cache_get( 'hit', 'global' ) );
346 | $this->assertEquals( "_$site", wp_cache_get( 'hit' ) );
347 | $this->assertEquals( $site, wp_cache_get( 'hit', 'this' ) );
348 |
349 | $this->assertRedisCalls( 'get', 3 );
350 |
351 | $wp_object_cache->cache = array();
352 |
353 | wp_cache_get( 'hit', 'global' );
354 | }
355 |
356 | switch_to_blog( $site_1 );
357 | }
358 |
359 | public function test_multisite_preloads() {
360 | global $wp_object_cache;
361 |
362 | wp_cache_add_global_groups( 'global' );
363 |
364 | wp_cache_add( 'hit', 'global', 'global' );
365 |
366 | $site_1 = get_current_site()->blog_id;
367 | $site_2 = wpmu_create_blog( wp_generate_password( 12, false ), '/', 'Site 2', 1 );
368 |
369 | switch_to_blog( $site_1 );
370 |
371 | wp_cache_add( 'hit', $site_1, 'this' );
372 |
373 | wp_cache_get( 'hit', 'this' );
374 | wp_cache_get( 'hit', 'global' );
375 |
376 | $wp_object_cache->save_preloads( $site_1 );
377 |
378 | $wp_object_cache->cache = array();
379 | $wp_object_cache->to_preload = array();
380 |
381 | switch_to_blog( $site_2 );
382 |
383 | $wp_object_cache->preload( $site_2 );
384 | $this->redis_spy->_reset();
385 |
386 | $this->assertEquals( 'global', wp_cache_get( 'hit', 'global' ) );
387 | $this->assertFalse( wp_cache_get( 'hit', 'this' ) );
388 |
389 | $this->assertRedisCalls( 'get', 2 );
390 |
391 | switch_to_blog( $site_1 );
392 |
393 | $wp_object_cache->cache = array();
394 | $wp_object_cache->to_preload = array();
395 |
396 | $wp_object_cache->preload( $site_1 );
397 | $this->redis_spy->_reset();
398 |
399 | $this->assertEquals( 'global', wp_cache_get( 'hit', 'global' ) );
400 | $this->assertEquals( $site_1, wp_cache_get( 'hit', 'this' ) );
401 |
402 | $this->assertRedisCalls( 'get', 0 );
403 | }
404 |
405 | public function test_preload_incr_decr() {
406 | global $wp_object_cache;
407 |
408 | wp_cache_incr( 'incr' );
409 | wp_cache_get( 'incr' );
410 |
411 | wp_cache_incr( 'decr' );
412 | wp_cache_get( 'decr' );
413 |
414 | $wp_object_cache->save_preloads( 'hash' );
415 |
416 | $wp_object_cache->cache = array();
417 | $wp_object_cache->to_preload = array();
418 |
419 | $wp_object_cache->preload( 'hash' );
420 | $this->redis_spy->_reset();
421 |
422 | $this->assertEquals( 1, wp_cache_get( 'incr' ) );
423 | $this->assertEquals( -1, wp_cache_get( 'decr' ) );
424 |
425 | $this->assertRedisCalls( 'get', 0 );
426 | }
427 | }
428 |
--------------------------------------------------------------------------------