This article explains basics of content syndications and demonstrates how to use Zend Framework Zend_Feed classes for consuming a news feed of your site.
Content syndication is now a technology any site cannot live without. Just because it is the simplest method that keeps your readers updated with site news, new releases, blog posts, and dozens of other information channels.
Content syndication formats are machine-readable XML formats
Syndication is performed by adding another view of your site content. The format of this view should be one of the formats other programs can read and process. A typical situation is when you have your content in HTML document, filled with the HTML tags and markup elements, and only a few designers and developers know how the information can be extracted from this view. So, it is almost impossible for your readers to stay connected without opening browser and selecting bookmark or typing site URL each time they want to check whether you updated the content or not.
Consider another scenario: you have your news in some easy readable and standardized text format so any software that supports one or two common formats can read your site news automatically and show them in a list of news channels user subscribed to. And they can subscribe to your news in one or two mouse clicks. It looks much better then the first scenario, doesn't it?
Channel and entities within is the main concept
The most typical feed that you may find in modern systems like Wordpress or blogger.com is made up of one channel and entities that belong to this channel, e.g. blog and its posts or post and its comments. But some formats, RSS for example, support multiple channels per feed as follows:
feed/ feed info channel1/ channel info item1/ item info content item2/ item info content channel2/ item3/ item info content
info sections contain information about the corresponding feed elements. The information section can contain the modification date, author, full or short text of the element, link to mp3 file (in case of podcast), and so on.
Syndication, RDF, RSS, Atom, feed are just different names of the one thing
As usual, when a new technology is growing up this process is powered by collaborative effort of many people and a lot of them have their own view on how to achieve the result. That makes a lot of ways of doing the same but only few of them are considered as "best practices" and in case of content syndication we have very few formats that are really used by the world: RSS and Atom. But very frequently you may see or hear the other "magic" names like "feed", "XML", "syndicate" or "subscribe". They are very close to each other. The "feed" word or (and what is a little bit more unambiguous) the "site feed" term means that some site, probably yours or which you are reading (like this one :-)), "syndicates" or makes available for feed aggregation software to "subscribe" on site content updates in a form of some XML format, typically Atom or RSS. Even if you read that some site has one of "RSS thingamajiggies" it is just means that it has an RSS feed.
Atom format details
As you may already know, the Atom format is based on the XML document format, which means that an Atom document is a text document that is human-readable and is made up of tags and some of the tags may have content. As is the convention, all Atom-specific tags are in the corresponding namespace. The root element (tag) of an Atom document is "feed" and the "feed" tag should contain some info about the feed and may contain the "entry" tags.
Notice that Atom feed contains only one "channel" in the "feed" tag and it is supposed that if site has more than one channel then each should have its own Atom feed.
The following pseudo-XML document demonstrates the basic structure of Atom feed:
<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom"> <id>{Uri}</id> <title type="{text|html|xhtml}" ? >{text|xhtmlDiv}</title> <updated>{xsd:dateTime}</updated> <generator uri="{Uri}" ? version="{text}" ? >{text}</generator> ? <icon>{Uri}</icon> ? <logo>{Uri}</logo> ? <rights type="{text|html|xhtml}" ? >{text|xhtmlDiv}</rights> ? <subtitle type="{text|html|xhtml}" ? >{text|xhtmlDiv}</subtitle> ? <author> <name>{text}</name> <uri>{Uri}</uri> ? <email>{EmailAddress}</email> ? </author> * <contributor> <name>{text}</name> <uri>{Uri}</uri> ? <email>{EmailAddress}</email> ? </contributor> * <category term="{text}" scheme="{Uri}" ? label="{text}" ? /> * <link href="{Uri}" rel="{NCName|Uri}" ? type="{MediaType}" ? hreflang="{LanguageTag}" ? title="{text}" ? length="{text}" ? /> * <entry> <id>{Uri}</id> <title type="{text|html|xhtml}" ? >{text|xhtmlDiv}</title> <updated>{xsd:dateTime}</updated> <published>{xsd:dateTime}</published> ? <rights type="{text|html|xhtml}" ? >{text|xhtmlDiv}</rights> ? <summary type="{text|html|xhtml}" ? >{text|xhtmlDiv}</summary> ? (<content type="{text|html|xhtml}" ? >{text|xhtmlDiv}</content> | <content type="{MediaType}" ?>{text|anyElement * }</content> | <content type="{MediaType}" ? src="{Uri}" />) ? <author> <name>{text}</name> <uri>{Uri}</uri> ? <email>{EmailAddress}</email> ? </author> * <contributor> <name>{text}</name> <uri>{Uri}</uri> ? <email>{EmailAddress}</email> ? </contributor> * <category term="{text}" scheme="{Uri}" ? label="{text}" ? /> * <link href="{Uri}" rel="{NCName|Uri}" ? type="{MediaType}" ? hreflang="{LanguageTag}" ? title="{text}" ? length="{text}" ? /> * <source> <id>{Uri}</id> ? <title type="{text|html|xhtml}" ? >{text|xhtmlDiv}</title> ? <updated>{xsd:dateTime}</updated> ? <icon>{Uri}</icon> ? <logo>{Uri}</logo> ? <rights type="{text|html|xhtml}" ? >{text|xhtmlDiv}</rights> ? <subtitle type="{text|html|xhtml}" ? >{text|xhtmlDiv}</subtitle> ? <generator uri="{Uri}" ? version="{text}" ? >{text}</generator> ? <author> <name>{text}</name> <uri>{Uri}</uri> ? <email>{EmailAddress}</email> ? </author> * <contributor> <name>{text}</name> <uri>{Uri}</uri> ? <email>{EmailAddress}</email> ? </contributor> * <category term="{text}" scheme="{Uri}" ? label="{text}" ? /> * <link href="{Uri}" rel="{NCName|Uri}" ? type="{MediaType}" ? hreflang="{LanguageTag}" ? title="{text}" ? length="{text}" ? /> * </source> ? </entry> * </feed>
? near an attribute/element means that it is optional, * means that it is repeated and | defines an alternative.
Of course, the sketch above is not complete and Atom format specification contains more information and rules your Atom feed should conform to. Also you can use Feedvalidator service to test whether your atom feed is correct or not.
Here is an example of Atom feed, a simple feed with one entry:
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Alex @ Net acticles</title>
<link href="http://www.alexatnet.com/"/>
<updated>2007-07-20T20:07:00Z</updated>
<author>
<name>Alexander Netkachev</name>
</author>
<id>http://alexatnet.com/blog/2</id>
<entry>
<title>Syndicate content with Zend Framework</title>
<link href="http://www.alexatnet.com/blog/2/post/95"/>
<id>http://www.alexatnet.com/blog/2/post/95</id>
<updated>2007-07-20T20:07:00Z</updated>
<summary>This article explains basics of content syndications and
demonstrates how to use Zend Framework Zend_Feed classes for consuming
a news feed of your site.</summary>
</entry>
</feed>Zend Framework classes for consuming feeds are in Zend_Feed namespace
Zend Framework classes that help the developer create a feed are in Zend_Feed namespace. Of course, "namespace" just means that their names begin with Zend_Feed and they are located in the "Zend/Feed" folder. The following diagram shows Zend_Feed base classes and Atom feed classes derived from them:

