Skip to content

Instantly share code, notes, and snippets.

@aaroncampbell
Forked from markjaquith/README.md
Created September 5, 2011 19:38
Show Gist options
  • Save aaroncampbell/1195743 to your computer and use it in GitHub Desktop.
Save aaroncampbell/1195743 to your computer and use it in GitHub Desktop.
TLC Transients — On the fly WordPress transients with soft expiration and one-liner chained syntax.

TLC Transients

A WordPress transients interface with support for soft-expiration (use old content until new content is available), background updating of the transients (without having to wait for a cron job), and a chainable syntax that allows for one liners.

Examples

In this simple example, we're defining a feed-fetching callback, and then using tlc_transient with a chain to point to that callback and use it, all in one line. Note that since we haven't used background_only(), the initial load of this will cause the page to pause.

<?php>
// Define your callback (other examples use this)
function my_callback() {
	return wp_remote_retrieve_body(
		wp_remote_get( 'http://example.com/feed.xml', array( 'timeout' => 30 ) )
	);
}

// Grab that feed
echo tlc_transient( 'example-feed' )
	->updates_with( 'my_callback' )
	->expires_in( 300 )
	->get();
?>

This time, we'll set background_only() in the chain. This means that if there has been a hard cache flush, or this is the first-ever request, it will return false. So your code will have to be written to gracefully degrade if the feed isn't yet available. This, of course, triggers a background update. And once it is available, it will start returning the content.

<?php
echo tlc_transient( 'example-feed' )
	->updates_with( 'my_callback' )
	->expires_in( 300 )
	->background_only()
	->get();
?>

We don't have to chain, of course.

<?php
$t = tlc_transient( 'example-feed' );
if ( true ) {
	$t->updates_with( 'my_callback' );
} else {
	$t->updates_with( 'some_other_callback' );
}

$t->expires_in( 300 );
echo $t->get();
?>

We can even pass parameters to our callback.

<?php
// Define your callback
function my_callback_with_param( $param ) {
	return str_replace(
		'foo',
		$param,
		wp_remote_retrieve_body( wp_remote_get( 'http://example.com/feed.xml', array( 'timeout' => 30 ) ) ),
	);
}

// Grab that feed
echo tlc_transient( 'example-feed' )
	->updates_with( 'my_callback_with_param', array( 'bar' ) )
	->expires_in( 300 )
	->background_only()
	->get();
?>
<?php
class TLC_Transient_Update_Server {
public function __construct() {
add_action( 'init', array( $this, 'init' ) );
}
public function init() {
if ( isset( $_POST['_tlc_update'] ) ) {
$update = get_transient( 'tlc_update__' . $_POST['key'] );
if ( $update && $update[0] == $_POST['_tlc_update'] ) {
tlc_transient( $update[1] )
->expires_in( $update[2] )
->updates_with( $update[3], (array) $update[4] )
->set_lock( $update[0] )
->fetch_and_cache();
}
exit();
}
}
}
new TLC_Transient_Update_Server;
if ( !class_exists( 'TLC_Transient' ) ) {
class TLC_Transient {
public $key;
private $lock;
private $callback;
private $params;
private $expiration = 0;
private $force_background_updates = false;
public function __construct( $key ) {
$this->key = $key;
}
public function get() {
$data = get_transient( $this->key );
if ( false === $data ) {
// Hard expiration
if ( $this->force_background_updates ) {
// In this mode, we never do a just-in-time update
// We return false, and schedule a fetch on shutdown
$this->schedule_background_fetch();
return false;
} else {
// Bill O'Reilly mode: "We'll do it live!"
return $this->fetch_and_cache();
}
} else {
// Soft expiration
if ( $data[0] !== 0 && $data[0] < time() )
$this->schedule_background_fetch();
return $data[1];
}
}
private function schedule_background_fetch() {
if ( !$this->has_update_lock() ) {
set_transient( 'tlc_update__' . $this->key, array( $this->new_update_lock(), $this->key, $this->expiration, $this->callback, $this->params ), 300 );
add_action( 'shutdown', array( $this, 'spawn_server' ) );
}
return $this;
}
public function spawn_server() {
$server_url = home_url( '/?tlc_transients_request' );
wp_remote_post( $server_url, array( 'body' => array( '_tlc_update' => $this->lock, 'key' => $this->key ), 'timeout' => 0.01, 'blocking' => false, 'sslverify' => apply_filters( 'https_local_ssl_verify', true ) ) );
}
public function fetch_and_cache() {
// If you don't supply a callback, we can't update it for you!
if ( empty( $this->callback ) )
return false;
if ( $this->has_update_lock() && !$this->owns_update_lock() )
return; // Race... let the other process handle it
try {
$data = call_user_func_array( $this->callback, $this->params );
$this->set( $data );
} catch( Exception $e ) {}
$this->release_update_lock();
return $data;
}
public function set( $data ) {
// We set the timeout as part of the transient data.
// The actual transient has no TTL. This allows for soft expiration.
$expiration = ( $this->expiration > 0 ) ? time() + $this->expiration : 0;
set_transient( $this->key, array( $expiration, $data ) );
return $this;
}
public function updates_with( $callback, $params = array() ) {
$this->callback = $callback;
if ( is_array( $params ) )
$this->params = $params;
return $this;
}
private function new_update_lock() {
$this->lock = md5( uniqid( microtime() . mt_rand(), true ) );
return $this->lock;
}
private function release_update_lock() {
delete_transient( 'tlc_update__' . $this->key );
}
private function get_update_lock() {
$lock = get_transient( 'tlc_update__' . $this->key );
if ( $lock )
return $lock[0];
else
return false;
}
private function has_update_lock() {
return (bool) $this->get_update_lock();
}
private function owns_update_lock() {
return $this->lock == $this->get_update_lock();
}
public function expires_in( $seconds ) {
$this->expiration = (int) $seconds;
return $this;
}
public function set_lock( $lock ) {
$this->lock = $lock;
return $this;
}
public function background_only() {
$this->force_background_updates = true;
return $this;
}
}
}
// API so you don't have to use "new"
function tlc_transient( $key ) {
$transient = new TLC_Transient( $key );
return $transient;
}
// Example:
function sample_fetch_and_append( $url, $append ) {
$f = wp_remote_retrieve_body( wp_remote_get( $url, array( 'timeout' => 30 ) ) );
$f .= $append;
return $f;
}
function test_tlc_transient() {
$t = tlc_transient( 'foo' )
->expires_in( 30 )
->background_only()
->updates_with( 'sample_fetch_and_append', array( 'http://coveredwebservices.com/tools/long-running-request.php', ' appendfooparam ' ) )
->get();
var_dump( $t );
if ( !$t )
echo "The request is false, because it isn't yet in the cache. It'll be there in about 10 seconds. Keep refreshing!";
}
add_action( 'wp_footer', 'test_tlc_transient' );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment