add( $key, $value, $group, $expiration ); } /** * Closes the cache. * * This function has ceased to do anything since WordPress 2.5. The * functionality was removed along with the rest of the persistent cache. This * does not mean that plugins can't implement this function when they need to * make sure that the cache is cleaned up after WordPress no longer needs it. * * @return bool Always returns True */ function wp_cache_close() { return true; } /** * Decrement a numeric item's value. * * @param string $key The key under which to store the value. * @param int $offset The amount by which to decrement the item's value. * @param string $group The group value appended to the $key. * * @global WP_Object_Cache $wp_object_cache * * @return int|bool Returns item's new value on success or FALSE on failure. */ function wp_cache_decr( $key, $offset = 1, $group = '' ) { global $wp_object_cache; return $wp_object_cache->decrement( $key, $offset, $group ); } /** * Remove the item from the cache. * * @param string $key The key under which to store the value. * @param string $group The group value appended to the $key. * @param int $time The amount of time the server will wait to delete the item in seconds. * * @global WP_Object_Cache $wp_object_cache * * @return bool Returns TRUE on success or FALSE on failure. */ function wp_cache_delete( $key, $group = '', $time = 0 ) { global $wp_object_cache; return $wp_object_cache->delete( $key, $group, $time ); } /** * Invalidate all items in the cache. * * @param int $delay Number of seconds to wait before invalidating the items. * * @global WP_Object_Cache $wp_object_cache * * @return bool Returns TRUE on success or FALSE on failure. */ function wp_cache_flush( $delay = 0 ) { global $wp_object_cache; return $wp_object_cache->flush( $delay ); } /** * Retrieve object from cache. * * Gets an object from cache based on $key and $group. * * @param string $key The key under which to store the value. * @param string $group The group value appended to the $key. * * @global WP_Object_Cache $wp_object_cache * * @return bool|mixed Cached object value. */ function wp_cache_get( $key, $group = '' ) { global $wp_object_cache; return $wp_object_cache->get( $key, $group ); } /** * Retrieve multiple values from cache. * * Gets multiple values from cache, including across multiple groups * * Usage: array( 'group0' => array( 'key0', 'key1', 'key2', ), 'group1' => array( 'key0' ) ) * * Mirrors the Memcached Object Cache plugin's argument and return-value formats * * @param array $groups Array of groups and keys to retrieve * * @global WP_Object_Cache $wp_object_cache * * @return bool|mixed Array of cached values, keys in the format $group:$key. Non-existent keys false */ function wp_cache_get_multi( $groups ) { global $wp_object_cache; return $wp_object_cache->get_multi( $groups ); } /** * Increment a numeric item's value. * * @param string $key The key under which to store the value. * @param int $offset The amount by which to increment the item's value. * @param string $group The group value appended to the $key. * * @global WP_Object_Cache $wp_object_cache * * @return int|bool Returns item's new value on success or FALSE on failure. */ function wp_cache_incr( $key, $offset = 1, $group = '' ) { global $wp_object_cache; return $wp_object_cache->increment( $key, $offset, $group ); } /** * Sets up Object Cache Global and assigns it. * * @global WP_Object_Cache $wp_object_cache WordPress Object Cache * * @return void */ function wp_cache_init() { global $wp_object_cache; $wp_object_cache = new WP_Object_Cache(); } /** * Replaces a value in cache. * * This method is similar to "add"; however, is does not successfully set a value if * the object's key is not already set in cache. * * @param string $key The key under which to store the value. * @param mixed $value The value to store. * @param string $group The group value appended to the $key. * @param int $expiration The expiration time, defaults to 0. * * @global WP_Object_Cache $wp_object_cache * * @return bool Returns TRUE on success or FALSE on failure. */ function wp_cache_replace( $key, $value, $group = '', $expiration = 0 ) { global $wp_object_cache; return $wp_object_cache->replace( $key, $value, $group, $expiration ); } /** * Sets a value in cache. * * The value is set whether or not this key already exists in Redis. * * @param string $key The key under which to store the value. * @param mixed $value The value to store. * @param string $group The group value appended to the $key. * @param int $expiration The expiration time, defaults to 0. * * @global WP_Object_Cache $wp_object_cache * * @return bool Returns TRUE on success or FALSE on failure. */ function wp_cache_set( $key, $value, $group = '', $expiration = 0 ) { global $wp_object_cache; return $wp_object_cache->set( $key, $value, $group, $expiration ); } /** * Switch the interal blog id. * * This changes the blog id used to create keys in blog specific groups. * * @param int $_blog_id Blog ID * * @global WP_Object_Cache $wp_object_cache * * @return bool */ function wp_cache_switch_to_blog( $_blog_id ) { global $wp_object_cache; return $wp_object_cache->switch_to_blog( $_blog_id ); } /** * Adds a group or set of groups to the list of Redis groups. * * @param string|array $groups A group or an array of groups to add. * * @global WP_Object_Cache $wp_object_cache * * @return void */ function wp_cache_add_global_groups( $groups ) { global $wp_object_cache; $wp_object_cache->add_global_groups( $groups ); } /** * Adds a group or set of groups to the list of non-Redis groups. * * @param string|array $groups A group or an array of groups to add. * * @global WP_Object_Cache $wp_object_cache * * @return void */ function wp_cache_add_non_persistent_groups( $groups ) { global $wp_object_cache; $wp_object_cache->add_non_persistent_groups( $groups ); } class WP_Object_Cache { /** * Holds the Redis client. * * @var Predis\Client */ private $redis; /** * Track if Redis is available * * @var bool */ private $redis_connected = false; /** * Holds the non-Redis objects. * * @var array */ private $cache = array(); /** * List of global groups. * * @var array */ public $global_groups = array( 'users', 'userlogins', 'usermeta', 'site-options', 'site-lookup', 'blog-lookup', 'blog-details', 'rss' ); /** * List of groups not saved to Redis. * * @var array */ public $no_redis_groups = array( 'comment', 'counts' ); /** * Prefix used for global groups. * * @var string */ public $global_prefix = ''; /** * Prefix used for non-global groups. * * @var string */ public $blog_prefix = ''; /** * Track how many requests were found in cache * * @var int */ public $cache_hits = 0; /** * Track how may requests were not cached * * @var int */ public $cache_misses = 0; /** * Instantiate the Redis class. * * Instantiates the Redis class. * * @param null $persistent_id To create an instance that persists between requests, use persistent_id to specify a unique ID for the instance. */ public function __construct() { global $blog_id, $table_prefix; // General Redis settings $redis = array( 'host' => '127.0.0.1', 'port' => 6379, ); if ( defined( 'WP_REDIS_BACKEND_HOST' ) && WP_REDIS_BACKEND_HOST ) { $redis['host'] = WP_REDIS_BACKEND_HOST; } if ( defined( 'WP_REDIS_BACKEND_PORT' ) && WP_REDIS_BACKEND_PORT ) { $redis['port'] = WP_REDIS_BACKEND_PORT; } if ( defined( 'WP_REDIS_BACKEND_AUTH' ) && WP_REDIS_BACKEND_AUTH ) { $redis['auth'] = WP_REDIS_BACKEND_AUTH; } if ( defined( 'WP_REDIS_BACKEND_DB' ) && WP_REDIS_BACKEND_DB ) { $redis['database'] = WP_REDIS_BACKEND_DB; } if ( ( defined( 'WP_REDIS_SERIALIZER' ) ) ) { $redis['serializer'] = WP_REDIS_SERIALIZER; } else { $redis['serializer'] = Redis::SERIALIZER_PHP; } // Use Redis PECL library. try { $this->redis = new Redis(); $this->redis->connect( $redis['host'], $redis['port'] ); $this->redis->setOption( Redis::OPT_SERIALIZER, $redis['serializer'] ); if ( isset( $redis['auth'] ) ) { $this->redis->auth( $redis['auth'] ); } if ( isset( $redis['database'] ) ) { $this->redis->select( $redis['database'] ); } $this->redis_connected = true; } catch ( RedisException $e ) { // When Redis is unavailable, fall back to the internal back by forcing all groups to be "no redis" groups $this->no_redis_groups = array_unique( array_merge( $this->no_redis_groups, $this->global_groups ) ); $this->redis_connected = false; } /** * This approach is borrowed from Sivel and Boren. Use the salt for easy cache invalidation and for * multi single WP installs on the same server. */ if ( ! defined( 'WP_CACHE_KEY_SALT' ) ) { define( 'WP_CACHE_KEY_SALT', '' ); } // Assign global and blog prefixes for use with keys if ( function_exists( 'is_multisite' ) ) { $this->global_prefix = ( is_multisite() || defined( 'CUSTOM_USER_TABLE' ) && defined( 'CUSTOM_USER_META_TABLE' ) ) ? '' : $table_prefix; $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix ) . ':'; } } /** * Is Redis available? * * @return bool */ protected function can_redis() { return $this->redis_connected; } /** * Adds a value to cache. * * If the specified key already exists, the value is not stored and the function * returns false. * * @param string $key The key under which to store the value. * @param mixed $value The value to store. * @param string $group The group value appended to the $key. * @param int $expiration The expiration time, defaults to 0. * @return bool Returns TRUE on success or FALSE on failure. */ public function add( $key, $value, $group = 'default', $expiration = 0 ) { return $this->add_or_replace( true, $key, $value, $group, $expiration ); } /** * Replace a value in the cache. * * If the specified key doesn't exist, the value is not stored and the function * returns false. * * @param string $key The key under which to store the value. * @param mixed $value The value to store. * @param string $group The group value appended to the $key. * @param int $expiration The expiration time, defaults to 0. * @return bool Returns TRUE on success or FALSE on failure. */ public function replace( $key, $value, $group = 'default', $expiration = 0 ) { return $this->add_or_replace( false, $key, $value, $group, $expiration ); } /** * Add or replace a value in the cache. * * Add does not set the value if the key exists; replace does not replace if the value doesn't exist. * * @param bool $add True if should only add if value doesn't exist, false to only add when value already exists * @param string $key The key under which to store the value. * @param mixed $value The value to store. * @param string $group The group value appended to the $key. * @param int $expiration The expiration time, defaults to 0. * @return bool Returns TRUE on success or FALSE on failure. */ protected function add_or_replace( $add, $key, $value, $group = 'default', $expiration = 0 ) { $derived_key = $this->build_key( $key, $group ); // If group is a non-Redis group, save to internal cache, not Redis if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) { // Check if conditions are right to continue if ( ( $add && isset( $this->cache[ $derived_key ] ) ) || ( ! $add && ! isset( $this->cache[ $derived_key ] ) ) ) { return false; } $this->add_to_internal_cache( $derived_key, $value ); return true; } // Check if conditions are right to continue if ( ( $add && $this->redis->exists( $derived_key ) ) || ( ! $add && ! $this->redis->exists( $derived_key ) ) ) { return false; } // Save to Redis $expiration = abs( intval( $expiration ) ); if ( $expiration ) { $result = $this->parse_predis_response( $this->redis->setex( $derived_key, $expiration, $value ) ); } else { $result = $this->parse_predis_response( $this->redis->set( $derived_key, $value ) ); } return $result; } /** * Remove the item from the cache. * * @param string $key The key under which to store the value. * @param string $group The group value appended to the $key. * @return bool Returns TRUE on success or FALSE on failure. */ public function delete( $key, $group = 'default' ) { $derived_key = $this->build_key( $key, $group ); // Remove from no_redis_groups array if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) { if ( isset( $this->cache[ $derived_key ] ) ) { unset( $this->cache[ $derived_key ] ); return true; } else { return false; } } $result = $this->parse_predis_response( $this->redis->del( $derived_key ) ); unset( $this->cache[ $derived_key ] ); return $result; } /** * Invalidate all items in the cache. * * @param int $delay Number of seconds to wait before invalidating the items. * @return bool Returns TRUE on success or FALSE on failure. */ public function flush( $delay = 0 ) { $delay = abs( intval( $delay ) ); if ( $delay ) { sleep( $delay ); } $this->cache = array(); if ( $this->can_redis() ) { $result = $this->parse_predis_response( $this->redis->flushdb() ); } return $result; } /** * Retrieve object from cache. * * Gets an object from cache based on $key and $group. * * @param string $key The key under which to store the value. * @param string $group The group value appended to the $key. * @return bool|mixed Cached object value. */ public function get( $key, $group = 'default' ) { $derived_key = $this->build_key( $key, $group ); if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) { if ( isset( $this->cache[ $derived_key ] ) ) { $this->cache_hits++; return is_object( $this->cache[ $derived_key ] ) ? clone $this->cache[ $derived_key ] : $this->cache[ $derived_key ]; } else { $this->cache_misses++; return false; } } if ( $this->redis->exists( $derived_key ) ) { $this->cache_hits++; $value = $this->redis->get( $derived_key ); } else { $this->cache_misses; return false; } $this->add_to_internal_cache( $derived_key, $value ); return is_object( $value ) ? clone $value : $value; } /** * Retrieve multiple values from cache. * * Gets multiple values from cache, including across multiple groups * * Usage: array( 'group0' => array( 'key0', 'key1', 'key2', ), 'group1' => array( 'key0' ) ) * * Mirrors the Memcached Object Cache plugin's argument and return-value formats * * @param array $groups Array of groups and keys to retrieve * @uses this::filter_redis_get_multi() * @return bool|mixed Array of cached values, keys in the format $group:$key. Non-existent keys null. */ public function get_multi( $groups ) { if ( empty( $groups ) || ! is_array( $groups ) ) { return false; } // Retrieve requested caches and reformat results to mimic Memcached Object Cache's output $cache = array(); foreach ( $groups as $group => $keys ) { if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) { foreach ( $keys as $key ) { $cache[ $this->build_key( $key, $group ) ] = $this->get( $key, $group ); } } else { // Reformat arguments as expected by Redis $derived_keys = array(); foreach ( $keys as $key ) { $derived_keys[] = $this->build_key( $key, $group ); } // Retrieve from cache in a single request $group_cache = $this->redis->mget( $derived_keys ); // Build an array of values looked up, keyed by the derived cache key $group_cache = array_combine( $derived_keys, $group_cache ); // Redis returns null for values not found in cache, but expected return value is false in this instance $group_cache = array_map( array( $this, 'filter_redis_get_multi' ), $group_cache ); $cache = array_merge( $cache, $group_cache ); } } // Add to the internal cache the found values from Redis foreach ( $cache as $key => $value ) { if ( $value ) { $this->cache_hits++; $this->add_to_internal_cache( $key, $value ); } else { $this->cache_misses++; } } return $cache; } /** * Sets a value in cache. * * The value is set whether or not this key already exists in Redis. * * @param string $key The key under which to store the value. * @param mixed $value The value to store. * @param string $group The group value appended to the $key. * @param int $expiration The expiration time, defaults to 0. * @return bool Returns TRUE on success or FALSE on failure. */ public function set( $key, $value, $group = 'default', $expiration = 0 ) { $derived_key = $this->build_key( $key, $group ); // If group is a non-Redis group, save to internal cache, not Redis if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) { $this->add_to_internal_cache( $derived_key, $value ); return true; } // Save to Redis $expiration = abs( intval( $expiration ) ); if ( $expiration ) { $result = $this->parse_predis_response( $this->redis->setex( $derived_key, $expiration, $value ) ); } else { $result = $this->parse_predis_response( $this->redis->set( $derived_key, $value ) ); } return $result; } /** * Increment a Redis counter by the amount specified * * @param string $key * @param int $offset * @param string $group * @return bool */ public function increment( $key, $offset = 1, $group = 'default' ) { $derived_key = $this->build_key( $key, $group ); $offset = (int) $offset; // If group is a non-Redis group, save to internal cache, not Redis if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) { $value = $this->get_from_internal_cache( $derived_key, $group ); $value += $offset; $this->add_to_internal_cache( $derived_key, $value ); return true; } // Save to Redis $result = $this->parse_predis_response( $this->redis->incrBy( $derived_key, $offset ) ); $this->add_to_internal_cache( $derived_key, (int) $this->redis->get( $derived_key ) ); return $result; } /** * Decrement a Redis counter by the amount specified * * @param string $key * @param int $offset * @param string $group * @return bool */ public function decrement( $key, $offset = 1, $group = 'default' ) { $derived_key = $this->build_key( $key, $group ); $offset = (int) $offset; // If group is a non-Redis group, save to internal cache, not Redis if ( in_array( $group, $this->no_redis_groups ) || ! $this->can_redis() ) { $value = $this->get_from_internal_cache( $derived_key, $group ); $value -= $offset; $this->add_to_internal_cache( $derived_key, $value ); return true; } // Save to Redis $result = $this->parse_predis_response( $this->redis->decrBy( $derived_key, $offset ) ); $this->add_to_internal_cache( $derived_key, (int) $this->redis->get( $derived_key ) ); return $result; } /** * Render data about current cache requests * * @return string */ public function stats() { ?>
_i18n( '_e', 'Cache Hits:' ); ?> _i18n( 'number_format_i18n', $this->cache_hits, false ); ?>
_i18n( '_e', 'Cache Misses:' ); ?> _i18n( 'number_format_i18n', $this->cache_misses, false ); ?>
_i18n( '_e', 'Using Redis?' ); ?>
can_redis() ? $this->_i18n( '__', 'yes' ) : $this->_i18n( '__', 'no' );
?>
_i18n( '_e', 'Caches Retrieved:' ); ?>