PhpRiot
Become Zend Certified

Prepare for the ZCE exam using our quizzes (web or iPad/iPhone). More info...


When you're ready get 7.5% off your exam voucher using voucher CJQNOV23 at the Zend Store

Extending Feed and Entry APIs

Extending Zend_Feed_Reader allows you to add methods at both the feed and entry level which cover the retrieval of information not already supported by Zend_Feed_Reader. Given the number of RSS and Atom extensions that exist, this is a good thing since Zend_Feed_Reader couldn't possibly add everything.

There are two types of Extensions possible, those which retrieve information from elements which are immediate children of the root element (e.g. <channel> for RSS or <feed> for Atom) and those who retrieve information from child elements of an entry (e.g. <item> for RSS or <entry> for Atom). On the filesystem these are grouped as classes within a namespace based on the extension standard's name. For example, internally we have Zend_Feed_Reader_Extension_DublinCore_Feed and Zend_Feed_Reader_Extension_DublinCore_Entry classes which are two Extensions implementing Dublin Core 1.0 and 1.1 support.

Extensions are loaded into Zend_Feed_Reader using Zend_Loader_PluginLoader, so their operation will be familiar from other Zend Framework components. Zend_Feed_Reader already bundles a number of these Extensions, however those which are not used internally and registered by default (so called Core Extensions) must be registered to Zend_Feed_Reader before they are used. The bundled Extensions include:

Table 70. Core Extensions (pre-registered)

DublinCore (Feed and Entry) Implements support for Dublin Core Metadata Element Set 1.0 and 1.1
Content (Entry only) Implements support for Content 1.0
Atom (Feed and Entry) Implements support for Atom 0.3 and Atom 1.0
Slash Implements support for the Slash RSS 1.0 module
WellFormedWeb Implements support for the Well Formed Web CommentAPI 1.0
Thread Implements support for Atom Threading Extensions as described in RFC 4685
Podcast Implements support for the Podcast 1.0 DTD from Apple

The Core Extensions are somewhat special since they are extremely common and multi-faceted. For example, we have a Core Extension for Atom. Atom is implemented as an Extension (not just a base class) because it doubles as a valid RSS module - you can insert Atom elements into RSS feeds. I've even seen RDF feeds which use a lot of Atom in place of more common Extensions like Dublin Core.

Table 71. Non-Core Extensions (must register manually)

Syndication Implements Syndication 1.0 support for RSS feeds
CreativeCommons A RSS module that adds an element at the <channel> or <item> level that specifies which Creative Commons license applies.

The additional non-Core Extensions are offered but not registered to Zend_Feed_Reader by default. If you want to use them, you'll need to tell Zend_Feed_Reader to load them in advance of importing a feed. Additional non-Core Extensions will be included in future iterations of the component.

Registering an Extension with Zend_Feed_Reader, so it is loaded and its API is available to Feed and Entry objects, is a simple affair using the Zend_Loader_PluginLoader. Here we register the optional Slash Extension, and discover that it can be directly called from the Entry level API without any effort. Note that Extension names are case sensitive and use camel casing for multiple terms.

<?php
Zend_Feed_Reader
::registerExtension('Syndication');
$feed Zend_Feed_Reader::import('http://rss.slashdot.org/Slashdot/slashdot');
$updatePeriod $feed->current()->getUpdatePeriod();

In the simple example above, we checked how frequently a feed is being updated using the getUpdatePeriod() method. Since it's not part of Zend_Feed_Reader's core API, it could only be a method supported by the newly registered Syndication Extension.

As you can also notice, the new methods from Extensions are accessible from the main API using PHP's magic methods. As an alternative, you can also directly access any Extension object for a similar result as seen below.

<?php
Zend_Feed_Reader
::registerExtension('Syndication');
$feed Zend_Feed_Reader::import('http://rss.slashdot.org/Slashdot/slashdot');
$syndication $feed->getExtension('Syndication');
$updatePeriod $syndication->getUpdatePeriod();

Writing Zend_Feed_Reader Extensions

Inevitably, there will be times when the Zend_Feed_Reader API is just not capable of getting something you need from a feed or entry. You can use the underlying source objects, like DOMDocument, to get these by hand however there is a more reusable method available by writing Extensions supporting these new queries.

As an example, let's take the case of a purely fictitious corporation named Jungle Books. Jungle Books have been publishing a lot of reviews on books they sell (from external sources and customers), which are distributed as an RSS 2.0 feed. Their marketing department realises that web applications using this feed cannot currently figure out exactly what book is being reviewed. To make life easier for everyone, they determine that the geek department needs to extend RSS 2.0 to include a new element per entry supplying the ISBN-10 or ISBN-13 number of the publication the entry concerns. They define the new <isbn> element quite simply with a standard name and namespace URI:

<?php
JungleBooks 1.0
:
http://example.com/junglebooks/rss/module/1.0/

A snippet of RSS containing this extension in practice could be something similar to:

<?php
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0"
   xmlns:content="http://purl.org/rss/1.0/modules/content/"
   xmlns:jungle="http://example.com/junglebooks/rss/module/1.0/">
<channel>
    <title>Jungle Books Customer Reviews</title>
    <link>http://example.com/junglebooks</link>
    <description>Many book reviews!</description>
    <pubDate>Fri, 26 Jun 2009 19:15:10 GMT</pubDate>
    <jungle:dayPopular>
        http://example.com/junglebooks/book/938
    </jungle:dayPopular>
    <item>
        <title>Review Of Flatland: A Romance of Many Dimensions</title>
        <link>http://example.com/junglebooks/review/987</link>
        <author>Confused Physics Student</author>
        <content:encoded>
        A romantic square?!
        </content:encoded>
        <pubDate>Thu, 25 Jun 2009 20:03:28 -0700</pubDate>
        <jungle:isbn>048627263X</jungle:isbn>
    </item>
</channel>
</rss>

Implementing this new ISBN element as a simple entry level extension would require the following class (using your own class namespace outside of Zend).

<?php
class My_FeedReader_Extension_JungleBooks_Entry
    
extends Zend_Feed_Reader_Extension_EntryAbstract
{
    public function 
getIsbn()
    {
        if (isset(
$this->_data['isbn'])) {
            return 
$this->_data['isbn'];
        }
        
$isbn $this->_xpath->evaluate(
            
'string(' $this->getXpathPrefix() . '/jungle:isbn)'
        
);
        if (!
$isbn) {
            
$isbn null;
        }
        
$this->_data['isbn'] = $isbn;
        return 
$this->_data['isbn'];
    }

    protected function 
_registerNamespaces()
    {
        
$this->_xpath->registerNamespace(
            
'jungle''http://example.com/junglebooks/rss/module/1.0/'
        
);
    }
}

This extension is easy enough to follow. It creates a new method getIsbn() which runs an XPath query on the current entry to extract the ISBN number enclosed by the <jungle:isbn> element. It can optionally store this to the internal non-persistent cache (no need to keep querying the DOM if it's called again on the same entry). The value is returned to the caller. At the end we have a protected method (it's abstract so it must exist) which registers the Jungle Books namespace for their custom RSS module. While we call this an RSS module, there's nothing to prevent the same element being used in Atom feeds - and all Extensions which use the prefix provided by getXpathPrefix() are actually neutral and work on RSS or Atom feeds with no extra code.

Since this Extension is stored outside of Zend Framework, you'll need to register the path prefix for your Extensions so Zend_Loader_PluginLoader can find them. After that, it's merely a matter of registering the Extension, if it's not already loaded, and using it in practice.

<?php
if(!Zend_Feed_Reader::isRegistered('JungleBooks')) {
    
Zend_Feed_Reader::addPrefixPath(
        
'My_FeedReader_Extension''/path/to/My/FeedReader/Extension'
    
);
    
Zend_Feed_Reader::registerExtension('JungleBooks');
}
$feed Zend_Feed_Reader::import('http://example.com/junglebooks/rss');

// ISBN for whatever book the first entry in the feed was concerned with
$firstIsbn $feed->current()->getIsbn();

Writing a feed level Extension is not much different. The example feed from earlier included an unmentioned <jungle:dayPopular> element which Jungle Books have added to their standard to include a link to the day's most popular book (in terms of visitor traffic). Here's an Extension which adds a getDaysPopularBookLink() method to the feel level API.

<?php
class My_FeedReader_Extension_JungleBooks_Feed
    
extends Zend_Feed_Reader_Extension_FeedAbstract
{
    public function 
getDaysPopularBookLink()
    {
        if (isset(
$this->_data['dayPopular'])) {
            return 
$this->_data['dayPopular'];
        }
        
$dayPopular $this->_xpath->evaluate(
            
'string(' $this->getXpathPrefix() . '/jungle:dayPopular)'
        
);
        if (!
$dayPopular) {
            
$dayPopular null;
        }
        
$this->_data['dayPopular'] = $dayPopular;
        return 
$this->_data['dayPopular'];
    }

    protected function 
_registerNamespaces()
    {
        
$this->_xpath->registerNamespace(
            
'jungle''http://example.com/junglebooks/rss/module/1.0/'
        
);
    }
}

Let's repeat the last example using a custom Extension to show the method being used.

<?php
if(!Zend_Feed_Reader::isRegistered('JungleBooks')) {
    
Zend_Feed_Reader::addPrefixPath(
        
'My_FeedReader_Extension''/path/to/My/FeedReader/Extension'
    
);
    
Zend_Feed_Reader::registerExtension('JungleBooks');
}
$feed Zend_Feed_Reader::import('http://example.com/junglebooks/rss');

// URI to the information page of the day's most popular book with visitors
$daysPopularBookLink $feed->getDaysPopularBookLink();

// ISBN for whatever book the first entry in the feed was concerned with
$firstIsbn $feed->current()->getIsbn();

Going through these examples, you'll note that we don't register feed and entry Extensions separately. Extensions within the same standard may or may not include both a feed and entry class, so Zend_Feed_Reader only requires you to register the overall parent name, e.g. JungleBooks, DublinCore, Slash. Internally, it can check at what level Extensions exist and load them up if found. In our case, we have a full set of Extensions now: JungleBooks_Feed and JungleBooks_Entry.

Zend Framework