Files
SkinbaseNova/app/Console/Commands/GenerateSitemapsCommand.php
klevze 8a80aae21e Ship production optimization M1-M12.5A: queues, metrics, HTTP observability, and vector search reliability.
Keep similar-ai from tripping the global circuit on a lone URL 502, clamp Qdrant search to 100, and add Server-Timing plus slow-request logging. Studio shared props, Academy S3 exists caching, heat chunking, and Redis/scheduler hygiene stay in this rollout.
2026-08-25 07:58:47 +02:00

205 lines
7.3 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Services\Sitemaps\SitemapBuildService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
/**
* Builds all sitemap documents and writes them as static .xml files to the
* public disk (default: public/sitemaps/sitemap.xml and public/sitemaps/{name}.xml).
*
* Nginx can then serve those files directly (try_files $uri @php) without
* hitting PHP at all. The SitemapController falls back to these same files
* on the PHP path if a request does reach it before a static file exists.
*
* Run manually: php artisan skinbase:sitemaps:generate
* With filtering: php artisan skinbase:sitemaps:generate --only=artworks,users
*/
final class GenerateSitemapsCommand extends Command
{
protected $signature = 'skinbase:sitemaps:generate
{--only=* : Limit to specific sitemap families (comma or space separated)}
{--disk= : Override the target filesystem disk (default: sitemaps.static_publish.disk)}';
protected $description = 'Build all sitemaps and write them as static .xml files to the configured public sitemap disk.';
public function handle(SitemapBuildService $build): int
{
$totalStart = microtime(true);
$families = $this->selectedFamilies($build);
if ($families === []) {
$this->error('No valid sitemap families selected.');
return self::INVALID;
}
$diskName = (string) ($this->option('disk') ?: config('sitemaps.static_publish.disk', 'sitemaps_public'));
$disk = Storage::disk($diskName);
$written = 0;
$failed = 0;
$this->info('Disk: ' . $diskName);
$this->info('Families: ' . implode(', ', $families));
$this->newLine();
// ── Root sitemap index ────────────────────────────────────────────
// Write several paths so nginx `location = /sitemap.xml` and child
// listings stay in sync. `sitemaps/index.xml` is the canonical generated
// index: it is a new filename, so a scheduler user can create it even
// when a stale `sitemaps/sitemap.xml` is owned by another account.
$t = microtime(true);
$index = $build->buildIndex(force: true, persist: false, families: $families);
$indexPaths = ['sitemaps/index.xml', 'sitemaps/sitemap.xml', 'sitemap.xml'];
$indexWritten = 0;
foreach ($indexPaths as $path) {
if ($this->writeXml($disk, $path, $index['content'])) {
$written++;
$indexWritten++;
}
}
if ($indexWritten === 0) {
$this->error('Failed to write any root sitemap index file. Check ownership of public/sitemap.xml and public/sitemaps/.');
return self::FAILURE;
}
$this->line(sprintf(
' <info>✔</info> sitemap index %d entries %d path(s) <comment>%.3fs</comment>',
$index['url_count'],
$indexWritten,
microtime(true) - $t,
));
// ── Per-family documents ──────────────────────────────────────────
foreach ($families as $family) {
$familyStart = microtime(true);
$names = $build->canonicalDocumentNamesForFamily($family);
$this->newLine();
$this->line(sprintf(' <fg=cyan>%s</> (%d document(s))', $family, count($names)));
foreach ($names as $documentName) {
$t = microtime(true);
$built = $build->buildNamed($documentName, force: true, persist: false);
if ($built === null) {
$this->line(sprintf(' <comment>–</comment> %s <fg=red>SKIPPED</> (builder returned null)', $documentName));
$failed++;
continue;
}
$path = 'sitemaps/' . $documentName . '.xml';
if (! $this->writeXml($disk, $path, $built['content'])) {
$this->line(sprintf(' <fg=red>✖</> %s write failed', $documentName . '.xml'));
$failed++;
continue;
}
$written++;
$this->line(sprintf(
' <info>✔</info> %s %d URLs <comment>%.3fs</comment>',
$documentName . '.xml',
$built['url_count'] ?? 0,
microtime(true) - $t,
));
}
$this->line(sprintf(
' <fg=cyan>%s</> done <comment>%.3fs</comment>',
$family,
microtime(true) - $familyStart,
));
}
foreach ($build->enabledGroupIndexes() as $groupName => $groupFamilies) {
foreach ($groupFamilies as $family) {
if (! in_array($family, $families, true)) {
continue 2;
}
}
$t = microtime(true);
$built = $build->buildNamed($groupName, force: true, persist: false);
if ($built === null) {
$this->line(sprintf(' <comment>–</comment> %s.xml <fg=red>SKIPPED</> (group builder returned null)', $groupName));
$failed++;
continue;
}
$path = 'sitemaps/' . $groupName . '.xml';
if (! $this->writeXml($disk, $path, $built['content'])) {
$this->line(sprintf(' <fg=red>✖</> %s.xml write failed', $groupName));
$failed++;
continue;
}
$written++;
$this->line(sprintf(
' <info>✔</info> %s %d entries <comment>%.3fs</comment>',
$groupName . '.xml',
$built['url_count'] ?? 0,
microtime(true) - $t,
));
}
// ── Summary ───────────────────────────────────────────────────────
$this->newLine();
$this->info(sprintf(
'Done: %d file(s) written, %d failed total <comment>%.3fs</comment>',
$written,
$failed,
microtime(true) - $totalStart,
));
return $failed > 0 ? self::FAILURE : self::SUCCESS;
}
/** @return list<string> */
private function selectedFamilies(SitemapBuildService $build): array
{
$only = [];
foreach ((array) $this->option('only') as $value) {
foreach (explode(',', (string) $value) as $family) {
$normalized = trim($family);
if ($normalized !== '') {
$only[] = $normalized;
}
}
}
$enabled = $build->enabledFamilies();
if ($only === []) {
return $enabled;
}
return array_values(array_filter($enabled, fn (string $f): bool => in_array($f, $only, true)));
}
private function writeXml(\Illuminate\Contracts\Filesystem\Filesystem $disk, string $path, string $content): bool
{
$ok = $disk->put($path, $content);
if ($ok !== true) {
$this->warn(' Could not write '.$path);
return false;
}
return true;
}
}