cahuk/sitemap-generator

Flexible PHP sitemap generator

Installs: 2

Dependents: 0

Suggesters: 0

Security: 0

Stars: 2

Watchers: 2

Forks: 0

Open Issues: 0

pkg:composer/cahuk/sitemap-generator

1.0.0 2025-11-16 15:23 UTC

This package is auto-updated.

Last update: 2025-11-18 14:12:47 UTC


README

A flexible, high-performance PHP library for generating XML sitemaps of any scale.
Supports multiple URL providers, automatic partitioning, indexing, configurable priorities, frequencies, timestamps, and flexible storage options.

This package follows a clean DDD-inspired structure with separation between Domain, Application, and Infrastructure layers.

๐Ÿš€ Features

  • Generate XML sitemaps with zero external dependencies
  • Multiple sitemap sections (pages, blog, categories, etc.)
  • Automatic sitemap partitioning (e.g., split every 50,000 URLs or at your choice)
  • Fully customizable URL providers
  • Built-in XML renderer
  • Local filesystem writer (can be extended to S3, FTP, DB, etc.)
  • Strong DTOs, enums, and typed entries
  • Simple integration into any PHP project (Symfony, Laravel, custom framework)

๐Ÿš€ Installation

Install via Composer:

composer require cahuk/sitemap-generator

#๐Ÿงฉ Basic Example Below is a real working example based directly on this package:

declare(strict_types = 1);

use Cahuk\SitemapGenerator\Application\Service\SitemapGenerator;
use Cahuk\SitemapGenerator\Domain\Model\Enum\ChangeFrequencyEnum;
use Cahuk\SitemapGenerator\Application\Service\SitemapPartitioner;
use Cahuk\SitemapGenerator\Infrastructure\Renderer\XmlSitemapRenderer;
use Cahuk\SitemapGenerator\Application\Dto\SitemapGeneratorSettingDto;
use Cahuk\SitemapGenerator\Infrastructure\Repository\LocalFilesystemWriter;
use Cahuk\SitemapGenerator\Infrastructure\EntryProvider\ArrayUrlEntryProvider;

require_once './vendor/autoload.php';

// 1. Page URLs provider
$pagesEntriesProvider = new ArrayUrlEntryProvider(
    name           : 'sitemap-pages',
    urlEntryData   : [
        'https://example.com/'                      => ['lastModified' => '2025-01-01'],
        'https://example.com/page/our-team'         => ['lastModified' => '2025-01-01'],
        'https://example.com/page/contact-ua'       => ['lastModified' => '2025-01-01'],
        'https://example.com/page/terms'            => ['lastModified' => '2025-01-01'],
        'https://example.com/page/reviews'          => ['lastModified' => '2025-01-01'],
        'https://example.com/page/reviews/john-doe' => ['lastModified' => '2025-01-01'],
    ],
    defPriority    : 0.8,
    defLastModified: new DateTimeImmutable('-10 days'),
    defChangeFreq  : ChangeFrequencyEnum::Monthly,
);

// 2. Blog URLs provider
$blogEntriesProvider = new ArrayUrlEntryProvider(
    name        : 'sitemap-blog',
    urlEntryData: [
        'https://example.com/blog/hot-topic'    => [
            'lastModified' => '2025-01-01',
            'priority'     => 0.7,
            'changeFreq'   => 'daily',
        ],
        'https://example.com/blog/case-study'   => [
            'lastModified' => '2025-01-01',
            'priority'     => 0.5,
            'changeFreq'   => 'monthly',
        ],
        'https://example.com/blog/how-to-start' => [
            'lastModified' => '2025-01-01',
            'priority'     => 0.5,
            'changeFreq'   => 'yearly',
        ],
    ]
);

// 3. Maximum URLs per sitemap file
$maxUrlsPerSitemap = 5000;

// 4. Renderer + filesystem adapter
$sitemapRender = new XmlSitemapRenderer();

// 5. Generator
$sitemapGenerator = new SitemapGenerator(
    repository      : new LocalFilesystemWriter(
        basePath     : "./",
        renderer     : $sitemapRender,
        indexRenderer: $sitemapRender,
    ),
    generatorSetting: new SitemapGeneratorSettingDto(
        host: 'https://freedemo.games/',
    ),
    partitioner     : new SitemapPartitioner(
        $maxUrlsPerSitemap,
        ...[$pagesEntriesProvider, $blogEntriesProvider],
    ),
);

// 6. Generate
$result = $sitemapGenerator->generateSitemap();

echo "Generated:" . PHP_EOL;
$result = [
    'generated_at'            => $result->generatedAt()->format(DATE_W3C),
    'duration'                => $result->getDuration(),
    'urls_count'              => $result->getUrlCount(),
    'path'                    => $result->getPath(),
    'file_names'              => implode(', ', $result->getFileNames()),
    'memory_usage_bytes'      => $result->getMemoryUsedBytes(),
    'memory_peak_usage_bytes' => $result->getMemoryPeakUsageBytes(),
];

foreach ($result as $field => $value) {
    echo "$field: $value " . PHP_EOL;
}

#๐Ÿ“ Output The generator produces:

sitemap.xml
sitemap-pages.xml
sitemap-blog.xml

With automatic when needed. Example of generated index:

<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <sitemap>
        <loc>https://freedemo.games/sitemap-pages.xml</loc>
        <lastmod>2025-01-01</lastmod>
    </sitemap>
    <sitemap>
        <loc>https://freedemo.games/sitemap-blog.xml</loc>
        <lastmod>2025-01-01</lastmod>
    </sitemap>
</sitemapindex>

#๐Ÿงฑ URL Entry Providers This package ships out of the box with a ready-to-use provider: โœ” ArrayUrlEntryProvider (built-in) A simple provider that accepts an associative array where:

  • keys = URL strings
  • values = metadata (lastModified, priority, changeFreq, ...)
$pagesEntriesProvider = new ArrayUrlEntryProvider(
    name           : 'sitemap-pages',
    urlEntryData   : [
        'https://example.com/' => [
            'lastModified' => '2025-01-01',
        ],
        'https://example.com/page/our-team' => [
            'lastModified' => '2025-01-01',
        ],
    ],
    defPriority    : 0.8,
    defLastModified: new DateTimeImmutable(),
    defChangeFreq  : ChangeFrequencyEnum::Monthly,
);

#๐Ÿงฉ Creating Custom Providers If the built-in ArrayUrlEntryProvider is not enough, you can easily create your own custom provider by implementing:

Cahuk\SitemapGenerator\Domain\Model\EntryProviderInterface

This allows you to fetch URLs from:

  • a database
  • an API
  • CMS
  • search index
  • filesystem
  • any dynamic source Example skeleton:
<?php

declare(strict_types = 1);

namespace Cahuk\SitemapGenerator\Infrastructure\EntryProvider;

use Override;
use PDO;
use Exception;
use DateTimeImmutable;
use Cahuk\SitemapGenerator\Domain\Model\UrlEntry;
use Cahuk\SitemapGenerator\Domain\Model\ValueObject\Url;
use Cahuk\SitemapGenerator\Domain\Model\ValueObject\Priority;
use Cahuk\SitemapGenerator\Domain\Model\Enum\ChangeFrequencyEnum;
use Cahuk\SitemapGenerator\Domain\Model\ValueObject\ChangeFrequency;
use Cahuk\SitemapGenerator\Domain\EntryProvider\UrlEntryProviderInterface;

/**
 * Class MyCustomProvider
 *
 * Example of a custom URL provider powered by database entries.
 */
final class MyCustomProvider implements UrlEntryProviderInterface
{
    private string $name;
    private PDO    $pdo;

    private ?float               $defPriority;
    private ?DateTimeImmutable   $defLastModified;
    private ?ChangeFrequencyEnum $defChangeFreq;

    /**
     * @param string                   $name
     * @param PDO                      $pdo
     * @param float|null               $defPriority
     * @param DateTimeImmutable|null   $defLastModified
     * @param ChangeFrequencyEnum|null $defChangeFreq
     */
    public function __construct(
        string $name,
        PDO $pdo,
        ?float $defPriority = null,
        ?DateTimeImmutable $defLastModified = null,
        ?ChangeFrequencyEnum $defChangeFreq = null,
    ) {
        $this->name            = $name;
        $this->pdo             = $pdo;
        $this->defPriority     = $defPriority;
        $this->defLastModified = $defLastModified;
        $this->defChangeFreq   = $defChangeFreq;
    }

    #[Override]
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Returns iterable list of UrlEntry objects from the database.
     *
     * Your database table must contain at least:
     *   - url (string)
     *   - updated_at (optional, datetime)
     *   - priority (optional float)
     *   - change_freq (optional string matching ChangeFrequencyEnum)
     *
     * @throws Exception
     */
    #[Override]
    public function getUrlEntry(): iterable
    {
        $stmt = $this->pdo->query("
            SELECT 
                url,
                updated_at,
                priority,
                change_freq
            FROM sitemap_urls
        ");

        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {

            // URL value object
            $url = new Url($row['url']);

            // Priority
            $priority = isset($row['priority']) ?
                new Priority((float)$row['priority']) :
                ($this->defPriority ? new Priority($this->defPriority) : null);

            // Last modified
            $lastModified = !empty($row['updated_at'])
                ? new DateTimeImmutable($row['updated_at'])
                : $this->defLastModified;

            // Change frequency
            $changeFreq = !empty($row['change_freq'])
                ? new ChangeFrequency(ChangeFrequencyEnum::from($row['change_freq']))
                : ($this->defChangeFreq ? new ChangeFrequency($this->defChangeFreq) : null);

            yield new UrlEntry(
                url         : $url,
                lastModified: $lastModified,
                priority    : $priority,
                changeFreq  : $changeFreq,
            );
        }
    }
}

#๐Ÿงช Performance

  • The generator tracks:
  • Execution time
  • URL count
  • Memory usage
  • Peak memory usage
generated_at: 2025-01-01T10:00:00+00:00
duration: 0.183s
urls_count: 42
path: ./sitemap.xml
file_names: sitemap.xml, sitemap-pages.xml, sitemap-blog.xml
memory_usage_bytes: 24576
memory_peak_usage_bytes: 49152