Xml sitemaps with Symfony2

Symfony2 is a nice PHP framework that provides different ways to solve problems. XML sitemap files are the de-facto standard to inform search engines about the content of a website. Using Symfony2 to generate xml sitemaps with a simple controller/template solution is described in this article.

Requirements

There are no special requirements. This solution does not require special bundles or libraries. To view the generated sitemaps file with a browser, an xslt template can be used (e.g. from http://gstoolbox.sourceforge.net/).

Solution

A flat, simple controller is used to generate the list of urls (with their meta data) – and this is rendered inside of a simple template.

Sitemaps controller

You can insert the following code also in an already existing container. Using a special controller makes it clear where to search for when adding additional urls.

<?php
// src/Acme/Sample/StoreBundle/Controller/SitemapsController.php
namespace Acme\Sample\StoreBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

class SitemapsController extends Controller
{

    /**
     * @Route("/sitemap.{_format}", name="sample_sitemaps_sitemap", Requirements={"_format" = "xml"})
     * @Template("AcmeSampleStoreBundle:Sitemaps:sitemap.xml.twig")
     */
    public function sitemapAction() 
    {
        $em = $this->getDoctrine()->getEntityManager();
        
        $urls = array();
        $hostname = $this->getRequest()->getHost();

        // add some urls homepage
        $urls[] = array('loc' => $this->get('router')->generate('home'), 'changefreq' => 'weekly', 'priority' => '1.0');

        // multi-lang pages
        foreach($languages as $lang) {
            $urls[] = array('loc' => $this->get('router')->generate('home_contact', array('_locale' => $lang)), 'changefreq' => 'monthly', 'priority' => '0.3');
        }
        
        // urls from database
        $urls[] = array('loc' => $this->get('router')->generate('home_product_overview', array('_locale' => 'en')), 'changefreq' => 'weekly', 'priority' => '0.7');
        // service
        foreach ($em->getRepository('AcmeSampleStoreBundle:Product')->findAll() as $product) {
            $urls[] = array('loc' => $this->get('router')->generate('home_product_detail', 
                    array('productSlug' => $product->getSlug())), 'priority' => '0.5');
        }

        return array('urls' => $urls, 'hostname' => $hostname);
    }
}

We use some of the nice features provided by Symfony2:

  • parameter _format: the only allowed value is xml, which is handled by Symfony2 – it adds the correct headers and settings for the output, so that it is correct xml.

Sitemaps twig template

twig is nice and provides handy functions. Here is the template for the sitemapAction:

// src/Acme/Sample/StoreBundle/Resources/views/Sitemaps/sitemap.xml.twig
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="{{asset("sitemap.xsl")}}"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for url in urls %}
    <url>{# check if hostname is not alreay in url#}
        <loc>{%if url.loc|replace({hostname:''}) == url.loc%}{{hostname}}{{url.loc}}{%else%}{{url.loc}}{%endif%}</loc>
{% if url.lastmod is defined %}
        <lastmod>{{url.lastmod}}</lastmod>
{% endif %}
{% if url.changefreq is defined %}
        <changefreq>{{url.changefreq}}</changefreq>
{% endif %}
{% if url.priority is defined %}
        <priority>{{url.priority}}</priority>
{% endif %}
    </url>
{% endfor %}
</urlset>

Details

The template requires a parameter urls that is a list (array) of url-arrays with the keys url, hostname, changefreq and priority.
This simple, quick and dirty solution does not implement additional meta data (e.g. images) for each of the url elements. It also does not handle extended sitemaps as e.g. news and video.

Conclusion

There are some sitemap bundles available – but they are not necessary. Since the heavy part (computing and adding of the urls) is application specific (here handled in the sitemapAction function), sitemap bundles introduce a new dependency that can break. And the generation is really simple, so this solution might fit for a lot of people.
Found a bug, idea how to improve – contact us.

License of the code samples: LGPL / you can use this code as you like. It would be great if you could add a link to this page or enarion.net, though.