1

I have created a plugin that caches some of the data it uses - mainly Salesforce REST API call results, and expires them at intervals (by default every 24 hours).

The plugin calls these methods to control the caching:

/**
 * Check to see if this API call exists in the cache
 * if it does, return the transient for that key
 *
 * @param string $url The API call we'd like to make.
 * @param array  $args The arguents of the API call.
 * @return get_transient $cachekey
 */
public function cache_get( $url, $args ) {
    if ( is_array( $args ) ) {
        $args[] = $url;
        array_multisort( $args );
    } else {
        $args .= $url;
    }
    $prefix = esc_sql( $this->transient_prefix() );
    $cachekey = $prefix . md5( wp_json_encode( $args ) );
    return get_transient( $cachekey );
}

/**
 * Create a cache entry for the current result, with the url and args as the key
 *
 * @param string $url The API query URL.
 * @param array  $args The arguments passed on the API query.
 * @param array  $data The data received.
 * @param string $cache_expiration How long to keep the cache result around for.
 * @return Bool whether or not the value was set
 * @link https://developer.wordpress.org/reference/functions/set_transient/
 */
public function cache_set( $url, $args, $data, $cache_expiration = '' ) {
    if ( is_array( $args ) ) {
        $args[] = $url;
        array_multisort( $args );
    } else {
        $args .= $url;
    }
    $prefix = esc_sql( $this->transient_prefix() );
    $cachekey = $prefix . md5( wp_json_encode( $args ) );
    // Cache_expiration is how long it should be stored in the cache.
    // If we didn't give a custom one, use the default.
    if ( '' === $cache_expiration ) {
        $cache_expiration = $this->options['cache_expiration'];
    }
    return set_transient( $cachekey, $data, $cache_expiration );
}

/**
 * Get the cache transient prefix for this plugin and return it
 *
 * @return The transient prefix
 */
private function transient_prefix() {
    $transient_prefix = 'sfwp';
    return $transient_prefix;
}

/**
 * If there is a WordPress setting for how long to keep this specific cache, return it and set the object property
 * Otherwise, return seconds in 24 hours
 *
 * @param string $option_key The cache item to keep around.
 * @param int    $expire The default time after which to expire the cache.
 * @return The cache expiration saved in the database.
 */
public function cache_expiration( $option_key, $expire ) {
    $cache_expiration = get_option( $option_key, $expire );
    return $cache_expiration;
}

I'd like to provide users with a way to clear this cache manually though, in the event that they change up the configuration of their Salesforce objects and don't want to wait for the automatic expire. I'd like to avoid clearing the entire site's cache in this event though (this is my understanding of what wp_cache_flush() would do).

I've just added the $transient_prefix in the methods above, as I am realizing that this would be easy if the site is just using the Transients API. But if they're using an object cache, I'm out of my league.

I've created a cache_purge method like this:

/**
 * Create a cache entry for the current result, with the url and args as the key
 *
 * @param string $subset If we only want to purge WordPress data, Salesforce data, options, etc.
 * @return Bool whether or not the purge was successful
 */
public function cache_purge( $subset = '' ) {
    $prefix = esc_sql( $this->transient_prefix() );

    // cache is stored somewhere other than the options table
    if ( wp_using_ext_object_cache() ) {

    } else {
        // cache is stored in the options table. this is pretty easy.
        $options = $this->wpdb->options;
        $t = esc_sql( '_transient_timeout_' . $prefix . '%' );
        $sql = $wpdb ->prepare( "SELECT option_name FROM $options WHERE option_name LIKE '%s'", $t );
        $transients = $this->wpdb->get_col( $sql );
        foreach ( $transients as $transient ) {
            // Strip away the WordPress prefix in order to arrive at the transient key.
            $key = str_replace( '_transient_timeout_', '', $transient );
            // Now that we have the key, use WordPress core to the delete the transient.
            delete_transient( $key );
        }
    }
}

My understanding is that this would let me check for the existence of any external object cache (which I think would cover the caching plugins, as well as Varnish/memcache/etc.) and if there's not one, clear the transients API.

Am I correct so far? If so, what can I do to clear the same data from an object cache?

1

We note that the Transients API uses the Object Cache API, when we use an object-cache.php drop-in file.

For example if wp_using_ext_object_cache() returns true, then

get_transient( $transient ) 
-> wp_cache_get( $transient, 'transient' )

set_transient( $transient, $value, $expiration ) 
-> wp_cache_set( $transient, $value, 'transient', $expiration )

delete_transient( $transient ) 
-> wp_cache_delete( $transient, 'transient' )

where the group is transient.

The Object Cache API doesn't support wp_cache_delete_group() to delete cache by a group and it currently has a wontfix in ticket #4476.

The reason given there was that not all persistent cache solutions supports it. But there are some ways described in the ticket, that depend on the cache solution.

As an example, the WP Redis persistent object cache implementation supports wp_cache_delete_group(), but not the Memcached Object Cache implementation.

If we use transients in our plugin, with persistent object cache, then we should note that if we used:

if( function_exists( 'wp_cache_delete_group' ) )
{
    wp_cache_delete_group( 'transient' );
}

then it looks like we would flush all the transients, not just the ones set in our own plugin.

If the cache keys are known beforehand, then we could delete those with:

wp_cache_delete( $cachekey, $cachegroup );

where $cachegroup is 'transient' if we used transients.

  • Thank you. I think the thing I'm unclear on is how you identify the cache group itself. You say wp_cache_delete_group( 'transient' ) and, if I'm understanding correctly, this would allow the plugin to delete a cache group if the cache implementation supported it. So in your example, it would work with Redis but not Memcache. So for memcache, you'd just let users clear the entire cache, I suppose. But what is the transient value there that names the group, for Redis to use? – Jonathan Stegall Sep 12 '17 at 17:24
  • 1
    WordPress sets the 'transient' cache group, when we use *_transient() calls, so we can't change that group, unless we go to the lower level and write our own wp_cache_*() calls. But I wonder if we could store the keys in another key/value (without expire), so we could loop over those to delete? That would mean extra book-keeping. Alternatively if we know the sets of keys beforehand that can be generated, we could loop over all those possibilities (if they're not too many) and delete. If I recall correctly W3TC allows the user to clear the object cache, but I think it's a total flush or ?? – birgire Sep 12 '17 at 17:57
  • Yeah I think W3TC is a full object cache clear. Super cache is as well. I suppose it sounds like, for my purpose, the best way is to try to store the set of keys beforehand (I'm assuming I would store this in a transient also) and then allow the purge method to delete each key. Is that what you're saying? – Jonathan Stegall Sep 12 '17 at 18:35
  • yes or variation of it that suits your needs. – birgire Sep 12 '17 at 19:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.