Try Tuts+ Premium, Get Cash Back!
Getting Started with the WordPress Transient API, Part 2

Getting Started with the WordPress Transient API, Part 2

Tutorial Details
  • Program: WordPress
  • Version (if applicable): 3.2.1
  • Difficulty: Medium
  • Estimated Completion Time: 60 minutes

In the first post in this series, we took defined what the API is, how it differs form the settings API, and some of the general calls that we can make to the API. In this post, we’ll be taking a look at a practical implementation of the API and how to handle some idiosyncrasies that come with dealing with expired data.

The WordPress Transients API is a powerful (but really-easy-to-use) aspect of the WordPress API. Generally speaking, it makes it really easy to store data with an expiration time, and makes it really easy to take advantage of various caching plugins to ultimately increase the speed of your site.


Setup The Plugin

For the purposes of this plugin, we’re going to create a simple widget that will list a blogs top commenters of all time. The goal of the plugin is to keep it lean so that we can highlight the transients functionality of the plugin.

Note that all of the plugin’s files can be retrieved from GitHub at any time. In the mean time, go ahead and create a directory called ‘top-commenters-cached’ and make sure that it has the following directory structure:

If you’re not interested in localizing the plugin, feel free to leave the ‘lang’ directory out of the plugin. At this point, we’re ready to begin writing the widget.


Basic Functionality

The plugin is simple. It should…

  • Allow the user to give the widget a custom
  • Retrieve the top 10 most popular commenters of the life of the blog

Easy enough. Here’s the code for the basic plugin. Note that it’s commented throughout so spend sometime reading through it to understand what we’re doing. If you’re unfamiliar with the widget API, don’t forget to check out our WordPress Widget Boilerplate post.

class Top_Commenters_Cached extends WP_Widget {

	const name = 'Top Commenters (Cached!)';
	const locale = 'top-commenters-cached-locale';
	const slug = 'top-commenters-cached';
	

	/*--------------------------------------------------*/
	/* Constructor
	/*--------------------------------------------------*/
	
	/**
	 * The widget constructor. Specifies the classname and description, instantiates
	 * the widget, loads localization files, and includes necessary scripts and
	 * styles.
	 */
	function Top_Commenters_Cached() {

		$widget_opts = array (
			'classname' => self::name, 
			'description' => __('A plugin used to demonstrate the WordPress Transients API for an Envato blog series.', self::locale)
		);	
		$this->WP_Widget(self::slug, __(self::name, self::locale), $widget_opts);
		
		load_plugin_textdomain(self::locale, false, dirname(plugin_basename( __FILE__ ) ) . '/lang/' );
		
	} // end constructor

	/*--------------------------------------------------*/
	/* API Functions
	/*--------------------------------------------------*/
	
	/**
	 * Outputs the content of the widget.
	 *
	 * @args			The array of form elements
	 * @instance
	 */
	function widget($args, $instance) {
	
		extract($args, EXTR_SKIP);
		
		echo $before_widget;
		
		$widget_title = empty($instance['widget_title']) ? '' : apply_filters('widget_title', $instance['widget_title']);
		$commenters = $this->query_for_commenters();
    
		// Display the widget
		include(WP_PLUGIN_DIR . '/' . self::slug . '/views/widget.php');
		
		echo $after_widget;
		
	} // end widget
	
	/**
	 * Processes the widget's options to be saved.
	 *
	 * @new_instance	The previous instance of values before the update.
	 * @old_instance	The new instance of values to be generated via the update.
	 */
	function update($new_instance, $old_instance) {
		
		$instance = $old_instance;
		
		$instance['widget_title'] = $this->strip($new_instance, 'widget_title');
    
		return $instance;
		
	} // end widget
	
	/**
	 * Generates the administration form for the widget.
	 *
	 * @instance	The array of keys and values for the widget.
	 */
	function form($instance) {
	
		$instance = wp_parse_args(
			(array)$instance,
			array(
				'widget_title' => ''
			)
		);
	
		$widget_title = $this->strip($instance, 'widget_title');
		
		// Display the admin form
    	include(WP_PLUGIN_DIR . '/' . self::slug . '/views/admin.php');
		
	} // end form

	/*--------------------------------------------------*/
	/* Private Functions
	/*--------------------------------------------------*/
	
	/**
	 * Retrieves the weekly top commenters for the past week and stores the values in the cache.
	 * If the cache is empty, then the function will request information from the database and 
	 * store it in the cache.
	 */
	private function query_for_commenters() {
	
		$commenters = null;
		
		// query the database for the top commenters
		global $wpdb;
		$commenters = $wpdb->get_results("
			select count(comment_author) as comments_count, comment_author, comment_type
			from $wpdb->comments
			where comment_type != 'pingback'
			and comment_author != ''
			and comment_approved = '1'
			group by comment_author
			order by comment_author desc
			LIMIT 10
		");

		return $commenters
	
	} // end query_for_commenters
	
	/*--------------------------------------------------*/
	/* Helper Functions
	/*--------------------------------------------------*/
  
	/**
	 * Convenience method for stripping tags and slashes from the content
	 * of a form input.
	 *
	 * @obj			The instance of the argument array
	 * @title		The title of the element from which we're stripping tags and slashes.
	 */
	private function strip($obj, $title) {
		return strip_tags(stripslashes($obj[$title]));
	} // end strip
	
} // end class
add_action('widgets_init', create_function('', 'register_widget("Top_Commenters_Cached");')); 
?>

Next, let’s take a look at the widget’s view. This is the part of the plugin that’s responsible for displaying the list of comments. It works by displaying the widget’s title (if it’s defined), then loops through the results creating a new list item.

