Zend Framework – How To Implement RSS Feeds the Easy Way

Zend Framework - How to Implement RSS Feeds the Easy Way

Ever wanted to know how to take an existing Zend Framework application where you have posts, records, news or anything else stored in a data source and updated regularly, and make it available as a feed for your users that they can subscribe to?

Well today, that’s what I want to show you. Recently, during the development of the burgeoning PHP cloud development casts site, I had just that situation.

A report came in indicating that the subscribe route was broken – sadly, in reality, it had not yet been implemented. Now there were a number of paths that could have been taken using some very capable libraries. Doing a quick Google search reveals the following options:

I’ve used Magpie RSS previously and can happily speak well of it, but I’ve not used SimplePie. But why would I use an external library, when I can use the library that comes with the zend framework, which the rest of the app’s built in along with some other components to complete the work?

So with a little help from two outstanding components of the Zend Framework, I’m going to show you how to implement subscriber feeds in your app, in both RSS 2.0 and Atom 1.0 formats, peppered with a little bit of magic from context switches and some simple, custom, routes.

Setup the Routes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
; Atom
routes.subscribe-atom.type = "Zend_Controller_Router_Route"
routes.subscribe-atom.route = "subscribe/atom"
routes.subscribe-atom.defaults.module = "screencasts"
routes.subscribe-atom.defaults.controller = "subscribe"
routes.subscribe-atom.defaults.action = "subscribe"
routes.subscribe-atom.defaults.format = "atom"
 
; RSS
routes.subscribe-rss.type = "Zend_Controller_Router_Route"
routes.subscribe-rss.route = "subscribe/rss"
routes.subscribe-rss.defaults.module = "screencasts"
routes.subscribe-rss.defaults.controller = "subscribe"
routes.subscribe-rss.defaults.action = "subscribe"
routes.subscribe-rss.defaults.format = "rss"

The routing is critical, but arguably one of the simplest steps in the whole process. As the Zend_Feed_Writer class, at the time of writing, supports only Atom 1.0 & RSS 2.0, then we’re going to setup routes just for these two formats.

You can see in the two routes above, that they’re largely similar. In the first, we’ve linked subscribe/atom to the subscribe action of the subscribe controller of the screencasts module. To properly interact with the ContextSwitch later, we’ve specified the format as “atom”.

For the RSS format, we copy & past the previous route and change atom to rss, where appropriate. Now, we could have probably condensed these two routes in to one route, but that’s a story for another day. With the routes done, let’s move on to retrieving the data and building the feeds.

Load the Feed with your Data Source Data

The code below looks at the core functions in the subscribe controller in the php cloud development casts codebase that we referenced a moment ago.

I like to keep code nice, simple and professional, as anti-monolithic as possible. So I’ve separated out code in to blocks that keep to the traditional UNIX programming principle of do one thing well and no more.

1
2
3
4
5
6
7
8
9
/**
 * An action to allow subscribing to feeds
 */
public function subscribeAction()
{
    $format = $this->_getParam('format', self::DEFAULT_FORMAT);
    $feed = $this->_getFeed($format);
    $this->view->feedOutput = $feed;
}

You can see that the subscribe action is very small. It sets a format from the format supplied, or sets one from the class default. After that, it runs the class function, _getFeed, to generate the feed and assigns the result to the view variable feedOutput.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * Build the core feed information
 */
protected function _getFeed($format) 
{
    $front = Zend_Controller_Front::getInstance();
 
    // get a handle on the screencasts datasource
    $this->_config = new Zend_Config($front->getParam('bootstrap')->getOption('app'));
 
    $feed = new Zend_Feed_Writer_Feed;
    $feed->setTitle($this->_config->feed->title);
    $feed->setLink($this->view->fullUrl($this->view->baseUrl()));
    $feed->setDescription($this->_config->feed->description);
 
    if ($format == 'atom') {
        $feed->setFeedLink($this->view->fullUrl($this->view->baseUrl()) . '/subscribe/atom', 'atom');
    } else {
        $feed->setFeedLink($this->view->fullUrl($this->view->baseUrl()) . '/subscribe/rss', 'rss');
    }
 
    $feed->addAuthor(array(
        'name'  => trim($this->_config->feed->author->name),
        'email' => trim($this->_config->feed->author->email),
        'uri'   => $this->view->fullUrl($this->view->baseUrl()),
    ));
    $feed->setDateModified(time());
    $feed->addHub($this->view->fullUrl($this->view->baseUrl()));
    $feed = $this->_addFeeds($feed);
 
    return $feed->export($format);
}

