From d5a57e3a459e0a02aaf3b1d5c735a3bd3f21bda0 Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber Date: Mon, 13 Jul 2020 11:16:49 -0700 Subject: [PATCH 1/2] Implement `wp_cache_get_multiple()` for WP 5.5 --- object-cache.php | 88 ++++++++++++++++++++++++++++++++++++ tests/phpunit/test-cache.php | 50 ++++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/object-cache.php b/object-cache.php index 6835dc2..95cc24c 100644 --- a/object-cache.php +++ b/object-cache.php @@ -133,6 +133,24 @@ function wp_cache_get( $key, $group = '', $force = false, &$found = null ) { return $wp_object_cache->get( $key, $group, $force, $found ); } +/** + * Retrieves multiple values from the cache in one call. + * + * @see WP_Object_Cache::get_multiple() + * @global WP_Object_Cache $wp_object_cache Object cache global instance. + * + * @param array $keys Array of keys under which the cache contents are stored. + * @param string $group Optional. Where the cache contents are grouped. Default empty. + * @param bool $force Optional. Whether to force an update of the local cache + * from the persistent cache. Default false. + * @return array Array of values organized into groups. + */ +function wp_cache_get_multiple( $keys, $group = '', $force = false ) { + global $wp_object_cache; + + return $wp_object_cache->get_multiple( $keys, $group, $force ); +} + /** * Increment numeric cache item's value * @@ -644,6 +662,74 @@ public function get( $key, $group = 'default', $force = false, &$found = null ) return $value; } + /** + * Retrieves multiple values from the cache in one call. + * + * @param array $keys Array of keys under which the cache contents are stored. + * @param string $group Optional. Where the cache contents are grouped. Default empty. + * @param bool $force Optional. Whether to force an update of the local cache + * from the persistent cache. Default false. + * @return array Array of values organized into groups. + */ + public function get_multiple( $keys, $group = 'default', $force = false ) { + if ( empty( $group ) ) { + $group = 'default'; + } + + $cache = array(); + if ( ! $this->_should_persist( $group ) ) { + foreach ( $keys as $key ) { + $cache[ $key ] = $this->_isset_internal( $key, $group ) ? $this->_get_internal( $key, $group ) : false; + false !== $cache[ $key ] ? $this->cache_hits++ : $this->cache_misses++; + } + return $cache; + } + + // Attempt to fetch values from the internal cache. + if ( ! $force ) { + foreach ( $keys as $key ) { + if ( $this->_isset_internal( $key, $group ) ) { + $cache[ $key ] = $this->_get_internal( $key, $group ); + $this->cache_hits++; + } + } + } + $remaining_keys = array_diff( $keys, array_keys( $cache ) ); + // If all keys were satisfied by the internal cache, we're sorted. + if ( empty( $remaining_keys ) ) { + return $cache; + } + if ( $this->_should_use_redis_hashes( $group ) ) { + $redis_safe_group = $this->_key( '', $group ); + $results = $this->_call_redis( 'hmGet', $redis_safe_group, $remaining_keys ); + } else { + $ids = array(); + foreach ( $remaining_keys as $key ) { + $ids[] = $this->_key( $key, $group ); + } + $results = $this->_call_redis( 'mget', $ids ); + } + // Process the results from the Redis call. + foreach ( $remaining_keys as $i => $key ) { + $value = isset( $results[ $i ] ) ? $results[ $i ] : false; + if ( false !== $value ) { + // All non-numeric values are serialized + $value = is_numeric( $value ) ? intval( $value ) : unserialize( $value ); + $this->_set_internal( $key, $group, $value ); + $this->cache_hits++; + } else { + $this->cache_misses++; + } + $cache[ $key ] = $value; + } + // Make sure return values are returned in the order of the passed keys. + $return_cache = array(); + foreach ( $keys as $key ) { + $return_cache[ $key ] = isset( $cache[ $key ] ) ? $cache[ $key ] : false; + } + return $return_cache; + } + /** * Increment numeric cache item's value * @@ -1239,7 +1325,9 @@ protected function _call_redis( $method ) { case 'IsConnected': case 'exists': case 'get': + case 'mget': case 'hGet': + case 'hmGet': return false; } diff --git a/tests/phpunit/test-cache.php b/tests/phpunit/test-cache.php index 2c76b25..a6a7146 100644 --- a/tests/phpunit/test-cache.php +++ b/tests/phpunit/test-cache.php @@ -11,6 +11,8 @@ class CacheTest extends WP_UnitTestCase { private static $get_key; + private static $mget_key; + private static $set_key; private static $incr_by_key; @@ -43,6 +45,7 @@ public function setUp() { self::$exists_key = WP_Object_Cache::USE_GROUPS ? 'hExists' : 'exists'; self::$get_key = WP_Object_Cache::USE_GROUPS ? 'hGet' : 'get'; + self::$mget_key = WP_Object_Cache::USE_GROUPS ? 'hmGet' : 'mget'; self::$set_key = WP_Object_Cache::USE_GROUPS ? 'hSet' : 'set'; self::$incr_by_key = WP_Object_Cache::USE_GROUPS ? 'hIncrBy' : 'incrBy'; // 'hIncrBy' isn't a typo here — Redis doesn't support decrBy on groups @@ -756,6 +759,53 @@ public function test_get_found() { $this->assertEquals( 1, $this->cache->cache_misses ); } + public function test_get_multiple() { + $this->cache->set( 'foo1', 'bar', 'group1' ); + $this->cache->set( 'foo2', 'bar', 'group1' ); + $this->cache->set( 'foo1', 'bar', 'group2' ); + + $found = $this->cache->get_multiple( array( 'foo1', 'foo2', 'foo3' ), 'group1' ); + + $expected = array( + 'foo1' => 'bar', + 'foo2' => 'bar', + 'foo3' => false, + ); + + $this->assertSame( $expected, $found ); + $this->assertEquals( 2, $this->cache->cache_hits ); + $this->assertEquals( 1, $this->cache->cache_misses ); + if ( $this->cache->is_redis_connected ) { + $this->assertEquals( + array( + self::$mget_key => 1, + self::$set_key => 3, + ), + $this->cache->redis_calls + ); + } else { + $this->assertEmpty( $this->cache->redis_calls ); + } + } + + public function test_get_multiple_non_persistent() { + $this->cache->add_non_persistent_groups( array( 'group1' ) ); + $this->cache->set( 'foo1', 'bar', 'group1' ); + $this->cache->set( 'foo2', 'bar', 'group1' ); + + $found = $this->cache->get_multiple( array( 'foo1', 'foo2', 'foo3' ), 'group1' ); + + $expected = array( + 'foo1' => 'bar', + 'foo2' => 'bar', + 'foo3' => false, + ); + $this->assertSame( $expected, $found ); + $this->assertEquals( 2, $this->cache->cache_hits ); + $this->assertEquals( 1, $this->cache->cache_misses ); + $this->assertEmpty( $this->cache->redis_calls ); + } + public function test_incr() { $key = rand_str(); From 7dcd65e797a9ccace58a0ef9c8d1ad1cdf09217b Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber Date: Mon, 13 Jul 2020 11:21:38 -0700 Subject: [PATCH 2/2] Update README for v1.1.0 --- README.md | 8 ++++++-- readme.txt | 8 ++++++-- wp-redis.php | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index df2de96..9e2dcf7 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ **Contributors:** getpantheon, danielbachhuber, mboynes, Outlandish Josh **Tags:** cache, plugin, redis **Requires at least:** 3.0.1 -**Tested up to:** 5.4 -**Stable tag:** 1.0.1 +**Tested up to:** 5.5 +**Stable tag:** 1.1.0 **License:** GPLv2 or later **License URI:** http://www.gnu.org/licenses/gpl-2.0.html @@ -107,6 +107,10 @@ There's a known issue with WordPress `alloptions` cache design. Specifically, a ## Changelog ## +### 1.1.0 (July 13, 2020) ### +* Implements `wp_cache_get_multiple()` for WordPress 5.5 [[#287](https://github.com/pantheon-systems/wp-redis/pull/287)]. +* Bails early when connecting to Redis throws an Exception to avoid fatal error [[285](https://github.com/pantheon-systems/wp-redis/pull/285)]. + ### 1.0.1 (April 14, 2020) ### * Adds support for specifying Redis database number from environment/server variables [[#273](https://github.com/pantheon-systems/wp-redis/pull/273)]. diff --git a/readme.txt b/readme.txt index 2b725de..d68b28e 100644 --- a/readme.txt +++ b/readme.txt @@ -2,8 +2,8 @@ Contributors: getpantheon, danielbachhuber, mboynes, Outlandish Josh Tags: cache, plugin, redis Requires at least: 3.0.1 -Tested up to: 5.4 -Stable tag: 1.0.1 +Tested up to: 5.5 +Stable tag: 1.1.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -107,6 +107,10 @@ There's a known issue with WordPress `alloptions` cache design. Specifically, a == Changelog == += 1.1.0 (July 13, 2020) = +* Implements `wp_cache_get_multiple()` for WordPress 5.5 [[#287](https://github.com/pantheon-systems/wp-redis/pull/287)]. +* Bails early when connecting to Redis throws an Exception to avoid fatal error [[285](https://github.com/pantheon-systems/wp-redis/pull/285)]. + = 1.0.1 (April 14, 2020) = * Adds support for specifying Redis database number from environment/server variables [[#273](https://github.com/pantheon-systems/wp-redis/pull/273)]. diff --git a/wp-redis.php b/wp-redis.php index a1a4e34..d4d6704 100644 --- a/wp-redis.php +++ b/wp-redis.php @@ -3,7 +3,7 @@ * Plugin Name: WP Redis * Plugin URI: http://github.com/pantheon-systems/wp-redis/ * Description: WordPress Object Cache using Redis. Requires the PhpRedis extension (https://github.com/phpredis/phpredis). - * Version: 1.0.1 + * Version: 1.1.0 * Author: Pantheon, Josh Koenig, Matthew Boynes, Daniel Bachhuber, Alley Interactive * Author URI: https://pantheon.io/ */