Creating an RSS feed

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.

Getting ready

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 } ?>

How to do it...

  1. Edit your app/config/routes.php file and add the following statement at the end:
    Router::parseExtensions('rss'),
    
  2. Edit your app/controllers/posts_controller.php file and add the following property to the PostsController class:
    public $components = array('RequestHandler'),
    
  3. While still editing 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'));
    }
    
  4. Create a folder named 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);
    ?>
    
  5. Edit your 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:

    How to do it...

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>

How it works...

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.

Adding view caching to an RSS feed

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.

See also

  • Consuming RSS feeds with a datasource in Chapter 5, Datasources.
  • Building REST services with JSON
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.237.29