Building a blog: RSS feed

4th November 2018

There aren’t a lot of ways to “subscribe to updates” on this blog. It’s a combination of me trying to keep things simple, and not having enough time to implement anything I like.

One thing I have managed to put in place is an RSS feed. My friend, Brent, has one on his blog; and there’s been a recent resurgence in the number of people who seem interested in having it too.

This blog’s feed follows a similar patten to the view generation I showed you previously:

public function __invoke(Request $request, Response $response)
    $posts = await Post::recent();

        ->addHeader("Content-Type", "application/xml;charset=UTF-8")
            '<?xml version="1.0" encoding="utf-8" ?>'.
            <rss version="2.0">
                    <description>{"Blog posts by Christopher Pitt"}</description>
                    {array_map($post ~> $this->renderPost($post), $posts)}
This is from app/Responder/FeedResponder.pre

Aerys (the asynchronous HTTP library I’m using) doesn’t have any sort of auto-wiring; so, even though I’m not using the $request parameter, I still need to include it before I can get to the $response parameter.

Pre\Phpx doesn’t handle all potential markup. The doctype declaration, for instance, isn’t supported. Here, I prepend it as a string. The same goes for where I define the doctype declaration of the layout component. Have I shown you that yet..?

First up, I get a list of recent posts. These come from the database, so I know ahead of time that I’ll probably need to await the response. I could also just look at the function declaration:

async public static function recent()
    static $posts;

    if (!$posts) {
        $posts = [];

        $files = await scandir(.."/../../posts");
        $files = array_filter($files, $file ~> endsWith($file, ".md"));
        $files = array_map($file ~> { return substr($file, 0, strlen($file) - 3); }, $files);
        $files = array_slice(sortDescending($files), 0, 10);

        foreach ($files as $file) {
            array_push($posts, await static::find($file));

    return $posts;
This is from app/Model/Post.pre

scandir is another Amp\File method. It returns a list of file names in a given path. Using that list, I can filter out all files ending in .md. Then, I can map them to get the names excluding that extension.

Notice the difference between the expression short-closure and the longer version. In this case, it’s because I have the weirdest production-only bug, which seems to treat substr as an entire expression, and everything else as arguments to be given to an immediate call of the containing closure.
I have exactly the same version of all Composer and NPM dependencies, running in both environments. I think it’s caused by the PHP Prettier plugin, specifically on Linux…

Then, I sort the list of file names in descending order, and fetch at most 10 of them. I should probably use an aggregate Promise function, so those find calls happen in parallel. At least this function is memoized, so it’ll only happen once per deployment (or server crash).

All that’s left is to render the individual posts:

private function renderPost($post)
    return (
            <pubDate>{$post->date->format("D, d M Y H:i:s O")}</pubDate>
This is from app/Responder/FeedResponder.pre

With the right content type header, and CDATA block; I can render HTML markup for each post. Most feed readers will display this in using their own CSS, and the results are pleasing in the handful of readers I’ve used.

I keep coming back to file-based posts and HTML-in-PHP; because there’s really not much more to this blog than those two things. There are the odd few tricks, like short-closures and async/await, but they’re just conveniences.

I guess I’ll have more to talk about, as I expand the feature-set and start to reuse the stack for Shawn’s blog.

Would you like me to keep you in the loop?

I write about all sorts of interesting code things, and I'd love to share them with you. I will only send you updates from the blog, and will not share your email address with anyone.