Zend_Feed also contains classes for creating RSS feeds and for building feeds from data. Here is the complete list of Zend_Feed classes, including classes from the diagram:
Zend/ Feed/ Builder/ Header/ Itunes Entry Exception Header Interface Abstract Atom Builder EntryAbstract EntryAtom EntryRss Exception Rss
Zend_Feed contains set of classes that helps you to build a feed and these classes are called "builders". Zend_Feed_Builder_Interface defines a set of methods Zend_Feed builder should provide. The main purpose of a Zend_Feed builder is to convert some general data source to list of entries and return them to Zend_Feed class. Now one realization of Zend_Feed_Builder_Interface, called "Zend_Feed_Builder", exists in Zend_Feed and this implementation converts an array with specific attributes to entries. Actually, Zend_Feed::importArray() calls this builder.
The structure of the Zend_Feed Atom feed is not as complete as the above general structure of the Atom feed and may be described with the following XML/PHP-like definition:
The following array demonstrates data structure that is used
for creating feeds
$array = array(
'link' => '...', // text/uri
'title' => '...', // text
'author' => array(
'name' => '...', // text
'email' => '...' // text
),
'lastUpdate' => ..., // timestamp
'published' => ..., // timestamp
'description' => '...', // text
'copyright' => '...', // text
'image' => '...', // text
'generator' => '...', // text
'entries' => array(
array(
'guid' => '...', // text/uri
'link' => '...', // text/uri
'title' => '...', // text
'lastUpdate' => ..., // timestamp
'description' => '...', // text
'content' => '...', // text/html
'category' => array(
array(
'term' => '...', // text
'scheme' => '...' // text/uri
),
...
)
),
...
),
'source' => array(
'title' => '...', // text
'url' => '...' // text/url
),
'enclosure' => array(
array(
'url' => '...', //text/url
'type' => '...', //text/mime
'length' => ... // int
),
...
),
'comments' => '...', // text
'commentRss' => '...' // text/url
);
The following XML-like document shows how the array is converted into XML document
<feed xmlns="http://www.w3.org/2005/Atom">
<id>{$array->link}</id>
<title>{$array->title}</title>
<author>
<name>{$array->author->name}</name>
<email>{$array->author->email}</email> ?
</author> ?
<updated>{($array->lastUpdate) ? $array->lastUpdate : time()}</updated>
<published>{$array->published}</published> ?
<link rel="self" href="{$data->link}" hreflang="{$data->language}" ? />
<subtitle>{$array->description}</subtitle> ?
<rights>{$array->copyright}</rights> ?
<logo>{$array->image}</logo> ?
<generator>{($array->generator) ? $array->generator : 'Zend_Feed'}</generator>
{foreach $array->entries as $entry}
<entry>
<id>{isset($entry->guid) ? $entry->guid : $entry->link}</id>
<title>{$entry->title}</title>
<updated>{($entry->lastUpdate) ? $entry->lastUpdate : time()}</updated>
<link rel="alternate" href="{$entry->link}" />
<summary><![CDATA[{$entry->description}]]></summary>
<content type="html"><![CDATA[{$entry->content}]]></content> ?
{foreach $entry->category as $category}
<category term="{$category['term']}" scheme="{$category['scheme']}" ?></category>
{endforeach}
<source>
<title>{$entry->source['title']}</title>
<link rel="alternate" href="{$entry->source['url']}">{$entry->source['title']}</link>
</source> ?
{foreach $entry->enclosure as $enclosure}
<link rel="enclosure" href="{$enclosure['url']}" type="{$enclosure['url']}" ?
length="{$enclosure['length']}" />
{endforeach}
<wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">{$entry->comments}</wfw:comment> ?
<wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">{$entry->commentRss}</wfw:commentRss> ?
</entry>
{endforeach}
</feed>
How to create Atom feed with Zend Framework
There are typical steps that the site developer should follow to create a feed and here they are.
Step zero: Plan your feed and write tests
The first what should be done is a plan. Here is it and this simplifies this task but your plan may contain a different options and detailed explanation of what kind of content the feed should contains, how it is displayed on site pages, URL of the feed, and so forth.
Some methodologies require to write testing suits before actual coding and this really clarifies some aspects of implementation. Consider the following recommendation about how to write test suits in case of Zend Framework: Automatic testing of MVC applications created with Zend Framework
Step one: Prepare data
An Atom feed can contains one entry or feed with entries and the second is the most typical situation. The feed with entries can depend on request parameters, e.g. blog posts from a particular category or bookmarks of a specific user.
Zend Framework methodology suggests to have a table class that is used for accessing data of the application and represents table in database. For a feed with entries you may need to select one data row that represents the feed and a rowset that represents the entities of the feed.
The following example demonstrates how to prepare data for a feed that contains recent blog posts:
database structure
`object` table (
`id` int primary key,
`parent` int foreign key to `objects`(`id`),
`title` varchar(255),
`modified` date,
... some other fields ...
)
data model class
class Objects extends Zend_Db_Table
{
protected $_name = 'object';
function getRecentPosts($blog)
{
$db = $this->getAdapter();
$select = $db->select()
->from(array('objects', array('title', 'modified')))
->where(array('parent=?', $blog))
->order('modified desc')
->limit(10);
return $db->fetchAll($select);
}
}
Get data for blog specified by $id
$objects = new Objects();
$blog = $objects->find($id)->current();
if (!$blog) {
return $this->_forward('error');
}
$posts = $objects->getRecentPosts($id);Step two: Create a controller action that generates the feed
When the data is ready it is time to generate the feed. Zend Framework manual contains excellent guidelines for that and the following example demonstrates how to create a feed using an array of items and default array builder:
class Blog_IndexController extends Zend_Controller_Action
{
public function atomAction()
{
$id = $this->_getParam('blog', 0);
// get data
$objects = new Objects();
$blog = $objects->find($id)->current();
if (!$blog) {
return $this->_forward('error');
}
$posts = $objects->getRecentPosts($id);
// prepare an array that our feed is based on
$feedArray = array(
'title' => $blog->title,
'link' => 'http://www.alexatnet.com/blog/' . $id . '/atom',
'lastUpdate' => (0 == $posts->count() ? date('c', strtotime()) : date('c')),
'charset' => 'utf-8',
'description' => $blog->description,
'author' => 'Alexander Netkachev',
'email' => 'alexander.netkachev@gmail.com',
'copyright' => 'Alexander Natkachev, all rights reserved',
'generator' => 'Zend Framework Zend_Feed',
'language' => 'en',
'entries' => array()
);
foreach ($posts as $post) {
$feedArray['entries'][] = array(
'title' => $post->title,
'link' => 'http://www.alexatnet.com/blog/' . $id . '/'
. date('Y/m/d', strtotime($post->updated)) . '/'
. $post->name,
'description' => $post->description,
'lastUpdate' => strtotime($post->updated),
'content' => $post->content
)
}
// create feed document
$feed = Zend_Feed::importArray($feedArray, 'atom');
// adjust created DOM document
foreach ($feed as $entry) {
$element = $entry->summary->getDOM();
// modify summary DOM node
}
// send feed XML to client
$feed->send();
}
}Step three: Validate the feed
And of course, any created code should be tested. Feedvalidator.org is a service that can verify your feed and report error in case it is not valid. Validation is simple, just paste your feed url into the input box (as on the picture) and click "Validate".

If everything is ok with the feed it just displays "This is a valid Atom 1.0 feed":

But if the feed contains errors the service displays detailed information for errors and some tips on how to fix them.