Markus Michalski FAQ

XML-Sitemap: Technische Details

Technische Details der XML-Sitemap für Sylius 2: Ablauf der Generierung, Plugin-Struktur, Speicherlayout mit Sperrdatei, Aufbau des XML, Datenbanktabelle, Admin-Integration und Reader.

English Version

Zurück zur Übersicht

XML-Sitemap: Technische Details

Diese Dokumentation bezieht sich immer auf die aktuellste veröffentlichte Version.

Ablauf der Generierung

graph TD
    A["Befehl oder Admin-Button"] --> B["SitemapRefresher"]
    B --> C{"Einstellungen, Channel und Host in Ordnung?"}
    C -->|Nein| D["SitemapGenerationException"]
    C -->|Ja| E{"Sperre des Channels frei?"}
    E -->|Nein| D
    E -->|Ja| F["SitemapGenerator"]
    F --> G["Provider: Produkte, Kategorien, Seiten, eigene"]
    G --> H["Dateien nach URL-Anzahl und Größe teilen"]
    H --> I["Index zuletzt schreiben"]
    I --> J["Veraltete Dateien löschen"]
    J --> K["Letzte Generierung speichern"]
  1. SitemapRefresher lädt die Einstellungen und den Sylius-Channel, prüft beides und baut einen ChannelContext (Hostname, Standardsprache, Sprachen, Menü-Taxon, Einstellungen). Alle Werte werden vorab kopiert, weil die Reader den Entity Manager zwischen den Blöcken leeren.
  2. Die Sperre pro Channel wird nicht blockierend angefordert. Ist sie belegt, endet der Lauf sofort mit einer Meldung.
  3. SitemapGenerator prüft zuerst die Abschnittsnamen aller Provider, bevor irgendetwas geschrieben wird.
  4. Die Provider laufen in der Reihenfolge ihrer Tag-Priorität. Jede URL wird über einen 60-Bit-Hash ihrer Adresse auf Dopplungen geprüft.
  5. Jeder Abschnitt wird in eine temporäre Datei gestreamt. Eine neue Datei beginnt, bevor die nächste URL eines der beiden Limits (max_urls_per_file, max_bytes_per_file) überschreiten würde. Eine einzelne URL, die größer als das ganze Budget ist, bekommt eine eigene Datei.
  6. Ohne jede URL entsteht sitemap-empty-1.xml.
  7. Der Index wird zuletzt geschrieben, danach werden veraltete Dateien gelöscht, die zum Muster sitemap-<abschnitt>-<nr>.xml passen. Fremde Dateien im Ordner bleiben unberührt.
  8. Jede Ausnahme außer SitemapGenerationException wird in eine solche umgewandelt, damit ein Fehler nur den betroffenen Channel betrifft. Nach dem Lauf werden Zeitpunkt und URL-Anzahl in der Einstellung gespeichert.

Plugin-Struktur

src/
  Catalog/       Reader für Produkte und Kategorien (Doctrine, hinter Interfaces) und ihre Datensätze
  Command/       mmd:sitemap:generate, mmd:sitemap:setup
  Controller/    Auslieferung der Sitemap, Admin-Aktion Jetzt generieren
  DependencyInjection/  Konfiguration und Extension
  Entity/        ChannelSettings
  Exception/     SitemapGenerationException
  Form/Type/     Formular der Channel-Einstellungen
  Generator/     Generator, Refresher, Channel-Kontext
  Menu/          Eintrag in der Admin-Seitenleiste
  Migrations/    Tabelle der Channel-Einstellungen
  Provider/      URL-Provider und UrlProviderInterface
  Settings/      Repository der Channel-Einstellungen
  Sitemap/       Wertobjekte und XML-Writer
  Storage/       Dateispeicher und Sperre
  Twig/          Funktion sitemap_plugin_version()
  Url/           URL-Bau über den Router, Hreflang, Bild-URLs
config/          services.php, Routen, Grid, Twig-Hooks
templates/       Titelblock und Button der Admin-Seite
translations/    Deutsch und Englisch

Speicherlayout

<storage_dir>/
  <Channel-Code>.lock                    Sperrdatei
  <Channel-Code>/
    index.xml                            Sitemap-Index, ausgeliefert als /sitemap-index.xml und /sitemap.xml
    sitemap-products-1.xml
    sitemap-categories-1.xml
    sitemap-pages-1.xml

Geschrieben wird über eine temporäre Datei im Zielordner, die an ihren Platz umbenannt wird. Dateinamen müssen auf [A-Za-z0-9_-]+.xml passen, weil der Name aus der URL kommt und den Ordner nicht verlassen darf. Die Sperre ist ein flock() pro Channel, das das Betriebssystem beim Prozessende selbst freigibt.

Aufbau des XML

Jede Datei ist ein urlset mit den Namensräumen für Sitemap, XHTML und Google-Bilder:

<url>
  <loc>https://shop.example.com/de/products/beispiel</loc>
  <lastmod>2026-10-01T10:00:00+00:00</lastmod>
  <changefreq>weekly</changefreq>
  <priority>0.8</priority>
  <xhtml:link rel="alternate" hreflang="de" href="https://shop.example.com/de/products/beispiel"/>
  <xhtml:link rel="alternate" hreflang="x-default" href="https://shop.example.com/de/products/beispiel"/>
  <image:image>
    <image:loc>https://shop.example.com/media/image/ab/cd/bild.jpg</image:loc>
    <image:title>Beispielprodukt</image:title>
  </image:image>
</url>

Der Index ist ein sitemapindex mit loc und lastmod je Datei. Die URL-Fragmente sind nicht eingerückt, weil Leerraum nur Dateigröße kostet.

Datenbank

Eine Tabelle, mmd_sylius_sitemap_channel_settings, mit höchstens einer Zeile pro Channel. Der Channel wird über seinen Code zugeordnet, nicht über einen Fremdschlüssel, damit keine Tabelle des Sylius-Kerns gemappt werden muss.

Spalte Typ Standard
id int, Primärschlüssel
channel_code varchar(255), eindeutig
enabled bool falsch
include_products, include_taxons, include_static_pages, include_images, include_hreflang bool wahr
product_change_frequency, taxon_change_frequency varchar(16) weekly
static_change_frequency varchar(16) monthly
product_priority float 0.8
taxon_priority float 0.6
static_priority float 0.5
last_generated_at datetime, nullable
last_url_count int, nullable

Admin-Integration

Das Grid und die Twig-Hooks (Titelblock mit Plugin-Version, Button „Jetzt generieren“) bindet das Bundle ein, indem es die Konfiguration per Prepend vor die der Anwendung stellt, weil eine config/config.yaml des Plugins in einer echten Anwendung nicht geladen wird. Die Admin-Routen nutzen den Parameter %sylius_admin.path_name% statt eines festen Segments admin, damit sie bei einem geänderten Admin-Pfad weiter innerhalb von Firewall und Zugriffsregel liegen. Der Menüeintrag hängt in der gemeinsamen Gruppe „MMKreativ Plugins“.

Reader

Der Produkt-Reader holt zuerst eine Seite von 500 IDs (Schlüsselvergleich statt OFFSET), dann die Entitäten samt Übersetzungen und, falls Bilder gewünscht sind, Bildern, und leert danach den Entity Manager. Der Kategorie-Reader liest alle Taxons ab Ebene 1 in einer Abfrage in Nested-Set-Reihenfolge und überspringt in PHP den Unterbaum jedes deaktivierten Taxons.