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();
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(
'/path/to/My/FeedReader/Extension', '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(
'/path/to/My/FeedReader/Extension', '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.




