‘Faster WordPress Templates With The Transients API’ Slides, Code Now Available

Here’s the slide deck from my WordCamp Maine 2016 presentation, “Faster Templates With The WordPress Transients API”:

Direct link to Google Drive document: https://docs.google.com/presentation/d/1qRhAJ22feA7ojl7bG-u4DP3g-d3q53WSrtnQeKXP3cs/edit?usp=sharing

Related posts

I have previously written two posts on the WordPress Transients API: How To: Use Transients To Speed Up WordPress Templates and Using WordPress Transients To Speed Up Your PHP-Heavy Templates: Introduction.

Sample code: Transients for complex / recycled WP Queries

This code sample is a generalization of the code used to create the movies landing page on MaineToday.com. It allows us to cache the displays of movies, which require somewhat expensive custom field queries; and it allows us to recycle the “jump” menu at the top of the page, for use on the movies and theaters single page templates.

<?php
function create_pulldown_transient() {
	//'jump' menu
	$output = '';

	$output = '<!-- transient created ' . date( 'r' ) . ' -->';
	$output .= '<select id="mt-movies-select" class="mt-movies-jump">';
	$output .= '<option value="">Pick a movie ...</option>';

	$movies = get_posts(
		array(
			'posts_per_page' => -1, 
			'post_type' => 'mt_movie', 
			'meta_key' => 'movie_nowplaying', 
			'meta_value' => '1', 
			'orderby' => 'title', 
			'order' => 'ASC'
		)
	);

	foreach( $movies as $movie ) {
		$output .= '<option value="' . get_post_permalink( $movie->ID ) . '">' . $movie->post_title . '</option>' . "\n";
	}

	$output .= '</select>';
	$output .= '<select id="mt-theaters-select" class="mt-movies-jump">';
	$output .= '<option value="">or Pick a theater ...</option>';

	$theaters = get_posts(
		array(
			'posts_per_page' => -1, 
			'post_type' => 'mt_theater', 
			'orderby' => 'title', 
			'order' => 'ASC'
		)
	);
	foreach( $theaters as $theater ) {
		$output .= '<option value="' . get_post_permalink( $theater->ID ) . '">' . $theater->post_title . '</option>' . "\n";
	}

	$output .= '</select>';
	
	return $output;
}

function create_movies_transient() {
	$output = '';
	
	//get timestamps for start and end of this week
	date_default_timezone_set( 'GMT' );
	$start = new DateTime( 'Sunday last week midnight' );
	$end = new DateTime( 'Saturday this week 23:59:59' );

	//get all nowplaying movies new this week
	$query = new WP_Query(
		array(
			'post_type' => 'mt_movie', 
			'meta_query' => array(
				array(
					'key' => 'movie_releasedate',
					'value' => array( $start->format( 'U' ), $end->format( 'U' ) ),
					'compare' => 'BETWEEN',
					'type' => 'NUMERIC'
				)
			),
			'posts_per_page' => -1, 
			'orderby' => 'title', 
			'order' => 'ASC'
		)
	);

	$output = '<!-- transient created ' . date( 'r' ) . ' -->';
	$output .= '<h2>NEW THIS WEEK</h2>';

	$has_new_movies = false;

	if( $query->have_posts() ) {
		$new_movies_segment = '<ul>';
		while( $query->have_posts() ) { 
			$query->next_post();
			
			$msdate =  get_post_meta( $query->post->ID, 'movie_releasedate', true );
			$today = mktime( 23, 59, 59, date( 'n' ), date( 'j' ), date( 'Y' ) );
			
			//make sure "new this week" movie isn't a one-day engagement prior to today
			if( ( '1' != get_post_meta( $query->post->ID, 'movie_nowplaying', true ) && $msdate > $today )  || '1' == get_post_meta( $query->post->ID, 'movie_nowplaying', true ) ) {
				$has_new_movies = true;
				$new_movies_segment .= '<li>';
				$new_movies_segment .= '<a href="' . get_post_permalink($query->post->ID) . '">';
				if( ! empty( get_the_post_thumbnail($query->post->ID, 'full') ) ) {
					$new_movies_segment .= get_the_post_thumbnail( $query->post->ID, 'full' );
				}
				else {
					$new_movies_segment .= '<img src="/wp-content/uploads/2014/04/nomovie.png" />';
				}
				$new_movies_segment .= '</a>';
				
				if( $msdate > $today && '1' != get_post_meta( $query->post->ID, 'movie_nowplaying', true ) ) {
					$new_movies_segment .= '<span class="mt-movie-opendate">Opens ' . date( 'l', $msdate ) . '</span>';
				}
				
				$new_movies_segment .= '<h3><a href="' . get_post_permalink($query->post->ID) . '">' . $query->post->post_title . '</a></h3>';
				$new_movies_segment .= '<p>' . $query->post->post_excerpt . '</p>';
				$new_movies_segment .= '</li>';
			}
			
		}
		$new_movies_segment .= '</ul>';
	}

	if( !$has_new_movies ) {
		$new_movies_segment = '<p><em>No new movies this week.</em></p>';
	}

	$output .= $new_movies_segment;

	$output .= '<h2>NOW PLAYING:</h2>';
	
	//all nowplaying movies not new this week
	$query = new WP_Query(
		array(
			'post_type' => 'mt_movie', 
			'meta_query' => array(
				array(
					'key' => 'movie_releasedate',
					'value' => array( $start->format( 'U' ), $end->format( 'U' ) ),
					'compare' => 'NOT BETWEEN',
					'type' => 'NUMERIC'
				),
				array(
					'key' => 'movie_nowplaying',
					'value' => '1'
				)
			),
			'posts_per_page' => -1, 
			'orderby' => 'title', 
			'order' => 'ASC'
		)
	);

	if( $query->have_posts() ) {
		$output .= '<ul>';
		while( $query->have_posts() ) {
			$query->next_post();
			$output .= '<li>';
			$output .= '<a href="' . get_post_permalink($query->post->ID) . '">';
			if( ! empty( get_the_post_thumbnail( $query->post->ID, 'full' ) ) ) {
				$output .= get_the_post_thumbnail( $query->post->ID, 'full' );
			}
			else {
				$output .= '<img src="/wp-content/uploads/2014/04/nomovie.png" />';
			}
			$output .= '</a>';
			$output .= '<h3><a href="' . get_post_permalink( $query->post->ID ) . '">' . $query->post->post_title . '</a></h3>';
			$output .= '<p>' . $query->post->post_excerpt . '</p>';
			$output .= '</li>';
		}
		$output .= '</ul>';
	}
	else {
		$output .= '<p><em>No movies now playing.</em></p>';
	}

	return output;
}

