- - By Craig Buckler

How to Create Your Own Twitter Widget in PHP, Part 3

In Part 1 of this series, we examined the Twitter API, created a PHP TwitterStatus class, and imported the latest tweets in JSON format. In Part 2, we parsed the Twitter data, replaced links, and generated the complete HTML for our widget. In this last post, we’ll cache our widget and translate tweet dates into a friendlier format — download the full source code here.

The Case for Caching

Currently, our TwitterStatus class will fetch the most recent tweets every time we call the Render() method. Retrieving and parsing that data takes several seconds and incurs a significant processing overhead. It’s simply not necessary; we could receive 10 visitors per second to our website, yet only post a single tweet every month. In addition, Twitter will prevent applications making too many unnecessary calls to the API.

Therefore, we’ll cache our resulting HTML code in a file on the local system. If the age of that file is less than $CacheFor seconds (a public property), it will be retrieved and used rather than interrogating the Twitter API.

Our Render() method will therefore generate a filename in the local $cache string. It’s set to $this->cache (the directory where data is cached), followed by the Twitter ID, a dash, the number of tweets, and a .html extension, e.g. ‘/twitter/cache/sitepointdotcom-10.html’:


public function Render() {

	// returned HTML string
	$render = '';

	// cached file available?
	$cache = $this->cache . $this->ID . '-' . $this->Count . '.html';

We can then calculate the number of seconds which have expired since the file was created (assuming it exists):


	$cacheage = (file_exists($cache) ? time() - filemtime($cache) : -1);

If the cache file doesn’t exist or the number of seconds is greater than $CacheFor, we need to re-generate the HTML widget cache file using the code we wrote in Part 2:


	if ($cacheage < 0 || $cacheage > $this->CacheFor) {

If the HTML is correctly generated in our $render string, we can save it to our cache file:


	// update cache file
	file_put_contents($cache, $render);
important: File permissions

File permissions are the most likely and frustrating problem you’ll encounter with this code. Your web server and/or PHP must have permission to read, write and modify files within the cache directory. On Linux and Mac systems, chmod 755 may be enough … but not always. On Windows, it’s usually a matter of granting appropriate folder permissions to the IIS user account.

In the code I’ve supplied, a caching failure will simply re-fetch the data from Twitter. That’s not always desirable in a production environment–it may be preferable to raise an error.

The $render string will be empty if a valid cache file exists. It will also be empty if the Twitter API call fails; in that situation, we’ll retrieve the last available cached file:


// fetch from cache
if ($render == '' && $cacheage > 0) {
	$render = file_get_contents($cache);
}

To complete our Render() method, we return $render to the calling code. However, we must parse the dates first…


return $this->ParseDates($render);

Date Parsing

In Part 2, you will have noticed that tweet creation dates are rendered as “{DATE:Thu Dec 23 10:00:00 +0000 2010}”. So why didn’t we convert these to nicely-formatted dates when the HTML was generated?

Had we simply wanted to display the tweet’s date and time, we could have parsed it during the caching phase to result in something like “10:10am on 23 December 2010.” However, we can optionally show relative date strings such as “just now,” “10 minutes ago,” “yesterday,” “last week” or “3 months ago.” If these were generated at the caching stage, they could be incorrect within a few minutes.

For example, if a tweet was shown as “2 minutes ago,” that text would remain static for the lifetime of the cache file. If we’re caching for 15 minutes and re-visited the page 5 minutes later, it would still show “2 minutes ago” rather than “7 minutes ago.” Therefore, we must store the date/time in the cache file and translate it when the widget is displayed.

Dates are parsed by the private ParseDates method which is passed our rendered/cached HTML. First, it stores the current time as $now so we can use it within relative date calculations:


private function ParseDates($str) {

	// current datetime
	$now = new DateTime();

A regular expression is used to extract all dates into the $m array using preg_match_all(). We then loop through every matched date and set $stime to a new DateTime object:


	preg_match_all('/{DATE:(.+)}/U', $str, $m);
	for ($i = 0, $il = count($m[0]); $i &lt; $il; $i++) {

		$stime = new DateTime($m[1][$i]);

Now for the tricky part. If the developer has chosen a ‘friendly’ date format, we’ll use the PHP’s DateTime diff() function to return a DateInterval object.

We now know the difference in years ($y DateInterval property), months ($m), days ($d), hours ($h), minutes ($i) and seconds ($s) but we need to be a little more fuzzy with our logic. For example, if a tweet was sent 2 minutes and 50 second ago, it’s better to display “3 minutes ago.” Similarly, if it were 11 months and 20 days ago, it’s better to display “last year.”

The result of the friendly date format is stored as a string named $date:


		if ($this->DateFormat == 'friendly') {

			// friendly date format
			$ival = $now->diff($stime);

			$yr = $ival->y;
			$mh = $ival->m + ($ival->d > 15);
			if ($mh > 11) $yr = 1;
			$dy = $ival->d + ($ival->h > 15);
			$hr = $ival->h;
			$mn = $ival->i + ($ival->s > 29);

			if ($yr > 0) {
				if ($yr == 1) $date = 'last year';
				else $date = $yr . ' years ago';
			}
			else if ($mh > 0) {
				if ($mh == 1) $date = 'last month';
				else $date = $mh . ' months ago';
			}
			else if ($dy > 0) {
				if ($dy == 1) $date = 'yesterday';
				else if ($dy < 8) $date = $dy . ' days ago';
				else if ($dy < 15) $date = 'last week';
				else $date = round($dy / 7) . ' weeks ago';
			}
			else if ($hr > 0) {
				$hr += ($ival->i > 29);
				$date = $hr . ' hour' . ($hr == 1 ? '' : 's') . ' ago';
			}
			else {
				if ($mn < 3) $date = 'just now';
				else $date = $mn . ' minutes ago';
			}

		}

Alternatively, the developer might prefer a standard PHP date() format as specified in the $DateFormat property:


		else {
			// standard PHP date format
			$date = $stime->format($this->DateFormat);
		}

We can now replace the encoded date accordingly and return our string to the calling Render() method:


		// replace date
		$str = str_replace($m[0][$i], $date, $str);

	}

	return $str;
}

Twitter widget in PHP

Using Our TwitterStatus Class

Our TwitterStatus class is now complete. It can be used within any PHP code by include()-ing the twitterstatus.php code, instantiating a TwitterStatus object and echoing the result of the Render() method, e.g.,


<?php
include('twitter/twitterstatus.php');

// show last 10 updates from sitepointdotcom
$t = new TwitterStatus('sitepointdotcom', 10);
echo $t->Render();
?>

A sprinkling of CSS will transform our HTML into a pleasing Twitter feed widget.

I hope you found this series of posts useful. Please download the source files and use them as you wish. A link back to SitePoint is appreciated.

Sponsors