Building Sitemaps

1.3 version
Maintained

Building Sitemaps

New in version 1.2: Sitemap support was introduced in SeoBundle 1.2.

This bundle helps loading documents that should go in a sitemap, extracting the information from them and rendering them to a sitemap information.

You can generate sitemaps in different formats and it is possible to provide more than one set of configurations.

Setting Up Sitemap Support

You need to register the route for the controller that is serving sitemaps:

  • YAML
    1
    2
    3
    4
    5
    # app/config/routing.yml
    # ...
    sitemaps:
        prefix: /sitemaps
        resource: "@CmfSeoBundle/Resources/config/routing/sitemap.xml"
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <!-- app/config/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
       <!-- ... -->
    
       <import prefix="/sitemaps" resource="@CmfSeoBundle/Resources/config/routing/sitemap.xml" />
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    // app/config/routing.php
    $sitemap = $loader->import("@CmfSeoBundle/Resources/config/routing/sitemap.xml")
    $sitemap->addPrefix('/sitemaps');
    $collection->addCollection($sitemap);
    
    return $collection;
    

If you only have one sitemap, you can omit the prefix. The URL of the default sitemap is /sitemap.{_format}. If you configure several sitemaps (see below), you might want to use the prefix to get /sitemaps/{config}.{_format}.

The minimal configuration is to just enable sitemap support with the default settings:

  • YAML
    1
    2
    3
    4
    # app/config/config.yml
    cmf_seo:
        sitemap:
            enabled: true
    
  • XML
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services">
    
        <config xmlns="http://example.org/schema/dic/cmf_seo">
            <sitemap enabled="true"/>
        </config>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    // app/config/config.php
    $container->loadFromExtension('cmf_seo', array(
        'sitemap' => array(
            'enabled' => true,
        ),
    ));
    

Rendering Sitemaps

Sitemaps can be rendered in different formats. While Json sitemaps are generated by serializing the information to Json, all other formats require a template. The SeoBundle comes with default templates for HTML and XML.

You can configure other templates to change the formatting or add new formats. The template is passed a list of UrlInformation objects in the parameter urls. A good starting point to build a custom template is the default template for that format provided by the SeoBundle bundle.

The templates are specified for a specific sitemap (see below) or in the defaults section:

  • YAML
    1
    2
    3
    4
    5
    6
    # app/config/config.yml
    cmf_seo:
        sitemap:
            defaults:
                templates:
                    html: AppBundle:Sitemap:default.html.twig
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services">
    
        <config xmlns="http://example.org/schema/dic/cmf_seo">
            <sitemap>
                <defaults>
                    <template format="html">AppBundle:Sitemap:default.html.twig</template>
                </defaults>
            </sitemap>
        </config>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // app/config/config.php
    $container->loadFromExtension('cmf_seo', array(
        'sitemap' => array(
            'defaults' => array(
                'templates' => array(
                    'html' => 'AppBundle:Sitemap:default.html.twig',
                ),
            ),
        ),
    ));
    

The formats for the templates are not limited, you can add any format you like.

UrlInformationProvider and Customizing Sitemap Information

The UrlInformationProvider consists of 3 steps:

# Loader: Load models from a database or similar; # Voter: Decide whether the loaded models should be included in the sitemap; # Guesser: Populate the UrlInformation from the models.

Each of these steps is chaining handlers. You can add your own handlers by implementing the corresponding interface LoaderInterface, VoterInterface or GuesserInterface respectively. Then define a service and tag it with cmf_seo.sitemap.loader, cmf_seo.sitemap.voter or cmf_seo.sitemap.guesser.

A guesser to determine when a page was last modified could look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// src/AppBundle/Seo/Sitemap/LastModifiedGuesser.php
namespace AppBundle\Seo\Sitemap;

use Symfony\Cmf\Bundle\SeoBundle\Sitemap\GuesserInterface;
use AppBundle\Model\LastModifiedInterface;

class LastModifiedGuesser implements GuesserInterface
{

    /**
     * Updates UrlInformation with new values if they are not already set.
     *
     * @param UrlInformation $urlInformation The value object to update.
     * @param object         $object         The sitemap element to get values from.
     * @param string         $sitemap        Name of the sitemap being built.
     */
    public function guessValues(UrlInformation $urlInformation, $object, $sitemap)
    {
        if ($urlInformation->getLastModification()) {
            // guessers should not overwrite existing values
            return;
        }
        if (!$object instanceof LastModifiedInterface) {
            return;
        }

        // we assume that the LastModifiedInterface provides a method getLastModifiedDate that returns a DateTime object
        $urlInformation->setLastModification($object->getLastModifiedDate());
    }
}

A guesser should never overwrite an existing value. Rather, you control which guesser comes first with the help of the priority attribute to the service tag. All default guessers, voters and loaders have a priority of -1 or -2.

You can also restrict which sitemaps a guesser (or loader or voter) is used for. By default, it applies to all sitemaps, but you can specify a comma separated list in the sitemaps attribute of the tag.

The service definition looks as follows:

  • YAML
    1
    2
    3
    4
    5
    services:
        seo.sitemap.guesser.last_modified:
            class: AppBundle\Seo\Sitemap\LastModifiedGuesser
            tags:
                - { name: cmf_seo.sitemap.guesser, priority: 10 }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <?xml version="1.0" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <service id="seo.sitemap.guesser.last_modified" class="AppBundle\Seo\Sitemap\LastModifiedGuesser">
                <tag name="cmf_seo.sitemap.guesser" priority="10"/>
            </service>
        </services>
    </container>
    
  • PHP
    1
    2
    3
    $container->register('seo.sitemap.guesser.last_modified', 'AppBundle\Seo\Sitemap\LastModifiedGuesser')
        ->addTag('cmf_seo.sitemap.guesser', array('priority' => 10)
    ;
    

Loaders and voters work exactly the same, with the tags cmf_seo.sitemap.loader and cmf_seo.sitemap.voter. Their tags also have a priority and sitemaps attribute.

Multiple Sitemaps

You can create more than one sitemap with a different configuration of loaders, voters and guessers. The most common use case is to have sub-sitemaps with limited loaders or additional voters.

If you do not specify any sitemaps, a sitemap called sitemap will be automatically created. If you want to keep that default map but add additional sitemaps, you need to explicitly specify sitemap as well:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # app/config/config.yml
    cmf_seo:
        sitemap:
            configurations:
                sitemap: ~
                categories:
                    default_change_frequency: hourly
                    templates:
                        html: AppBundle:Sitemap:categories.html.twig
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services">
    
        <config xmlns="http://example.org/schema/dic/cmf_seo">
            <sitemap>
                <configuration name="sitemap"/>
                <configuration name="categories" default-change-frequency="hourly">
                    <template format="html">AppBundle:Sitemap:categories.html.twig</template>
                </configuration>
            </sitemap>
        </config>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // app/config/config.php
    $container->loadFromExtension('cmf_seo', array(
        'sitemap' => array(
            'configurations' => array(
                'sitemap' => null,
                'categories' => array(
                    'default_change_frequency' => 'hourly',
                    'templates' => array(
                        'html' => 'AppBundle:Sitemap:categories.html.twig',
                    ),
                ),
            ),
        ),
    ));
    

You will now be able to serve a categories sitemap at <prefix>/categories.html.

In this example, you might register an additional voter for the categories sitemap to only keep category pages in the sitemap. All default configuration is merged into each sitemap configuration.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.