//render page
get_header();
get_sidebar();
?>
<div class="mt-main-col">
	<div class="mt-movies-picker">
		<h2>Movies &amp; Showtimes</h2>
		<?php
		//nav pulldown menus
		if( false === ( $pulldown = get_transient( 'movies_picker' ) ) {
			$pulldown = create_pulldown_transient();
			set_transient( 'movies_picker', $pulldown, 14 * HOUR_IN_SECONDS );
		}
		echo $pulldown;
		?>
	</div><!-- //mt-movies-picker -->

	<div class="mt-movies-landing">
	<?php
	//movies listing
	if( false === ( $movies = get_transient( 'movies_listing' ) ) {
		$movies = create_movies_transient();
		set_transient( 'movies_listing', $movies, 14 * HOUR_IN_SECONDS );
	}
	echo $movies;
	?>
	</div><!-- //mt-movies-landing -->
</div><!-- //mt-main-col --> 
<?php
get_footer(); 
?>

Github gist: https://gist.github.com/dougvdotcom/26535a4dea6b491b1a9eb180e3ce4bed

Sample code: Transients to selectively cache parts of a page

This sample demonstrates how we can use transients to cache multiple segments of a single webpage for different durations. This removes the need to set our primary cache to expire the page as often as the most frequently changed code; instead, all content can be “pseudo-cached” according to its actual expiration.

It is lifted from the post, How To: Use Transients To Speed Up WordPress Templates.

In this case, we’re building a local, live music blog landing page, with three sections:

  • A listing of upcoming gigs, which refreshes every 5 minutes;
  • a list of music reviews, which updates every 4 hours; and
  • a list of venues, which updates once a day.
//get all asides that are in the category music-gigs
if( false === ( $output = get_transient( 'my-gig-asides-list' ) ) ) {
	//rebuild transient
	$gigs = new WP_Query( array(
		'post_status' => 'publish',
		'posts_per_page' => 10,
		'orderby' => 'date',
		'order' => 'DESC',
		'tax_query' => array(
			array(
				'taxonomy' => 'post_format',
				'field' => 'slug',
				'terms' => array(
					'post-format-aside'
				),
				'operator' => 'IN'
			),
			array(
				'taxonomy' => 'category',
				'field' => 'slug',
				'terms' => array(
					'music-gigs'
				),
				'operator' => 'IN'
			)
		)
	) );
	
	//create transient's value
	$output = '<div id="music-gigs"><h2>Upcoming gigs</h2><ul>';
	if( $gigs->have_posts() ) {
		while( $gigs->have_posts() ) {
			$gigs->the_post();
			$output .= '<li><a href="' . the_permalink() . '"><h3>' . the_title() . '</h3></a>';
			$output .= the_excerpt() . '</li>';
		}
	} else {
		$output .= '<li>No upcoming gigs available.</li>';
	}
	$output .= "</ul></div>";
	
	//store transient in WP database; expires in 5 minutes
	set_transient( 'my-gig-asides-list', $output, 5 * MINUTE_IN_SECONDS );
}
//one way or another, we have a valid value in $output; echo it
echo $output;

//get the music review posts
if( false === ( $output = get_transient( 'my-music-reviews' ) ) ) {
	$reviews = new WP_Query( array(
		'post_type' => 'music_reviews',
		'post_status' => 'publish',
		'posts_per_page' => -1,
		'orderby' => 'title',
		'order' => 'ASC'
	) );

	$output = '<div id="music-reviews"><h2>Music reviews</h2>';
	if( $reviews->have_posts() ) {
		while( $reviews->have_posts() ) {
			$output .= '<div class="music-review">';
			$reviews->the_post();
			$output .= '<a href="' . the_permalink() . '"><h2>' . the_title() . '</h2></a>';
			$output .= the_excerpt();
			$output .= "</div>";
		}
	} else {
		$output .= "<p>No venues available.</p>";
	}
	$output .= '</div>';
	
	//this transient lasts 4 hours
	set_transient( 'my-music-reviews', $output, 4 * HOUR_IN_SECONDS );
}
echo $output;

//get the music review posts
if( false === ( $output = get_transient('my-venue-reviews') ) ) {
	$reviews = new WP_Query( array(
		'post_type' => 'music_venues',
		'post_status' => 'publish',
		'posts_per_page' => -1,
		'orderby' => 'title',
		'order' => 'ASC'
	) );

	$output = '<div id="music-venues"><h2>Where to listen</h2>';
	if( $reviews->have_posts() ) {
		while( $reviews->have_posts() ) {
			$output .= '<div class="music-venues">';
			$reviews->the_post();
			$output .= '<a href="' . the_permalink() . '"><h2>' . the_title() . '</h2></a>';
			$output .= the_excerpt();
			$output .= "</div>";
		}
	} else {
		$output .= "<p>No venues available.</p>";
	}
	$output .= '</div>';
	
	//this transient lasts one day
	set_transient( 'my-venue-reviews', $output, DAY_IN_SECONDS );
}
echo $output;

Github gist: https://gist.github.com/dougvdotcom/804e1661d4c9fa4acc3fd5d3d96db74d

Sample code: Showing content to authenticated users

This sample demonstrates how we can use transients to show content to logged-in users. The same techniques can be used to allow us to leverage PHP superglobals, such as cookies, GET and POST objects, server / environment variables, etc., all of which are generally unavailable, or at least very unpredictable, if we are caching a web page.

This code is lifted from How To: Use Transients To Speed Up WordPress Templates

if( current_user_can( 'read' ) ) {
	if( false === ( $output = get_transient( 'my-insider-news' ) ) ) {
		$blurbs = new WP_Query( 
			array(
				'post_status' => 'publish',
				'posts_per_page' => 5,
				'orderby' => 'date',
				'order' => 'DESC',
				'tax_query' => array(
					array(
						'taxonomy' => 'post_format',
						'field' => 'slug',
						'terms' => array( 'post-format-aside' ),
						'operator' => 'IN'
					),
					array(
						'taxonomy' => 'category',
						'field' => 'slug',
						'terms' => array( 'insiders' ),
						'operator' => 'IN'
					)
				)
			) 
		);

		$output = '<ul>';
		if( $blurbs->have_posts() ) {
			while( $blurbs->have_posts() ) {
				$blurbs->the_post();
				$output .= '<li><a href="' . the_permalink() . '"><h3>' . the_title() . '</h3></a>';
				$output .= the_excerpt() . '</li>';
			}
		} else {
			$output .= '<li>No insider information today.</li>';
		}
		$output .= '</ul>';

		//transient will last a half-hour
		set_transient('my-insider-news', $output, 30 * MINUTE_IN_SECONDS);
	}
} else {
	$output = "<p>Join the Insiders to get news before everyone else!</p>";
}

echo $output;

Github gist: https://gist.github.com/dougvdotcom/b1d761cb590ddf85269fffa27e333ae6

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  • Check out the Commenting Guidelines before commenting, please!
  • Want to share code? Please put it into a GitHub Gist, CodePen or pastebin and link to that in your comment.
  • Just have a line or two of markup? Wrap them in an appropriate SyntaxHighlighter Evolved shortcode for your programming language, please!