Skip to content
Caution: You are browsing the legacy symfony 1.x part of this website.

How to build a syndication feed

Symfony version
Language

Overview

Whether your application lists posts, images, news, questions or anything else, if the update rate is higher than once a month, you must provide a syndication feed (RSS, Atom, etc.) to your users so that they can keep up-to-date about your website from within a feed aggregator. The good news is, if your object model is built in the right way, this won't take you more than a couple lines to develop, since symfony provides a feed builder plugin that does it all for you.

Introduction

The example explored in this chapter is an simple blog application with a Post and an Author table:

Post Author
id id
author_id first_name
title last_name
description email
body
created_at

The Post class is extended by a getStrippedTitle() method that transforms the title into a string that can be used in an URI, replacing spaces by dashes, upper case by lower case, and removing all special characters:

public function getStrippedTitle()
{
  $text = strtolower($this->getTitle());
 
  // strip all non word chars
  $text = preg_replace('/\W/', ' ', $text);
  // replace all white space sections with a dash
  $text = preg_replace('/\ +/', '-', $text);
  // trim dashes
  $text = preg_replace('/\-$/', '', $text);
  $text = preg_replace('/^\-/', '', $text);
 
  return $text;
}

The Author class is extended by a custom ->getName() method as follows:

public function getName()
{
  return $this->getFirstName().' '.$this->getLastName()
}

If you need more details about the way to extend the model, refer to Chapter 8.

The routing.yml contains the following rule:

post:
    url:   /permalink/:stripped_title
    param: { module: post, action: read }

If you need more details about the routing system, refer to Chapter 9.

A special feed module is built for the occasion, and all the actions and templates will be placed in it.

$ symfony init-module myapp feed

Expected result

The feed action has to output an Atom feed. As a reminder of all the information that need to be included in an Atom feed, here is an example:

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
  <title>The mouse blog</title>
  <link href="http://www.myblog.com/" />
  <updated>2005-12-11T16:23:51Z</updated>
  <author>
    <name>Peter Clive</name>
    <author_email>pclive@myblog.com</author_email>
  </author>
  <id>4543D55FF756G734</id>
 
  <entry>
    <title>I love mice</title>
    <link href="http://www.myblog.com/permalink/i-love-mice" />
    <id>i-love-mice</id>
    <author>
      <name>Peter Clive</name>
      <author_email>pclive@myblog.com</author_email>
    </author>
    <updated>2005-12-11T16:23:51Z</updated>
    <summary>Ever since I bought my first mouse, I can't live without one.</summary>
  </entry>
 
  <entry>
    <title>A mouse is better than a fish</title>
    <link href="http://www.myblog.com/permalink/a-mouse-is-better-than-a-fish" />
    <id>a-mouse-is-better-than-a-fish</id>
    <author>
      <name>Bob Walter</name>
      <author_email>bwalter@myblog.com</author_email>
    </author>
    <updated>2005-12-09T09:11:42Z</updated>
    <summary>I had a fish for four years, and now I'm sick. They smell.</summary>
  </entry>
 
</feed>

Install the plug-in

Symfony provides a sfFeed plug-in that automates most of the feed generation. To install it, use the symfony command line:

$ symfony plugin-install http://plugins.symfony-project.com/sfFeedPlugin

This installs the classes of the plug-in in the plugins/ directory. TO let symfony autoload them, you need to clear the configuration cache:

$ symfony cc

If you want to learn more about plug-ins, how they extend the framework and how you can package the features that you use across several projects into a plug-in, refer to Chapter 17 of the symfony book.

Build the feed by hand

In the feed module, create a lastPosts action:

public function executeLastPosts()
{
  $feed = sfFeed::newInstance('atom1');
 
  $feed->setTitle('The mouse blog');
  $feed->setLink('http://www.myblog.com/');
  $feed->setAuthorEmail('pclive@myblog.com');
  $feed->setAuthorName('Peter Clive');
 
  $c = new Criteria;
  $c->addDescendingOrderByColumn(PostPeer::CREATED_AT);
  $c->setLimit(5);
  $posts = PostPeer::doSelect($c);
 
  foreach ($posts as $post)
  {
    $item = new sfFeedItem();
    $item->setFeedTitle($post->getTitle());
    $item->setFeedLink('@permalink?stripped_title='.$post->getStrippedTitle());
    $item->setFeedAuthorName($post->getAuthor()->getName());
    $item->setFeedAuthorEmail($post->getAuthor()->getEmail());
    $item->setFeedPubdate($post->getCreatedAt('U'));
    $item->setFeedUniqueId($post->getStrippedTitle());
    $item->setFeedDescription($post->getDescription());
 
    $feed->addItem($item);
  }
 
  $this->feed = $feed;
}

The initial factory method creates an instance of the sfFeed class for the 'Atom' format. The sfFeed and sfFeedItem classes are symfony add-ons made especially for feed construction. At the end of the action, the $feed variable contains a sfFeed object which includes several sfFeedItem objects. To transform the object into an actual Atom feed, the lastPostsSuccess.php template simply contains:

