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.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • ahallicks

    Having had a play with the code (which is very awesome by the way, and I really like the simple styling of the box!) one thing to note is that PHP’s DateTime Diff function is only for users of PHP 5.3.0 and above.

    My host has still not upgraded so I had to write a little work-around using a javascript method that does the same thing (converted to PHP for this) but doesn’t require PHP 5.3.0. I’ve added a little if that checks the PHP version and returns the correct method based on the version.

    I’ll post the code shortly at http://webdesignandseo.net/twitter/ for anyone else that wants to use it.

    I also made one slight change that automatically created the folder for cache, if it didn’t exist, and added permissions to it to make it writeable :-)

    • http://www.optimalworks.net/ Craig Buckler

      Thanks ahallicks.

      DateInterval() is a PHP 5.3-only object. I should have spotted that, but thanks for providing a fix.

      Great idea about creating and modifying the cache folder too.

  • http://www.sky-web.net/ Dr John

    I had errors returned for this line in the constructor

    $this->cache = __DIR__ . ‘/cache/’; // cache location

    Changing it to this solved the problem

    $this->cache = ‘cache/’; // cache location

    This happened on both my home test set-up and my host’s server (and thanks ahallicks for your mods, as both servers were running a slightly older version of php as well). Hope it helps someone.

    Perfect timing for the articles too – a client asked me about using Twitter, and displaying her Tweets on her web site 4 days ago! Today I used this to show her tweets and included those from one of the people she follows as well! I just made a second instance of the object and rendered that too. Two lines of extra code. She’s impressed.

  • tmapm

    I had to do this bug-fix to get the cache working for me:

    Line 32.
    $this->cache = dirname(__FILE__) . ‘/cache/’; // cache location

  • Cups

    Have not tried your code yet, but will be doing shortly.

    As an aside, and probably like many reading this I will be attempting to build this into some kind of CMS/blog/forum etc and interjecting a “tweet this now” feature for my clients.

    Great that you are dealing with formatting dates etc, but fresh in my mind is this news from yesterday:

    http://blog.programmableweb.com/2011/01/11/google-adds-api-for-url-shortener-and-link-analytics/

    So perhaps another follow-up post could integrate a url shortener too, s’just another regex and a cURL call after all.

    Part 4 maybe?

    • ahallicks

      Why would you need to shorten URL’s? Most of them will be shortened anyway, as the character limit for twitter is only 140. At any rate, you’re not going to be showing more than 140 characters for one tweet, so why would you need to add a URL shortener to it?

      Obviously, if you were talking about building a “tweet this now” into the widget that would be a good idea and maybe I’ve misread your comment?

  • sabduh

    cool article thanks a bunch

  • sabduh

    how about a widget that displays the user’s home_timeline after authentication?

    • http://www.optimalworks.net/ Craig Buckler

      That’s definitely possible, although it’s a little more complex and people may be nervous about publishing tweets from people they’re following directly on their website. I’ll look into it, though.

  • Cups

    OK, here is a use-case for a shortener.

    You have some admin users who create Articles. When they have finished publishing their article “Man falls of bike into ditch”, you pop up a screen which contains a prompt – do you want to tweet this story?

    Naturally the user may well want to sex up that headline for twitter… “Local driver shocked to see man fly over his car bonnet” and create a link to the story (which naturally, we can hard-code in for the user).

    But instead of http://www.seoreadywebsite.com/articles/man-falls-off-bike-into-ditch (71 chars) we can reduce the size of it automatically using Googles API http://goo.gl/NE2Mj (19 chars).

    What is more, we can now guarantee that the user can create text tweets of 120 chars – it just needs a countdowner to create a nice helpful GUI for our users which appends the short url onto the end of that text.

    That is the kind of thing I envisioned.

  • John Peter

    Syphon School of Programming provides advanced training in PHP, in Zend Certification curriculum. It is a Cochin, Kerala based institution. I wish to point you to their site: http://www.syphon.in

  • purencool

    Having a twitter feed on your web site is great but one issue we have found the type of content that we display in the feed affects the sites persona.

    In the end the part of the feed that is seen is our own. As you can’t trust what type of lanuguage others use or what links they will send you.

    But great artical thanks. I am going tp make some changes to my personal plugin

  • DaveWalker

    I hope I’ve not double posted. I first posted then registered.

    Many thanks to Craig and all comments. Thanks to this tutorial I’ve managed to make a WordPress Plugin which can host multiple occurrences of the widget and also deals with Twitter lists.

    See an example at – http://www.commonsenseminister.com/

  • http://www.historyfiles.co.uk/ Peter

    I love the detail in your step-through guide, even though as a beginner I find much of it baffling. But having put all the files in the correct places and applied chmod commands where needed, all I get on the web page when I run it is:

    Twitter Status Widget
    Parse error: syntax error, unexpected T_STRING, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or ‘}’ in /[my web site path]/twitter/twitterstatus.php on line 15

    And that’s without making any changes to the php files. I’m purely an html writer, not php, so maybe I’m missing something obvious here, but it would be a big relief if you could tell me what it was.