kphoen/sitemap-generator
A framework-agnostic PHP library for building XML sitemaps. Add URLs with optional metadata, then render standards-compliant sitemap or sitemap index files, optionally gzipped. Simple, dependency-free API for keeping search engines up to date.
The package is not Laravel-aware: there is no service provider, facade, config file, or artisan command. You wire it yourself.
Minimal steps:
composer require kphoen/sitemap-generator — but expect the resolver to land on the 2015 release. Because the repo is archived and has zero dependents, treat the constraint as pinned, and if you keep it long-term, plan to fork it into packages/ (a path repository) or vendor the handful of classes directly into app/Support/Sitemap.DateTime handling, loose comparisons) before writing a line of application code.Sitemap container, a Url value object (loc, lastmod, changefreq, priority), a generator that serialises the container to a file/resource, and a builder abstraction whose job is to turn a data source — Propel, Doctrine, or your own class — into sitemap entries.First use case, in Laravel terms: a console command that walks your Eloquent models, builds the sitemap in memory, and writes public/sitemap.xml.
// app/Console/Commands/GenerateSitemap.php
use Kphoen\SitemapGenerator\Sitemap;
use Kphoen\SitemapGenerator\Url;
use Kphoen\SitemapGenerator\SitemapGenerator;
public function handle()
{
$sitemap = new Sitemap();
$sitemap->addUrl(new Url(route('home')));
foreach (Post::where('published', true)->cursor() as $post) {
$sitemap->addUrl(new Url(
route('posts.show', $post),
$post->updated_at->toDateTime(),
Url::FREQUENCY_WEEKLY,
0.8
));
}
(new SitemapGenerator($sitemap))->generate(public_path('sitemap.xml'));
}
Check the README for the exact constructor/method names — the shape above is faithful to the package's model, but the signatures are from a 2015 codebase and worth confirming against src/ before you build on them.
Command-driven generation, not request-time. Register sitemap:generate and call it from your deploy script or $schedule->command('sitemap:generate')->daily(). Never build the sitemap inside a controller; a cache miss on a cold container will hold a PHP worker for the duration of a full table scan.
One loop per content type. Keep the command readable by extracting addPosts($sitemap), addCategories($sitemap), etc. Use cursor() or chunkById() rather than get() — the Sitemap object holds every Url in memory until generation, so the dataset is your memory ceiling.
Use the builder abstraction for ORM sources. The package's selling point is that a builder can enumerate entities for you. If you adopt it, keep the builder thin (route name + lastmod column + priority) and let it return plain Url objects. If the bundled Doctrine/Propel builders don't fit Eloquent, write a small EloquentBuilder implementing the package's builder contract rather than special-casing in the command — that is the extension point the package actually expects you to use.
Sitemap indexes for scale. The spec caps a file at 50,000 URLs / 50 MB uncompressed. If you approach that, generate sitemap-posts-1.xml, sitemap-posts-2.xml, … and hand-write a <sitemapindex> pointing at each. This package does not manage the index, so budget for that yourself.
Serving. Write to public/ so nginx/Apache serves it as a static file (no PHP boot), and reference it from public/robots.txt. If you must serve dynamically, cache the generated string with Cache::remember and set Content-Type: application/xml.
Testing. Assert on the generated XML rather than the object graph: simplexml_load_string(file_get_contents($path)), count <url> nodes, and assert a known model appears with an absolute URL and a valid <lastmod>. Add one test per content type; sitemap bugs are almost always "this section silently stopped being added."
Swap-friendliness. Wrap the package behind one class of your own (App\Support\Sitemap\Generator) that exposes add(url, lastmod, freq, priority) and write(path). Given the package's status, that adapter is the difference between a one-hour migration to spatie/laravel-sitemap and a refactor.
composer dump-autoload doesn't find the classes, check composer.json's autoload block before hunting elsewhere.route() gives absolute URLs; url() with a raw path won't. Query strings with & need escaping — confirm the writer does it, and never hand-concatenate ?a=1&b=2 into a Url.lastmod must be W3C/ISO 8601, not a MySQL timestamp string. Pass a DateTime (e.g. $model->updated_at->toDateTime()); a bare "2024-01-01 10:00:00" is invalid and Google will ignore or flag it.changefreq and priority are advisory. Google has said it largely ignores them. Don't invest in a priority-scoring scheme; spend the effort on accurate lastmod and on not including noindex/404/redirecting URLs.Url objects. Chunk into multiple sitemaps or stream output yourself. Debug OOMs with memory_get_peak_usage() wrapped around the generation call.https://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd locally, then submit in Google Search Console → Sitemaps. GSC's "couldn't fetch" errors usually mean a robots.txt block or a non-200 status, not malformed XML.Url object. There is no event system, no formatter interface, no gzip option. If you need .xml.gz, gzip the file after generation (gzencode) and submit the .gz URL — don't try to make the generator do it.where('published', true); review the command with the same eye as a public route.How can I help you explore Laravel packages today?