<?php echo $feed->getFeed() ?>

The template must not be decorated by a layout. In addition, the resulting page has to be declared as a text/xml content-type. So add a view.yml in the feed module config/ directory:

all:
  has_layout: off

  http_metas:
    content-type: text/xml

When called from a feed aggregator, the result of the action is now exactly the Atom feed described above:

http://www.myblog.com/feed/lastPosts

Use the short syntax

The use of all the setters for the item construction can be a little annoying, since there is a lot of information to define. Symfony provides a short hand syntax that produces the same effect, using an associative array:

public function executeLastPosts()
{
 
  $feed = sfFeed::newInstance('atom1');
 
  $feed->setTitle('The mouse blog');
  $feed->setLink('http://www.myblog.com/');
  $feed->setAuthorEmail('pclive@myblog.com');
  $feed->setAuthorName('Peter Clive');
 
  $c = new Criteria;
  $c->addDescendingOrderByColumn(PostPeer::CREATED_AT);
  $c->setLimit(5);
  $posts = PostPeer::doSelect($c);
 
  foreach ($posts as $post)
  {
    $item = array(
      'title'       => $post->getTitle(),
      'link'        => '@permalink?stripped_title='.$post->getStrippedTitle(),
      'authorName'  => $post->getAuthor()->getName(),
      'authorEmail' => $post->getAuthor()->getEmail(),
      'pubdate'     => $post->getCreatedAt(),
      'uniqueId'    => $post->getStrippedTitle(),
      'description' => $post->getDescription(),
    );
 
    $feed->addItemFromArray($item);
  }
 
  $this->feed = $feed;
}

It has exactly the same effect, but the syntax is clearer.

Let symfony do it for you

As the method names that are used to build a news field are more or less always the same, symfony can do exactly as above with only:

public function executeLastPosts()
{
 
  $feed = sfFeed::newInstance('atom1');
 
  $feed->setTitle('The mouse blog');
  $feed->setLink('http://www.myblog.com/');
  $feed->setAuthorEmail('pclive@myblog.com');
  $feed->setAuthorName('Peter Clive');
 
  $c = new Criteria;
  $c->addDescendingOrderByColumn(PostPeer::CREATED_AT);
  $c->setLimit(5);
  $posts = PostPeer::doSelect($c);
 
  $feed->setFeedItemsRouteName('@permalink');
  $feed->setItems($posts);
 
  $this->feed = $feed;
}

Isn't it pure magic?

The magic of the sfFeed object

The getter functions of the Post object are so clear that even symfony can understand them. That's because the sfFeed class has built-in mechanisms to deduce relevant information from well-organised classes:

  • To set the item title, it looks for a getFeedTitle(), a getTitle(), a getName() or a __toString() method.

    In the example, the Post object has a getName() method.

  • To set the link, it looks for an feed item route name (defined by a setFeedItemsRouteName() call). If there is one, it looks in the route url for parameters for which it could find a getter in the object methods. If not, it looks for a getFeedLink(), getLink(), getUrl() method.

    In the example, one route name is defined above in the action (@permalink). The routing rule contains a :stripped_title parameter and the Post object has a getStripped_Title() method, so the sfFeed object is able to define the URIs to link to.

  • To set the author's email, it looks for a getFeedAuthorEmail or a getAuthorEmail. If there is no such method, it looks for a getAuthor(), getUser() or getPerson() method. If the result returned is an object, it looks in this object for a getEmail or a getMail method.

    In the example, the Post object has a getAuthor(), and the Author object has a getName(). The same kind of rules is used for the author's name and URL.

  • To set the publication date, it looks for a getFeedPubdate(), getPubdate(), getCreatedAt() or a getDate() method.

    In the example, the Post object has a getCreatedAt

The same goes for the other possible fields of an Atom feed (including the categories, the summary, the unique id, etc.), and you are advised to browse the source of the sfFeed class to discover all the deduction mechanisms.

All in all, the way the accessors of the Post and Author objects are built allow the built-in shortcuts of the sfFeed to work, and the creation of the feed to be so simple.

Define custom values for the feed

In the list presented above, you can see that the first method name that the sfFeed object looks for is always a getFeedXXX(). This allows you to specify a custom value for each of the fields of a feed item by simply extended the model.

For instance, if you don't want the author's email to be published in the feed, just add the following getFeedAuthorEmail() method to the Post object:

public function getFeedAuthorEmail()
{
  return '';
}

This method will be found before the getAuthor() method, and the feed will not disclose the publishers' email addresses.

Use other formats

The methods described below can be transposed to build other RSS feeds. Simply change the parameter given to the feed factory:

// RSS 0.91
$feed = sfFeed::newInstance('rssUserland091');
// RSS 2.01
$feed = sfFeed::newInstance('rss201rev2');

This work is licensed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License license.