 0) { ?>
	

'; foreach($commenters as $commenter) { $comment_list .= '
  • '; // actually print the commenter's name and the number of comments $comment_list .= $commenter->comment_author; $comment_list .= ' (' . $commenter->comments_count . ')'; $comment_list .= '
  • '; } // end foreach $comment_list .= ''; echo $comment_list; ?>

    Obviously, we’ve left out part of the code. Namely, the admin panel. It should simply allow for users to enter a title for their widget:

    Remember that you can view the full source code and download the plugin from its GitHub repository.


    Cache The Data

    At this point, we have a functional plugin; however, we’re not actually caching any data yet. The most intensive part of this plugin is when we’re querying the database and the results of the query are what we actually want to cache so let’s do that.

    Locate the query in the code:

    	global $wpdb;
    	$commenters = $wpdb->get_results("
    		select count(comment_author) as comments_count, comment_author, comment_type
    		from $wpdb->comments
    		where comment_type != 'pingback'
    		and comment_author != ''
    		and comment_approved = '1'
    		group by comment_author
    		order by comment_author desc
    		LIMIT 10
    	");
    

    And let’s store the results for 12 hours using the transients API:

    set_transient('top_commenters_cached', $commenters, 60 * 60 * 12);
    

    Pretty easy, right? Of course, we’re not done yet.


    Retrieve The Data

    Once the transient is set, we need to be able to retrieve the transient. Let’s set that up now:

    private function query_for_commenters() {
    	return get_transient('top_commenters_cached');
    } // end query_for_commenters
    

    That’s all there is to it!

    But wait – if you recall from the first post in the series, transients actually expire so we’re not guaranteed to retrieve the transient.


    Finding Missing Data

    Regardless of what you’re doing, retrieving data that has expired generally follows the same process:

    • Check for the existence of the transient
    • If it exists, use it
    • If it doesn’t exist, set it then retrieve it

    So let’s do that within the context of our plugin:

    private function query_for_commenters() {
    
    	$commenters = null;
    	
    	// check to see if the transient exists. set it if it's expired or missing
    	if(!get_transient('top_commenters_cached')) {
    
    		// query the database for the top commenters
    		global $wpdb;
    		$commenters = $wpdb->get_results("
    			select count(comment_author) as comments_count, comment_author, comment_type
    			from $wpdb->comments
    			where comment_type != 'pingback'
    			and comment_author != ''
    			and comment_approved = '1'
    			group by comment_author
    			order by comment_author desc
    			LIMIT 10
    		");
    
    		// store the result 
    		set_transient('top_commenters_cached', $commenters, 60 * 60 * 12);
    		
    	} // end if 
    	
    	// transient is guaranteed to exist now, so return it
    	return get_transient('top_commenters_cached');
    
    } // end query_for_commenters
    

    Conclusion

    Not too bad, right?

    As you can see, working with the Transients API requires little more than knowing when to use it and what functions are available. In my opinion, it’s one of the most powerful aspects of the WordPress API.

    If you find yourself retrieving large amounts of data, looking for a way to expire data for a refresh, or simply wanting to take advantage of caching plugins, remember to take advantage of the Transients API.

    Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
    • http://blog.nicolas-juen.fr Rahe

      Hi,

      Good post but you can’t use the self::locale for the translation of text string in the __() ,or _e() function ;)
      Ressource here : http://markjaquith.wordpress.com/2011/10/06/translating-wordpress-plugins-and-themes-dont-get-clever/

      Bye !

      • http://tom@tommcfarlin.com Tom McFarlin
        Author

        This is a good point and your timing is perfect – just this morning, I just committed a change to the WordPress Widget Boilerplate to solve this exact problem.

        Thanks for mentioning this!

    • bommel666

      Did i see a GIT integration in your finder window screenshot?

      • http://tommcfarlin.com Tom McFarlin
        Author

        Negative – I do use GitHub For Mac but I don’t have it integrated into Finder. Sadly :).

    • http://ryanvanetten.com ryanve

      These two posts are a big help—thanks. I tried it in a couple spots but it’s hard to tell the speed difference. I want to setup some tests to better gauge what operations are “expensive” enough.

    • http://comoserunbuenamanteblog.blogspot.com/ Como ser un buen amante

      As a new blogger I’ve certainly enjoyed your blog. You have great content. Cheers and thank you!

    • http://comocultivarunbonsai.blogspot.com/ como cultivar un bonsai

      Wow! That’s great! Your blog is very nice and informative. I can learn a lot from your great article. Thank you so much for your share.

    • Pingback: wp-coder.net » Theme.fm Weekly Roundup #9 and #10

    • Pingback: A Free wordpress newsletter » Theme.fm Weekly Roundup #9 and #10

    • http://www.autoforecastexpert.net/ chan@http://www.autoforecastexpert.net/

      Very good information. I kind a like the use of Transients API It’s something new to me but i enjoyed reading your articles

    • http://comosuperarlatimidez.blogspot.com/ como superar la timidez

      Very good blog ill keep loking

    • http://comoaumentarlossenos.blogspot.com/ como aumentar los senos

      This blog have very good info, thanks for share.

    • http://comosubirdepesorapidamente.blogspot.com/ como subir de peso

      Yeah so good, thanks

    • http://remedioparalaresaca.blogspot.com/ remedio para la resaca

      Really intersting

    • http://www.ceor.de/portfolio ceor

      I have been exploring for a little bit for any high quality articles or weblog posts on this sort of area . Exploring in Yahoo I finally stumbled upon this site. Reading this info So i am satisfied to convey that I have an incredibly good uncanny feeling I discovered exactly what I needed. I so much definitely will make sure to don?t forget this web site and give it a look on a continuing basis.