The, protected, _getFeed method retrieves the key configuration settings that we’ve placed in the application.ini configuration file.

This, along with a custom view helper, allows us to initialise the Zend_Feed_Writer_Feed object; setting the title, link, description, feedLink, hub and author details. I likely should not set the modified date everytime the page is generated, but for now, it’s ok and so it’s set with the timestamp retrieved from the time() function.

The feed configuration in the application.ini is as follows:

1
2
3
4
5
6
; Core Feed (RSS/ATOM) Settings
app.feed.vendor = Malt Blue Ltd
app.feed.title = PHP Cloud Development Casts Feed
app.feed.description = Learn Cloud Development Through The Lens of PHP
app.feed.author.name = Matthew Setter
app.feed.author.email = subscribe@maltblue.com

After that, we’re down to the, protected, _addFeeds function. It’s here where we retrieve the screencasts data and use it to create feed items for the feed object.

So firstly, we retrieve feeds that are published, and retrieve an iterator to use when iterating over the results in the foreach loop. We then iterate through whilst we have screencast items and built a feed entry, adding the title, link, author, date modified and created, description and content from the screencast object.

After it’s all done, we return the feed object and we’re just about ready.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * Add on currently published feed items to the feed
 */
protected function _addFeeds($feed) 
{
    // find all the published feeds
    $this->_castsDatasource->isPublished(true)
                           ->find();
    $results = $this->_castsDatasource->getIterator();
 
    if (count($results) > 0) {    
        foreach ($results as $result) {
            $entry = $feed->createEntry();
            $entry->setTitle($result->title);
            $entry->setLink($this->view->fullUrl('/screencast/' . urlencode($result->title)));
            $entry->addAuthor(array(
                'name'  => trim($result->author->fullName()),
            ));
            $publishDate = new DateTime($result->publishDate);
            $createdDate = new DateTime($result->createdDate);
            $entry->setDateModified($publishDate->getTimestamp());
            $entry->setDateCreated($createdDate->getTimestamp());
            $entry->setDescription($result->description);
            $entry->setContent($result->synopsis);
            $feed->addEntry($entry);
        }
    }
 
    return $feed;
}

Pepper Your Controller with a Context Switch

Here’s where we put the previous work together. If you’re not familiar with ContextSwitch in Zend Framework, then you’re in for a treat. I won’t go over it specifically in too much detail, as the manual and Michelangelo van Dam cover it in great detail already.

But what we’re doing is that we’re setting up two new context’s, as the only one’s supported, out of the box, are JSON and XML. For our purposes, we need to setup one for RSS and ATOM.

So what we do is call setContext twice, one for each context and indicate the extension of the output and the header that needs to be sent with that kind of output. We then indicate, via addActionContext that the subscribe action can switch between the two formats and call initContext and we’re right to go.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$contextSwitch = $this->_helper->getHelper('contextSwitch');
$contextSwitch->setContext('rss', array(
    'suffix'    => 'xml',
    'headers'   => array(
    'Content-Type: application/rss+xml; charset=ISO-8859-1'
    )
))
->setContext('atom', array(
    'suffix'    => 'xml',
    'headers'   => array(
    'Content-type: application/atom+xml'
    )
))
->addActionContext('subscribe', array('rss','atom'))
->initContext();

Render Your Feed

Now since we’ve said that the file extension is xml, we need to setup a template slightly differently. Normally, we’d create a view script called subscribe.phtml for the subscribe action.

