Creating an RSS Feed for a Symfony Blog

Introduction

One of the features I've wanted to add to this blog for a while now, but kept putting off, until now, is an RSS feed. I recently created an account on connect.symfony.com and there was a spot to add an RSS feed to one's profile, which was the only thing stopping me from getting my profile to 100% and earning the "Profile completed" badge. Nothing like a little gamification to make me stop putting off this task.

Since I'm never one to re-invent the wheel, without justification, I of course looked to see if there were any Symfony bundles that would allow me to easily add an RSS feed to my blog. "Easily" being the keyword in that sentence. I did find several of bundles, but all of them seemed to have convoluted configuration, with a learning curve much steeper than rolling my own.

Oh, yea, you can see it in action here: https://eidson.info/rss

Rolling My Own

So, with a mission in hand I headed over to my google machine. The first handful of hits showed an implementation using PHP's XMLWriter, which is probably the 'right' way to do it, and would have been the better choice, if the XMl I needed to generate was any more complex than it was, but it seemed like overkill for such a simple string. Luckily, I stumbled across a post on talkerscode which gave me a good starting point.

Generating the XML

The first class that I created was a class to create my xml string that I wanted to return as my response (shown below). It is basically a simple static function that takes an array of posts and creates a properly formatted XMl string creating an item element for each post. When I tried to run this, the first couple of posts were handled properly, and then it choked and died. It turns out that the title of one of my post's contained an ampersand ('&'), which is not handled correctly by an xml parser, So I created a little helper function to escape any characters that were going to through the XML parsers for a loop.

<?php

namespace AppBundle\Rss;

class Xml
{
    public static function generate($posts)
    {
        $xml = <<<xml
<?xml version='1.0' encoding='UTF-8'?>
<rss version='2.0'>
<channel>
<title>Todd Eidson's Programming Blog</title>
<link>https://eidson.info</link>
<description>Programming Blog</description>
<language>en-us</language>
xml;
        foreach ($posts as $post) {

            $title = self::xmlEscape($post->getTitle());
            $url = self::xmlEscape($post->getUrl());
            $slug = self::xmlEscape($post->getHtml());
            $pubDate = $post->getPublished()->format('D, d M Y H:i:s T');
            $xml .= <<<xml
<item>
<title>{$title}</title>
<link>https://eidson.info/post/{$url}</link>
<description>{$slug}</description>
<pubDate>$pubDate</pubDate>
</item>
xml;
        }
        $xml .= "</channel></rss>";

        return $xml;
    }

    private static function xmlEscape($string) {
        return str_replace(array('&', '<', '>', '\'', '"'), array('&amp;', '&lt;', '&gt;', '&apos;', '&quot;'), $string);
    }
}

Creating a Controller

So now that I had the XML generation working smoothly, I needed a way to handle an incoming request and return the appropriate response, which consists of the Content-Type: text/xml; header and the XML string. So I was able to create a Symfony controller that fetched the posts, created a response, set the headers, added the XML content, and returned the response.

<?php
namespace AppBundle\Controller;

use AppBundle\Rss\Xml;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class RssController extends Controller
{
    /**
     * @Route("/rss", name="rss-feed")
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function rssAction()
    {
        $posts = $this->getDoctrine()
            ->getRepository('AppBundle:Post')
            ->fetch();

        $response = new Response();
        $response->headers->set("Content-type", "text/xml");
        $response->setContent(Xml::generate($posts));
        return $response;

    }
}

Conclusion

Now I had my rss feed up and running, I was able to get my connect.symfony.com profile to 100%, earn a badge, and scratch "create an RSS feed" off of my to-do list. There are a couple improvements that could be made. When I get more than 15 or 20 posts, I'll probably limit the response to the last 10 or so posts, and if I ever start to get a lot of traffic, I'll probably cache the XML and only update it when a new post is added, but remember kids, "premature optimization is the root of all evil"!

Thanks for reading. Feedback and/or questions are always appreciated!

If you liked this post, you can subscribe to the rss feed or follow me @ToddEidson on Twitter to be notified of future blog posts.

Date Published: 3 March, 2019

Tags: symfony php

About Todd

Full stack application developer. Life-long learner. Pragmatic programmer. Believer in clean coding. Proponent for extensible and reusable code. Hobbies include (very) amateur photography, collecting old jazz records and going to live music performances.

North Central Ohio, US
toddeidson[dot]info

Obligatory Disclaimer

All opinions are my own, probably wrong, and subject to change without notice.

© 2017-2019 Todd Eidson. All content is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.

Hosted on linode