Add custom sitemap entries

Overview

Of course Shopware comes with a sitemap generation feature, including products and categories, as well as some more URLs. This guide however will cover how you can add your own custom URLs to the sitemap.

Prerequisites

This guide is mostly built upon the guide about Adding a custom SEO URL, so you might want to have a look at that. The said guide comes with a custom entity, a controller with a technical route to display each entity, and a custom SEO URL. All of this will be needed for this guide, as we're going to add the custom entity SEO URLs to the sitemap here.

Adding an URL provider

So let's get started. Adding custom URLs to the sitemap is done by adding a so called "URL provider" to the system.
This is done by adding a new class, which is extending from Shopware\Core\Content\Sitemap\Provider\AbstractUrlProvider. It then has to be registered to the service container using the tag shopware.sitemap_url_provider.
It has to provide three methods:
  • getDecorated: Just throw an exception of type DecorationPatternException here. This is done for the sake of extending
    a class via decoration. Learn more about this here.
  • getName: A technical name for your custom URLs
  • getUrls: The main method to take care of. It has to return an instance of Shopware\Core\Content\Sitemap\Struct\UrlResult,
    containing an array of all URLs to be added.
Let's have a look at the example class:
CustomUrlProvider.php
services.xml
<plugin root>/src/Core/Content/Sitemap/Provider/CustomUrlProvider.php
1
<?php declare(strict_types=1);
2
​
3
namespace Swag\BasicExample\Core\Content\Sitemap\Provider;
4
​
5
use Doctrine\DBAL\Connection;
6
use Shopware\Core\Content\Sitemap\Provider\AbstractUrlProvider;
7
use Shopware\Core\Content\Sitemap\Struct\Url;
8
use Shopware\Core\Content\Sitemap\Struct\UrlResult;
9
use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\FetchModeHelper;
10
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
11
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
12
use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
13
use Shopware\Core\System\SalesChannel\SalesChannelContext;
14
use Swag\BasicExample\Core\Content\Example\ExampleEntity;
15
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
16
use Symfony\Component\Routing\RouterInterface;
17
​
18
class CustomUrlProvider extends AbstractUrlProvider
19
{
20
public const CHANGE_FREQ = 'daily';
21
public const PRIORITY = 1.0;
22
​
23
/**
24
* @var EntityRepositoryInterface
25
*/
26
private EntityRepositoryInterface $exampleRepository;
27
​
28
/**
29
* @var Connection
30
*/
31
private Connection $connection;
32
​
33
/**
34
* @var RouterInterface
35
*/
36
private RouterInterface $router;
37
​
38
public function __construct(
39
EntityRepositoryInterface $exampleRepository,
40
Connection $connection,
41
RouterInterface $router
42
) {
43
$this->exampleRepository = $exampleRepository;
44
$this->connection = $connection;
45
$this->router = $router;
46
}
47
​
48
public function getDecorated(): AbstractUrlProvider
49
{
50
throw new DecorationPatternException(self::class);
51
}
52
​
53
public function getName(): string
54
{
55
return 'custom';
56
}
57
​
58
/**
59
* {@inheritdoc}
60
*/
61
public function getUrls(SalesChannelContext $context, int $limit, ?int $offset = null): UrlResult
62
{
63
$criteria = new Criteria();
64
$criteria->setLimit($limit);
65
$criteria->setOffset($offset);
66
​
67
$exampleEntities = $this->exampleRepository->search($criteria, $context->getContext());
68
​
69
if ($exampleEntities->count() === 0) {
70
return new UrlResult([], null);
71
}
72
​
73
$seoUrls = $this->getSeoUrls($exampleEntities->getIds(), 'frontend.example.example', $context, $this->connection);
74
$seoUrls = FetchModeHelper::groupUnique($seoUrls);
75
​
76
$urls = [];
77
​
78
/** @var ExampleEntity $exampleEntity */
79
foreach ($exampleEntities as $exampleEntity) {
80
$exampleUrl = new Url();
81
$exampleUrl->setLastmod($exampleEntity->getUpdatedAt() ?? new \DateTime());
82
$exampleUrl->setChangefreq(self::CHANGE_FREQ);
83
$exampleUrl->setPriority(self::PRIORITY);
84
$exampleUrl->setResource(ExampleEntity::class);
85
$exampleUrl->setIdentifier($exampleEntity->getId());
86
​
87
if (isset($seoUrls[$exampleEntity->getId()])) {
88
$exampleUrl->setLoc($seoUrls[$exampleEntity->getId()]['seo_path_info']);
89
} else {
90
$exampleUrl->setLoc(
91
$this->router->generate(
92
'frontend.example.example',
93
['exampleId' => $exampleEntity->getId()],
94
UrlGeneratorInterface::ABSOLUTE_PATH
95
)
96
);
97
}
98
​
99
$urls[] = $exampleUrl;
100
}
101
​
102
return new UrlResult($urls, null);
103
}
104
}
Copied!
<plugin root>/src/Resources/config/services.xml
1
<?xml version="1.0" ?>
2
<container xmlns="http://symfony.com/schema/dic/services"
3
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
5
​
6
<services>
7
<service id="Swag\BasicExample\Core\Content\Sitemap\Provider\CustomUrlProvider" >
8
<argument type="service" id="swag_example.repository" />
9
<argument type="service" id="Doctrine\DBAL\Connection"/>
10
<argument type="service" id="router"/>
11
​
12
<tag name="shopware.sitemap_url_provider" />
13
</service>
14
</services>
15
</container>
Copied!
Let's go through this step by step. First of all we created a new class CustomUrlProvider, which is extending from the AbstractUrlProvider. Following are the constants CHANGE_FREQ and priority - you don't have to add those values as constants of course. They're going to be used later in the generation of the sitemap URLs.
Passed into the constructor are the repository for our custom entity, the DBAL connection used for actually fetching SEO URLs from the database, and the Symfony router in order to generate SEO URLs that have not yet been written to the database.
Now let's get to the main method getUrls. Here we start of with fetching all custom entities, using the provided $limit and $offset values. Make sure to always use those values, as the sitemap support "paging" and therefore you do not want to simply fetch all of your entities. If there arent't any entities to be fetched, there is nothing more to be done here.
Afterwards we fetch all already existing SEO URLs for our custom entities. Once again, have a look at our guide about adding a custom SEO URL if you don't know how to add custom SEO URLs in the first place.
We're then iterating over all of our fetched entities and we create an instance of Shopware\Core\Content\Sitemap\Struct\Url for each iteration. This struct requests each of the typical sitemap information:
  • lastMod: The last time this entry was modified. Just use the updatedAt value here, if available
  • changeFreq: How often will the entry most likely change?
    Possible values are always, hourly, daily, weekly, monthly, yearly and never
  • priority: Has to have a value between 0 and 1. URLs with higher priority are considered to be "more imporant" by common
    search engines.
  • resource: Just a name for your entry, in this example we're just using the entity class name
  • identifier: The ID of the entry, if available
The most important entry is set afterwards, which is the location: The actual SEO URL to be indexed. We're setting this value by checking if the SEO URL for the given entity was already generated, and if not, we're generating it on the fly.
All of those instances are then stored in array, which in return is passed to the UrlResult. And that's it already!
Last modified 6mo ago
Copy link
Edit on GitHub