A Braindead Simple PHP Feed Writer Class
Creating a feed usually is a quite simple thing, since the structure of both RSS and Atom feeds is rather simple. However, simplicity covers its own traps - and the similarity of RSS and Atom often leads to duplicated code. I wrote a braindead simple PHP class to avoid this.
All kind of feeds, be it Atom or RSS, wrap up the same data in a quite similar way. Taking this a starting point, I first seperated the data into two simple data structures, one for the feed and one for the items:
/**
* Simple Date Structure holding feed data
*/
class FeedWriterTitle
{
var $title = "";
var $lastUpdated = ""; // this is a date, no string!
var $description = "";
var $link = "";
var $imageURL = "";
var $generator = "";
var $encoding = "ISO-8859-1";
}
/**
* Simple Date Structure holding feed item data
*/
class FeedWriterItem
{
var $description = "";
var $title = "";
var $link = "";
var $pubDate = ""; // this is a date, no string!
var $guid = "";
var $author = "";
var $content = "";
}
The variable's names are similar to the RSS naming convention, so they should be self explaining.
Having the data separated, I continued with the core algorithm to write any kind of feed and wrapped in up in a class using the template method design pattern:
class FeedWriter
{
/**
* Write feed. Needs subclass to work.
*
* @param Array of FeedWriter Item instances
* @param FeedWriterTitle instance
*/
function main(&$arrItems, &$title)
{
// Send headers
if (headers_sent() === false)
{
header("Last-Modified: " . date("D, M j Y G:i:s O", $title->lastUpdated));
$this->sendHeader();
}
$this->printTitle($title);
foreach($arrItems as $item)
{
$this->printItem($item);
}
$this->printEnd();
}
function sendHeader()
{
header("Content-Type: text/xml");
}
function printItem(&$item)
{
die("Not implemented");
}
function printTitle(&$title)
{
die("Not implemented");
}
function printEnd()
{
die("Not implemented");
}
}
What does this class do? It takes an array of FeedWriterItem instance and then builds a feed using a templated algorithm - which means this class relies on subclasses to do the real work. It first sends a last-modified header and then lets the subclass send the according content-type header (which is text/xml by default). Afterwards it calls the subclass to write the beginning of the feed xml, iterates through all items and again calls the according function on the subclass to output an item. Finally the subclass is responsible to finish the feed by printing the according closing tags.
This will get clear, when looking at the RSS implementation:
class RSSWriter extends FeedWriter
{
function sendHeader()
{
header("Content-Type: text/xml");
}
function printItem(&$item)
{
?>
<item>
<title><?php print $item->title; ?></title>
<link><?php print $item->link; ?></link>
<description><?php print $item->description; ?></description>
<content:encoded><![CDATA[<?php print $item->content; ?>]]></content:encoded>
<pubDate><?php print date("D, M j Y G:i:s O", $item->pubDate); ?></pubDate>
<guid isPermaLink="true"><?php print $item->guid; ?></guid>
<author><?php print $item->author; ?></author>
</item>
<?php
}
function printTitle(&$title)
{
print '<?xml version="1.0" encoding="' . $title->encoding . '"?>';
?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
>
<channel>
<title><?php print $title->title; ?></title>
<link><?php print $title->link; ?></link>
<description><?php print $title->description; ?></description>
<language>de</language>
<pubDate><?php print date("D, M j Y G:i:s O", $title->lastUpdated); ?></pubDate>
<generator><?php print $title->generator; ?></generator>
<image>
<title><?php print $title->title; ?></title>
<link><?php print $title->link; ?></link>
<url><?php print $title->imageURL; ?></url>
</image>
<?php
}
function printEnd()
{
?>
</channel>
</rss>
<?php
}
}
I tend to declare this as self-explaining.
Now up to the actual usage. Imaging a squirrel breeder web site offering a feed of all new born squirrels. The implementation can look something like this:
// Retrieve last 10 newborn squirrels
$arrSquirrels =& SquirrelDAO::getLastSquirrels(10);
// Figure out time of birth for latest squirrel
$lastUpdateTime = time();
if (count($arrSquirrels) > 0)
{
$latestSquirrel =& $arrSquirrels[0];
$lastUpdateTime = $latestSquirrel->timeBorn;
}
// Populate the title
$title =& new FeedWriterTitle();
$title->generator = "The Squirrel Site";
$title->lastUpdated = $lastUpdateTime;
$title->description = "Latest squirrels born";
$title->link = "-- Link to website --";
$title->title = "The Squirrel Site";
// Create item for each squirrel
$items = array();
foreach($arrSquirrels as $squirrel)
{
$item =& new FeedWriterItem();
$item->title = $squirrel->name;
$item->link = $squirrel->site;
$item->guid = $item->link;
$item->pubDate = $squirrel->timeBorn;
$item->description = $squirrel->name . " was born on " . $squirrel->timeBornFormatted() . "!";
$item->content = "<p>" . $item->description . "</p>";
$arrItems[] =& $item;
}
$writer =& new RSSWriter();
$writer->main($arrItems, $title);
Simple, ain't it? You can download the FeedWriter class and implementations for both RSS and Atom here. Unzip the file in a directory of your choice and include either the AtomWriter or RSSWriter class (or both) to start creating your feeds.
Listening while writing this article:
- Freestylers - Ruffneck featuring Navigator
- Raspigaous - Controle d'Identité
- Big Youth - House Of Dreadlocks
- Revolutionary Dub Warriors - Warrior
- The Beatles - Octopus's Garden
- BB King with Albert Collins - Stormy Monday Blues
- Billy Bragg - Between The Wars
- Chubawamba - WWW Dot
- Big Youth - Black Man Message
- Linton Kwesi Johnson - Come Weh Goh Dung Deh
- Linton Kwesi Johnson - Fite Dem Back
- Frankie Goes To Hollywood - War
- Percubaba - Poltergeist
- Nick Cave & The Bad Seeds - When I First Came To Town
- Eek-A-Mouse - Atlantis Lover