As it’s a context switch, we have to name the file slightly differently. We have to name it subscribe.xml.phtml. If we were switching to, say, txt, we’d name it subscribe.txt.phtml instead. Can you see the connection between extension and file name?

<?php print $this->feedOutput; ?>

In the file, we then output the value of the feedOutput view variable we created earlier, which will output the content of the feed we’ve generated, which you can see below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>PHP Cloud Development Casts Feed</title>
    <description>Learn cloud development through the lens of PHP</description>
    <pubDate>Wed, 11 Jul 2012 11:00:26 -0700</pubDate>
    <generator>Zend_Feed_Writer 1.11.2 (http://framework.zend.com)</generator>
    <link>http://testing.phpclouddevelopmentcasts.com</link>
    <author>subscribe@maltblue.com (Matthew Setter)</author>
    <dc:creator>Matthew Setter</dc:creator>
    <atom:link rel="self" type="application/rss+xml" href="http://testing.phpclouddevelopmentcasts.com/subscribe/rss"/>
    <atom:link rel="hub" href="http://testing.phpclouddevelopmentcasts.com"/>
    <item>
      <title>Introduction to cloudControl</title>
      <description><![CDATA[In this, the first episode of PHP Cloud Development Casts, we go through how to get started deploying to the cloudControl (cloudControl.com) platform. 
 
We go through: taking an existing project and putting it under git control, initialising the cloudControl environment, pushing the code out, initialising the MySQL database and creating a testing branch based off of the master branch]]></description>
      <pubDate>Wed, 04 Jul 2012 00:00:00 -0700</pubDate>
      <link>http://testing.phpclouddevelopmentcasts.com/screencast/Introduction+to+cloudControl</link>
      <guid>http://testing.phpclouddevelopmentcasts.com/screencast/Introduction+to+cloudControl</guid>
      <author>Matthew Setter</author>
      <dc:creator>Matthew Setter</dc:creator>
      <content:encoded><![CDATA[In this, the first episode of PHP Cloud Development Casts, we go through how to get started deploying to the cloudControl (cloudControl.com) platform. 
 
We go through: taking an existing project and putting it under git control, initialising the cloudControl environment, pushing the code out, initialising the MySQL database and creating a testing branch based off of the master branch]]></content:encoded>
    </item>
  </channel>
</rss>

You’re All Done!

So there you have it. Through loading the data as we would in the rest of our application and then using that information with the Zend_Feed components, we’re able to transform our data in to a standards compliant RSS or Atom feed.

Then, by setting up a ContextSwitch for our subscribe action, we are able to dramatically minimise the code that that we need to write and instead, setup a few routes that simply supply a different format parameter to our query string.

We’ve been able to utilise further components of the framework, keeping our work clean, professional, tidy and maintainable. Want to add support for other feed formats? That’s a lesson left to you dear reader. But should you do so, feel free to email me and I’ll plug your work.

What Else Could We Do?

Now this isn’t all that we could have done. We could have combined the routes in to just one, we could have created a feed class as a minimalist wrapper over the Zend Feed code instead of directly including it in the controller.

What do you think? How would you handle this scenario?

Beginner Tutorial Zend Framework
  • Pingback: RSS Feeds mit dem Zend Framework 1 implementieren - Zend Framework Magazin

  • http://kennedyjacobs.wordpress Kennan Leonard

    <a href=”http://kennedyjacobs.wordpress”>Zend Development
    I like to keep Code amazing, simple and professional, as anti-monolithic as possible. So I’ve separated out code in to stops that keep to the traditional UNIX Development idea of do one aspect well</a> .

  • http://kennedyjacobs.wordpress Kennan Leonard

    http://kennedyjacobs.wordpress
    I like to keep Code amazing, simple and professional, as anti-monolithic as possible. So I’ve separated out code in to stops that keep to the traditional UNIX Development idea of do one aspect well .

  • http://kennedyjacobs.wordpress Kennan Leonard

    I like to keep Code amazing, simple and professional, as anti-monolithic as possible. So I’ve separated out code in to stops that keep to the traditional UNIX Development idea of do one aspect well .