Installation:
composer require ecourty/sitemap-bundle
Enable the bundle in config/bundles.php:
return [
// ...
EdouardCourty\SitemapBundle\EdouardCourtySitemapBundle::class => ['all' => true],
];
Basic Configuration:
Add a minimal config/packages/edouard_courty_sitemap.yaml:
edouard_courty_sitemap:
sitemaps:
default:
routes:
- path: '/'
priority: 1.0
changefreq: 'daily'
First Use Case: Generate a static sitemap via CLI:
php bin/console edouard-courty:sitemap:generate default
Output will be saved to public/sitemap.xml (default location).
Use the SitemapGenerator service to dynamically generate sitemaps in a controller:
use EdouardCourty\SitemapBundle\Generator\SitemapGenerator;
class SitemapController extends AbstractController
{
public function generate(SitemapGenerator $generator): Response
{
return $generator->generate('default');
}
}
# config/routes.yaml
sitemap:
path: /sitemap.xml
controller: App\Controller\SitemapController::generate
Leverage the CLI command for scheduled generation (e.g., via cron):
# Generate all configured sitemaps
php bin/console edouard-courty:sitemap:generate
# Generate a specific sitemap
php bin/console edouard-courty:sitemap:generate default
0 3 * * * cd /path/to/project && php bin/console edouard-courty:sitemap:generate >> /dev/null 2>&1
Configure Doctrine entities for dynamic sitemap entries:
edouard_courty_sitemap:
sitemaps:
blog:
entities:
- App\Entity\Post
- App\Entity\Category
entity_options:
App\Entity\Post:
route: 'post_show'
route_parameters:
id: 'id'
priority: 0.8
changefreq: 'weekly'
dql: 'WHERE p.published = true'
Split large sitemaps into an index:
edouard_courty_sitemap:
sitemaps:
index:
index:
mode: 'auto' # or 'manual'
sitemaps:
- 'default'
- 'blog'
Memory Issues:
dql filters to limit results:
dql: 'WHERE p.createdAt > :date LIMIT 1000'
parameters:
date: '2023-01-01'
Route Parameter Mismatches:
route_parameters match the actual route definition. Example:
# Correct (matches `post_show` route)
route_parameters:
id: 'id'
# Incorrect (will fail silently)
route_parameters:
slug: 'id' # Mismatch!
Caching Headers:
public/sitemap.xml .htaccess:
<FilesMatch "sitemap\.xml">
Header set Cache-Control "public, max-age=86400"
</FilesMatch>
Validate XML: Use an online validator (e.g., XML Validation) to check generated output for errors.
Log Queries: Enable Doctrine logging to debug DQL issues:
# config/packages/dev/doctrine.yaml
doctrine:
dbal:
logging: true
profiling: true
Check File Permissions:
Ensure the public/ directory is writable:
chmod -R 775 public/
Custom URL Providers: Extend functionality by creating a custom provider:
namespace App\Sitemap;
use EdouardCourty\SitemapBundle\Provider\UrlProviderInterface;
class CustomUrlProvider implements UrlProviderInterface
{
public function getUrls(): array
{
return [
['loc' => 'https://example.com/custom-page', 'priority' => 0.5],
];
}
}
Register it in config/packages/edouard_courty_sitemap.yaml:
edouard_courty_sitemap:
providers:
custom:
class: App\Sitemap\CustomUrlProvider
priority: 100
Custom Repository Methods: Override entity queries in a custom repository:
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class PostRepository extends ServiceEntityRepository
{
public function findPublishedPosts(): array
{
return $this->createQueryBuilder('p')
->where('p.published = :published')
->setParameter('published', true)
->getQuery()
->getResult();
}
}
Reference it in config:
edouard_courty_sitemap:
sitemaps:
blog:
entities:
- App\Entity\Post
entity_options:
App\Entity\Post:
repository_method: 'findPublishedPosts'
Streaming Large Datasets: For entities with >10,000 records, use cursor iteration in a custom provider:
use Doctrine\ORM\AbstractQuery;
class LargeDatasetProvider implements UrlProviderInterface
{
public function getUrls(): array
{
$em = $this->container->get('doctrine')->getManager();
$qb = $em->createQueryBuilder()
->select('p')
->from('App\Entity\Post', 'p')
->where('p.published = :published')
->setParameter('published', true);
$query = $qb->getQuery();
$query->setHint(AbstractQuery::HINT_FORCE_PARTIAL_LOAD, true);
$results = $query->iterate();
$urls = [];
foreach ($results as $result) {
$post = $result[0];
$urls[] = [
'loc' => $this->generateUrl('post_show', ['id' => $post->getId()]),
'priority' => 0.7,
];
}
return $urls;
}
}
Priority/Changefreq Validation: Values must adhere to sitemap standards:
priority: 0.0 to 1.0 (float).changefreq: always, hourly, daily, weekly, monthly, yearly, or never.Route Parameter Types:
Ensure parameters match the route’s expected type (e.g., int vs. string):
# Correct for integer ID
route_parameters:
id: 'id'
# Incorrect for string slug
route_parameters:
id: 'id' # Fails if route expects slug
Sitemap Index Modes:
auto: Generates index if >50 URLs.manual: Only generates index if explicitly configured.disabled: Never generates an index.How can I help you explore Laravel packages today?