Alright, gather ’round, code wranglers! Let’s talk about WordPress’s internal caching mechanism when it’s flying solo, without any fancy external caching plugins. We’re diving deep into wp_cache_get()
and wp_cache_set()
, the unsung heroes of WordPress performance when no other cache provider is configured.
(Ahem, clears throat, adjusts imaginary glasses) Let’s begin!
The Stage: wp-includes/cache.php
This is where the magic happens, or rather, the basic magic happens. When you don’t have Memcached, Redis, or some other caching system set up, WordPress relies on its own internal, in-memory object cache. This cache is held in a global variable, $wp_object_cache
.
The Players: wp_cache_get()
and wp_cache_set()
These two functions are the workhorses. wp_cache_get()
retrieves data from the cache, and wp_cache_set()
stores data into it.
Scenario 1: No External Cache Provider
If you haven’t installed and activated a persistent object cache plugin (like Memcached Object Cache, Redis Object Cache, etc.), WordPress uses its default, non-persistent object cache. This means the cache exists only for the duration of a single request. Once the page is generated and sent to the user, the cache is wiped clean.
Under the Hood: $wp_object_cache
The $wp_object_cache
variable is an instance of the WP_Object_Cache
class. Let’s take a look at its structure.
/**
* Core class used to implement an object cache.
*
* @since 2.0.0
*/
class WP_Object_Cache {
/**
* Holds the cached objects.
*
* @var array
*/
private $cache = array();
/**
* The number of cache calls.
*
* @var int
*/
public $cache_hits = 0;
/**
* Number of times cache missed.
*
* @var int
*/
public $cache_misses = 0;
/**
* List of global groups.
*
* @var array
*/
protected $global_groups = array();
/**
* List of non-persistent groups.
*
* @var array
*/
protected $non_persistent_groups = array();
/**
* Holds the value if WordPress is running in network mode.
*
* @var bool
*/
protected $multisite;
/**
* The blog ID.
*
* @var int
*/
protected $blog_id;
/**
* Instantiate the cache.
*
* @since 2.0.0
*
* @global array $wp_db_versions
*/
public function __construct() {
global $wp_db_versions;
$this->multisite = is_multisite();
$this->blog_id = (int) get_current_blog_id();
if ( function_exists( 'wp_cache_add_global_groups' ) ) {
wp_cache_add_global_groups( (array) $wp_db_versions );
}
}
// ... (Other methods will be discussed later) ...
}
global $wp_object_cache;
if ( ! isset( $wp_object_cache ) ) {
$wp_object_cache = new WP_Object_Cache();
}
Key things to note:
$cache
: This is the heart of the cache. It’s a simple associative array where data is stored. The keys are constructed from the cache key and group.$cache_hits
and$cache_misses
: These track how often the cache is successfully used (a "hit") versus how often it fails to find the requested data (a "miss"). Useful for debugging and performance analysis.$global_groups
and$non_persistent_groups
: These are used to manage which groups of cached data should be shared across the entire WordPress installation (global) and which should be specific to a particular site or part of the application (non-persistent). In the context of the internal cache, these groups are generally less crucial than when using a persistent cache.
Diving into wp_cache_get()
Let’s dissect the wp_cache_get()
function.
/**
* Retrieves the cache contents, if it exists.
*
* The contents are first attempted to be retrieved from the cache. If the
* cache is empty, then the callback function will be called to generate
* the cache contents. The results of the callback function are stored in the
* cache using the cache key.
*
* @since 2.0.0
*
* @global WP_Object_Cache $wp_object_cache Object cache global instance.
*
* @param int|string $key Cache key.
* @param string $group Optional. Cache group. Default empty.
* @param bool $force Optional. Whether to force an update of the local
* cache from the persistent cache. Default false.
*
* @return mixed|false Cached data on success, false on failure.
*/
function wp_cache_get( $key, $group = '', $force = false ) {
global $wp_object_cache;
return $wp_object_cache->get( $key, $group, $force );
}
It’s a simple wrapper around the WP_Object_Cache::get()
method. Let’s look at that method:
/**
* Retrieves the cache contents, if it exists.
*
* @since 2.0.0
*
* @param int|string $key Cache key.
* @param string $group Optional. Cache group. Default empty.
* @param bool $force Optional. Whether to force an update of the local
* cache from the persistent cache. Default false.
*
* @return mixed|false Cached data on success, false on failure.
*/
public function get( $key, $group = '', $force = false ) {
$key = (string) $key;
$group = (string) $group;
if ( isset( $this->cache[ $group ][ $key ] ) ) {
$this->cache_hits++;
if ( is_object( $this->cache[ $group ][ $key ] ) ) {
return clone $this->cache[ $group ][ $key ];
} else {
return $this->cache[ $group ][ $key ];
}
}
$this->cache_misses++;
return false;
}
Here’s the breakdown:
- Type Casting: The
$key
and$group
parameters are cast to strings. This ensures consistent key handling. - Cache Lookup: The code checks if the data exists in the
$cache
array using$this->cache[ $group ][ $key ]
. The cache is organized in a two-level structure: first by group, then by key within that group. - Cache Hit: If the data is found,
$this->cache_hits
is incremented. The data is then returned. Note theclone
operation when dealing with objects. This prevents modifications to the cached object from affecting the original data. - Cache Miss: If the data is not found,
$this->cache_misses
is incremented, andfalse
is returned.
Example wp_cache_get()
Usage:
$my_data = wp_cache_get( 'my_unique_key', 'my_group' );
if ( false === $my_data ) {
// Data not found in cache. Let's fetch it from the database or calculate it.
$my_data = expensive_operation(); // Replace with your actual data fetching code
// Store the data in the cache for future use.
wp_cache_set( 'my_unique_key', $my_data, 'my_group' );
}
// Now $my_data contains either the cached data or the newly fetched/calculated data.
echo "My data: " . $my_data;
The Star of the Show: wp_cache_set()
Now, let’s explore wp_cache_set()
.
/**
* Sets the data contents into the cache.
*
* The cache key and group are used to name the cache and to group the cache
* contents.
*
* @since 2.0.0
*
* @global WP_Object_Cache $wp_object_cache Object cache global instance.
*
* @param int|string $key Cache key.
* @param mixed $data Data to store in the cache.
* @param string $group Optional. Cache group. Default empty.
* @param int $expire Optional. When to expire the cache contents, in seconds.
* Default 0 (no expiration).
*
* @return bool False if not an object. True on success.
*/
function wp_cache_set( $key, $data, $group = '', $expire = 0 ) {
global $wp_object_cache;
return $wp_object_cache->set( $key, $data, $group, $expire );
}
Again, this is a wrapper for the WP_Object_Cache::set()
method:
/**
* Sets the data contents into the cache.
*
* @since 2.0.0
*
* @param int|string $key Cache key.
* @param mixed $data Data to store in the cache.
* @param string $group Optional. Cache group. Default empty.
* @param int $expire Optional. When to expire the cache contents, in seconds.
* Default 0 (no expiration).
*
* @return bool False if not an object. True on success.
*/
public function set( $key, $data, $group = '', $expire = 0 ) {
$key = (string) $key;
$group = (string) $group;
if ( is_object( $data ) ) {
$this->cache[ $group ][ $key ] = clone $data;
} else {
$this->cache[ $group ][ $key ] = $data;
}
return true;
}
The process:
- Type Casting:
$key
and$group
are cast to strings, similar towp_cache_get()
. - Data Storage: The
$data
is stored in the$cache
array at the location specified by the$group
and$key
:$this->cache[ $group ][ $key ] = $data;
. Again, if$data
is an object, it’s cloned to prevent modification of the original. - Return Value: The function always returns
true
to indicate success. (Historically, it returnedfalse
if the data was not an object, but this behavior has changed.)
Important Considerations with the Internal Cache
- Request-Bound: The internal cache is not persistent. Data stored in the cache is only available during the current request. Once the page is generated and sent to the user, the cache is destroyed.
- Expiration: The
$expire
parameter inwp_cache_set()
is ignored when using the internal cache. Expiration only comes into play with persistent object caches. - Memory Limits: The internal cache is limited by the PHP memory allocated to WordPress. Caching large amounts of data can lead to memory exhaustion errors. Be mindful of what you’re caching.
- Object Cloning: Both
wp_cache_get()
andwp_cache_set()
clone objects before returning or storing them. This is a good practice to prevent accidental modification of the cached data.
Global vs. Non-Persistent Groups
Let’s look at how $global_groups
and $non_persistent_groups
are used.
/**
* Adds a group to the list of global groups.
*
* @since 2.9.0
*
* @param string|array $groups A group or an array of groups to add.
*/
public function add_global_groups( $groups ) {
if ( ! is_array( $groups ) ) {
$groups = (array) $groups;
}
$this->global_groups = array_merge( $this->global_groups, $groups );
$this->global_groups = array_unique( $this->global_groups );
}
/**
* Adds a group to the list of non-persistent groups.
*
* @since 3.5.0
*
* @param string|array $groups A group or an array of groups to add.
*/
public function add_non_persistent_groups( $groups ) {
if ( ! is_array( $groups ) ) {
$groups = (array) $groups;
}
$this->non_persistent_groups = array_merge( $this->non_persistent_groups, $groups );
$this->non_persistent_groups = array_unique( $this->non_persistent_groups );
}
/**
* Resets internal cache keys and properties.
*
* @since 2.0.0
*/
public function reset() {
$this->cache = array();
$this->cache_hits = 0;
$this->cache_misses = 0;
}
/**
* Removes the contents of the cache key in the group.
*
* @since 2.0.0
*
* @param int|string $key What the key of the cache to delete.
* @param string $group Optional. Which group to delete from. Default empty.
*
* @return bool Always returns true.
*/
public function delete( $key, $group = '' ) {
$key = (string) $key;
$group = (string) $group;
unset( $this->cache[ $group ][ $key ] );
return true;
}
These functions help manage how the cache is handled, especially in a multisite environment or with persistent caching. add_global_groups
ensures certain data is consistent across all sites in a network, while add_non_persistent_groups
keeps data separate. reset
clears the cache, and delete
removes specific items.
Why Use wp_cache_get()
and wp_cache_set()
Even Without a Persistent Cache?
Even though the internal cache is short-lived, using wp_cache_get()
and wp_cache_set()
is still beneficial:
- Code Consistency: Using these functions prepares your code for easy integration with a persistent object cache later on. You won’t have to rewrite your code to use a different caching API.
- Reduced Redundant Operations: Within a single request, you might need the same data multiple times. The internal cache prevents you from performing the same database query or complex calculation repeatedly.
- Testing and Debugging: You can easily simulate caching behavior during development and testing.
Code Example: Optimizing a Loop
Let’s say you have a loop that needs to retrieve the same post meta data for each post.
// Without caching
$posts = get_posts( array( 'numberposts' => 10 ) );
foreach ( $posts as $post ) {
$meta_value = get_post_meta( $post->ID, 'my_custom_field', true );
echo $meta_value . '<br>';
}
// With caching
$posts = get_posts( array( 'numberposts' => 10 ) );
foreach ( $posts as $post ) {
$cache_key = 'post_meta_' . $post->ID . '_my_custom_field';
$meta_value = wp_cache_get( $cache_key, 'my_meta_group' );
if ( false === $meta_value ) {
$meta_value = get_post_meta( $post->ID, 'my_custom_field', true );
wp_cache_set( $cache_key, $meta_value, 'my_meta_group' );
}
echo $meta_value . '<br>';
}
In the second example, the get_post_meta()
function is only called once per post per request. Subsequent calls retrieve the data from the cache.
When to Consider a Persistent Cache
The internal cache is a good starting point, but for high-traffic sites, a persistent object cache (Memcached, Redis, etc.) is essential. Here’s when you should consider upgrading:
- Slow Page Load Times: If your site is consistently slow, especially for logged-in users or dynamic content, a persistent cache can significantly improve performance.
- High Database Load: If your database server is under heavy load, caching can reduce the number of database queries.
- Scalability: A persistent cache helps your site handle more traffic without requiring more server resources.
Troubleshooting
If you’re not seeing the expected caching behavior, here are some things to check:
- Typos: Double-check the cache keys and group names in
wp_cache_get()
andwp_cache_set()
. - Data Changes: Make sure the data you’re caching isn’t being modified elsewhere in your code.
- Object Cloning Issues: If you’re caching objects, ensure that the cloning is working correctly and that you’re not accidentally modifying the original cached object.
In Conclusion
While not as powerful as persistent caching solutions, the internal WordPress object cache provides a basic level of caching functionality. Understanding how wp_cache_get()
and wp_cache_set()
work under the hood is crucial for optimizing your WordPress site’s performance and preparing it for future scalability. Remember, caching is all about reducing redundant operations and serving data more efficiently. Use these functions wisely, and your WordPress site will thank you for it!
Now, go forth and cache all the things! (Responsibly, of course.)