Creating Custom Feeds with Zend_Feed - part 1

I had some time this weekend and I implemented the 'latest stuff'(news) feature. Now I can create feeds based on my posts or any other source I want. I can even combine external feeds,atom or rss ,to create a master feed and all this thanks to Zend_Feed

Here is an example on how you can combine three feeds , one rss , one atom and a local source to create a master feed:


$feed          = new Wu_Feed();
$remote_source = new Wu_Feed_Source_Remote('http://remotefeed.com/atom');
$feed->add($remote_source);
$remote_source = new Wu_Feed_Source_Remote('http://remotefeed.com/rss');
$feed->add($remote_source);

//of course you can have a builder
//object or a factory to cotain
//the local source building part
$local_source = new Wu_Feed_Source(); 
$entry = new Wu_Feed_Source_Entry();  
$entry->title = 'New Title';
$entry->content 'awesome content';
$local_source->add($entry);

$feed->add($local_source);
echo $feed->asRss();//outputs rss feed
//or
$feed->asAtom();//outputs atom.
//
$zend_feed = $feed->asZendFeed();//returns Zend_Feed object

I think asRss() and as asAtom() are clear, but what about the latest, asZendFeed()? Why would I need a Zend_Feed object, doesn't Wu_Feed inherit Zend_Feed ? No , it doesn't, but I am using it within Wu_Feed to do my work. I will explain more in the 'how it works' section.

If you noticed , my left navigation has a section 'Latest stuff'. Until now , it displayed my recent bookmarks , stored on a blogspot account. The code that did that was:


  $feed     = Zend_Feed::import('http://dordea.blogspot.com/feeds/posts/default');
        $results = array();
        foreach ($feed as $entry) {
            $result          = array();
            $result['title'] = $entry->title();
            $result['link']  = $entry->link('alternate');
            $results[]       = $result;
            
        }

        $this->view->results = $results;
        

Basically , to fill my left nav , I was using an external feed , because I was to lazy to make my own. The reason why asZendFeed() method exists is:

  • to have my original code working with no major modifications
  • to be sure my addons will work with any other classes that use Zend_Feed , from Zend Framework or from any other source
If you are thinking about performance , Zend_Cache does a wonderful job and allows me to ignore this part(at the moment) .
The code that fills my left nav is now:

$feed     = Wu_Feed_Factory::getLocal()->asZendFeed();
        $results = array();
        foreach ($feed as $entry) {
            $result          = array();
            $result['title'] = $entry->title();
            $result['link']  = $entry->link('alternate');
            $results[]       = $result;
            
        }

        $this->view->results = $results;

At some point , I want to alternate the 'Latest stuff' source(so that i could combine and filter the news using external or internal feeds).It makes sense (at least for me) to treat my news as a feed. If this 'plan' will fail in the future , at least now I have rss and atom feeds. Another important reason for going trough this 'effort', is that I will also use this code as a base for my new project.

How it works

Zend_Feed, by default , allows you to create your own feed, not just to import one. You can do that by using a big array (and by big i mean really big) or implementing the Zend_Builder_Interface(see the above link for details). Basically, you would do one of:



// importing a feed from a custom builder source
$atomFeedFromArray = Zend_Feed::importBuilder(new Zend_Feed_Builder($array));

// the following line is equivalent to the above; 
//by default a Zend_Feed_Atom instance is returned
$atomFeedFromArray = Zend_Feed::importArray(new Zend_Feed_Builder($array), 'atom');

// importing a rss feed from a custom builder array
$rssFeedFromArray = Zend_Feed::importArray(new Zend_Feed_Builder($array), 'rss');

// Note: This sample of code is extracted from 
// the Zend Official documentation
// http://framework.zend.com/manual/en/zend.feed.importing.html

The first line of code has the same effect as the second and it's even the same array, but do not be confused . Zend_Feed_Builder is just a sample implementation. The ideea is that you implement Zend_Feed_Builder_Interface and trough that interface , the feed will query you for a Zend_Feed_Header(via getHeader()) and a set of Zend_Feed_Entry(via getEntries()) objects.

Sounds nice , but for my first version , I preferred a set of objects that build the array mentioned before Zend_Feed_Builder kicked in. The reason? I find it more comfortable and I will not be tied to a data source. I can implement(and I will) a builder later, for now here is how I am using the classes I created. Generic Example:


title      = 'Example for my post';
$entry              = new Wu_Feed_Source_Entry();
$entry->title       = 'Hello world from a feed';
$entry->description =  'hello ..';
$entry->content     = 'Elaborate words about hello
 world';
$source->add($entry);
$feed = new Wu_Feed();
$feed->add($source);
echo $feed->asAtom();//outputs atom feed xml

To reduce the code I am writing, I am using some factory objects to create my feed source. One for the Wu_Feed objects, and one to create the source. Here is how the uml diagram looks:

feeds uml diagram

Wherever I need my feed, I am using Wu_Feed_Factory . This will protect me from any changes that I will make regarding the feed building process. Also , important to note , is that I am not inheriting Zend_Feed. Why? To protect me from myself and from any future changes that (perhaps) will affect Zend_Feed. I am using my classes to build the array needed by Zend_Feed to create the actual feed. Zend_Feed is used only when I actually need the output(or a Zend_Feed). There are other benefits to this approach , the most important for me: time. Again, I obtained a quick prototype of what I want to achieve in short time , but it's not a dirty solution. I can change anything regarding the building process , and my code that uses Wu_Feed will stay the same , as long as I obtain the feed object trough the same factory and my main methods add,asAtom,asRss and asZendFeed have the same effect.

Now , again , I am sure there are way better implementations and uses of Zend_Feed. But as something that I am doing in my free time , this solution works for me and took me around 1h to implement it. My point , again, is that you can quickly prototype something with a framework like ZF behind, and still , protect your present and future code from anything you don't master yet once you decide on how you want to use your new feature , which is the most important(and difficult) part most times(aka design).

What's next

After I will provide a standard way for you to build feed sources wihthout having to modify and understand the existing code (non existent php5 late static binding support delayed me) , I'll provide a link to the code presented here. Before that , I will use ZF to implement 'nice urls'. So instead of /blog/index/view/entry/123 to have /thinking_about_auth.html.

P.S.: You are free to call me a retard if this post was a waste of your time and had problems reading my non-sense.

K.