RSS feeds are a form of web services, as they provide a service, over the web, using a known format to expose data. Due to their simplicity, they are a great way to introduce us to the world of web services, particularly as CakePHP offers a built in method to create them.
In the recipe Consuming RSS feeds with a datasource from Chapter 5, Datasources, we learned how to fetch content from a foreign RSS feed. In this recipe, will do exactly the opposite: produce a feed for our site that can be used by other applications.
To go through this recipe we need a sample table to work with. Create a table named posts
, using the following SQL statement:
CREATE TABLE `posts`(posts `id` INT NOT NULL AUTO_INCREMENT, `title` VARCHAR(255) NOT NULL, `body` TEXT NOT NULL, `created` DATETIME NOT NULL, `modified` DATETIME NOT NULL, PRIMARY KEY(`id`) );
Add some sample data, using the following SQL statements:
INSERT INTO `posts`(`title`,posts `body`, `created`, `modified`) VALUES ('Understanding Containable', 'Post body', NOW(), NOW()), ('Creating your first test case', 'Post body', NOW(), NOW()), ('Using bake to start an application', 'Post body', NOW(), NOW()), ('Creating your first helper', 'Post body', NOW(), NOW()), ('Adding indexes', 'Post body', NOW(), NOW());
We proceed now to create the required controller. Create the class PostsController
in a file named posts_controller.php
and place it in your app/controllers
folder, with the following contents:
<?php class PostsController extends AppController { public function index() { $posts = $this->Post->find('all'), $this->set(compact('posts')); } } ?>
Create a folder named posts
in your app/views
folder, and then create the index
view in a file named index.ctp
and place it in your app/views/posts
folder, with the following contents:
<h1>Posts</h1> <?php if (!empty($posts)) { ?> <ul> <?php foreach($posts as $post) { ?> <li><?php echo $this->Html->link( $post['Post']['title'], array( 'action'=>'view', $post['Post']['id'] ) ); ?></li> <?php } ?> </ul> <?php } ?>
app/config/routes.php
file and add the following statement at the end:Router::parseExtensions('rss'),
app/controllers/posts_controller.php
file and add the following property to the PostsController
class:public $components = array('RequestHandler'),
PostsController
, make the following changes to the index()
method:public function index() {
$options = array();
if ($this->RequestHandler->isRss()) {
$options = array_merge($options, array(
'order' => array('Post.created' => 'desc'),
'limit' => 5
));
}
$posts = $this->Post->find('all', $options);
$this->set(compact('posts'));
}
rss
in your app/views/posts
folder, and inside the rss
folder create a file named index.ctp
, with the following contents:<?php $this->set('channel', array( 'title' => 'Recent posts', 'link' => $this->Rss->url('/', true), 'description' => 'Latest posts in my site' )); $items = array(); foreach($posts as $post) { $items[] = array( 'title' => $post['Post']['title'], 'link' => array('action'=>'view', $post['Post']['id']), 'description' => array('cdata'=>true, 'value'=>$post['Post']['body']), 'pubDate' => $post['Post']['created'] ); } echo $this->Rss->items($items); ?>
app/views/posts/index.ctp
file and add the following at the end of the view:<?php echo $this->Html->link('Feed', array('action'=>'index', 'ext'=>'rss')); ?>
If you now browse to http://localhost/posts
, you should see a listing of posts with a link entitled Feed. Clicking on this link should produce a valid RSS feed, as shown in the following screenshot:
If you view the source of the generated response, you can see that the source for the first item within the RSS document is:
<item> <title>Understanding Containable</title> <link>http://rss.cookbook7.kramer/posts/view/1</link> <description><![CDATA[Post body]]></description> <pubDate>Fri, 20 Aug 2010 18:55:47 -0300</pubDate> <guid>http://rss.cookbook7.kramer/posts/view/1</guid> </item>
We started by telling CakePHP that our application accepts the rss
extension with a call to Router::parseExtensions()
, a method that accepts any number of extensions. Using extensions, we can create different versions of the same view. For example, if we wanted to accept both rss
and xml
as extensions, we would do:
Router::parseExtensions('rss', 'xml'),
In our recipe, we added rss
to the list of valid extensions. That way, if an action is accessed using that extension, for example, by using the URL http://localhost/posts.rss
, then CakePHP will identify rss
as a valid extension, and will execute the ArticlesController::index()
action as it normally would, but using the app/views/posts/rss/index.ctp
file to render the view. The process also uses the file app/views/layouts/rss/default.ctp
as its layout, or CakePHP's default RSS layout if that file is not present.
We then modify how ArticlesController::index()
builds the list of posts, and use the RequestHandler
component to see if the current request uses the rss
extension. If so, we use that knowledge to change the number and order of posts.
In the app/views/posts/rss/index.ctp
view, we start by setting some view variables. Because a controller view is always rendered before the layout, we can add or change view variables from the view file, and have them available in the layout. CakePHP's default RSS layout uses a $channel
view variable to describe the RSS feed. Using that variable, we set our feed's title, link
, and description
.
We proceed to output the actual item files. There are different ways to do so, the first one is making a call to the RssHelper::item()
method for each item, and the other one requires only a call to RssHelper::items()
, passing it an array of items. We chose the latter method due to its simplicity.
While we build the array of items to be included in the feed, we only specify title, link, description
, and pubDate
. Looking at the generated XML source for the item, we can infer that the RssHelper
used our value for the link
element as the value for the guid
(globally unique identifier) element.
Note that the description
field is specified slightly differently than the values for the other fields in our item array. This is because our description may contain HTML code, so we want to make sure that the generated document is still a valid XML document.
By using the array notation for the description
field, a notation that uses the value
index to specify the actual value on the field, and by setting cdata
to true
, we are telling the RssHelper
(actually the XmlHelper
from which RssHelper
descends) that the field should be wrapped in a section that should not be parsed as part of the XML document, denoted between a <![CDATA[
prefix and a]]>
postfix.
The final task in this recipe is adding a link to our feed that is shown in the index.ctp
view file. While creating this link, we set the special ext
URL setting to rss
. This sets the extension for the generated link, which ends up being http://localhost/posts.rss
.
Our feeds may be consumed by feed search crawlers. If we are lucky, we may get tons and tons of requests looking for updates to our blog. It is unlikely that we will update our blog so often that we would have new posts every second, so our server load may force us to add some caching.
When looking to improve performance, some developers are content to only cache their database queries. In our recipe, this would mean caching the results obtained from our $this->Post->find('all')
call. Unless we have our database engine on a separate server that suffers from some considerable network latency, chances are this sort of caching will offer little or no benefit.
A much better solution is to use view caching. That is, caching the generated RSS feed, and using that cached document whenever a request is made to our feed, provided we are within the cache time. Fortunately, CakePHP offers us a view-caching implementation right from the dispatcher, speeding up the request considerably. If a cached view file is found, that file is rendered directly to the client, without any intervention by the controller, or the need to load models, components, or helpers.
We want to add caching only when our PostsController::index()
action is accessed with the rss
extension. That is, we don't want to cache the listing of posts, but its feed. So we will make sure to only specify caching information when a feed is requested. In fact, we are going to cache all actions in our PostsController
whenever the rss
extension is used.
The first thing we need to do is tell CakePHP to take view caching into account. Edit your app/config/core.php
file and uncomment the following line:
Configure::write('Cache.check', true);
Next, edit your app/controllers/posts_controller.php
file and add the Cache
helper to the PostsController
class. Without it, view caching will simply not work:
public $helpers = array('Cache'),
While still editing the PostsController
class, add the following method:
public function beforeFilter() { parent::beforeFilter(); if ($this->RequestHandler->isRss()) { $this->cacheAction = array($this->action => '1 hour'), } }
In this beforeFilter()
implementation, we are checking to see if the current request was made using the rss
extension. If so, we add the current action (whatever that may be) to the list of cached actions, and set the cache time to be 1 hour
.
If we access the feed multiple times within the hour, we should see the same feed we have been getting so far, but coming from the cache instead of being built in real time.
18.223.237.29