Sylius SEO Suite Pro: Erweiterbarkeit
Erweiterungspunkte des Sylius-SEO-Plugins: sechs Service-Tags mit Interfaces und Beispielen, Sitemap-Anbindung, Opt-in der Übersetzungs-Klassen, Twig Hooks, Twig-Funktionen und interne Klassen ohne Erweiterungs-API.
Sylius SEO Suite Pro: Erweiterbarkeit
Diese Seite beschreibt alle Stellen, an denen Du das Plugin um eigene Logik erweiterst: sechs Service-Tags mit Interfaces, die Sitemap-Anbindung, den Opt-in der Übersetzungs-Klassen, die Twig Hooks und Twig-Funktionen. Am Ende steht, welche Klassen interne Details sind und nicht zur Erweiterungs-API gehören.
Inhalt
- Grundprinzip: Interface implementieren, fertig
- Priorität festlegen
- Schema-Provider
- Product-Schema-Enricher
- hreflang-Provider
- Audit-Check
- OG-Bild-Provider
- Platzhalter-Provider
- Sitemap-Anbindung
- Opt-in der Übersetzungs-Klassen
- Twig Hooks
- Twig-Funktionen
- Stabilität und interne Klassen
Grundprinzip: Interface implementieren, fertig
Jeder der sechs Erweiterungspunkte ist ein Interface. Jeder Service, der es implementiert, bekommt das passende Service-Tag automatisch (Autoconfiguration). Du trägst kein Tag von Hand ein. Voraussetzung ist, dass die Klasse als Service registriert ist und autoconfigure aktiv ist, was im Standard-Setup von Symfony mit services: _defaults: autoconfigure: true für src/ der Fall ist.
| Erweiterungspunkt | Interface | Service-Tag |
|---|---|---|
| Schema.org-Typ | Mmd\SyliusSeoSuiteProPlugin\Schema\SchemaProviderInterface |
mmd_sylius_seo_suite_pro.schema_provider |
| Felder im Product-Knoten | Mmd\SyliusSeoSuiteProPlugin\Schema\ProductSchemaEnricherInterface |
mmd_sylius_seo_suite_pro.product_schema_enricher |
| hreflang für eigene Seitentypen | Mmd\SyliusSeoSuiteProPlugin\Hreflang\HreflangAlternatesProviderInterface |
mmd_sylius_seo_suite_pro.hreflang_provider |
| Eigener Audit-Check | Mmd\SyliusSeoSuiteProPlugin\Audit\AuditCheckInterface |
mmd_sylius_seo_suite_pro.audit_check |
| OG-Bild für eigene Objekttypen | Mmd\SyliusSeoSuiteProPlugin\OgImage\OgImageProviderInterface |
mmd_sylius_seo_suite_pro.og_image_provider |
| Eigene Platzhalter für SEO-Templates | Mmd\SyliusSeoSuiteProPlugin\Seo\Template\PlaceholderProviderInterface |
mmd_sylius_seo_suite_pro.placeholder_provider |
Die eingebauten Implementierungen laufen über dieselben Tags. Deine Klasse steht also gleichberechtigt neben ihnen, und die Priorität entscheidet über die Reihenfolge.
Nach dem Anlegen einer neuen Klasse einmal
bin/console cache:clearausführen, damit der Container sie aufnimmt.
Priorität festlegen
Der Standardwert ist 0. Höhere Zahlen kommen zuerst. Wie Du eine Priorität setzt, hängt davon ab, wie der Service registriert ist.
Attribut (empfohlen)
Mit #[AsTaggedItem] setzt Du die Priorität an der Klasse. Das Tag bleibt autoconfigured, es gibt also nur einen Eintrag im Container.
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
#[AsTaggedItem(priority: 50)]
final class MyProvider implements SchemaProviderInterface
{
// ...
}
Explizites Tag
Wenn Du den Service in config/services.yaml selbst konfigurierst, schalte autoconfigure für ihn ab. Sonst trägt er das Tag zweimal (einmal automatisch ohne Priorität, einmal von Hand mit).
services:
App\Seo\MyProvider:
autoconfigure: false
tags:
- { name: 'mmd_sylius_seo_suite_pro.schema_provider', priority: 50 }
Ohne Priorität wird der Service mit dem Standardwert 0 einsortiert. Bei gleicher Priorität entscheidet die Reihenfolge der Registrierung.
Schema-Provider
Ein Schema-Provider erzeugt einen schema.org-Knoten für das JSON-LD der Seite, zum Beispiel FAQPage oder LocalBusiness. Die eingebauten Provider (Product, BreadcrumbList, Organization, WebSite) laufen über denselben Tag. Grundlagen dazu auf der Seite Strukturierte Daten.
interface SchemaProviderInterface
{
public function supports(SchemaContext $context): bool;
/**
* @return array<string, mixed>
*/
public function provide(SchemaContext $context): array;
}
SchemaContext hat die Eigenschaften subject (Produkt oder Taxon, auf der Startseite null), homepage (bool), locale, channel und meta (die bereits aufgelösten MetaData mit Titel, Beschreibung und Canonical). Ein leeres Array aus provide() bedeutet: nichts ausgeben.
<?php
declare(strict_types=1);
namespace App\Seo;
use Mmd\SyliusSeoSuiteProPlugin\Schema\SchemaContext;
use Mmd\SyliusSeoSuiteProPlugin\Schema\SchemaProviderInterface;
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
#[AsTaggedItem(priority: 3)]
final class LocalBusinessSchemaProvider implements SchemaProviderInterface
{
public function supports(SchemaContext $context): bool
{
return $context->homepage;
}
public function provide(SchemaContext $context): array
{
return [
'@context' => 'https://schema.org',
'@type' => 'LocalBusiness',
'name' => (string) $context->channel->getName(),
'url' => $context->meta->canonicalUrl,
];
}
}
Reihenfolge: Höhere Priorität steht im JSON-LD weiter vorn. Eingebaut sind Product 30, BreadcrumbList 20, Organization 10 (nur Startseite) und WebSite 5 (nur Startseite). Wirft ein Provider eine Exception, wird sie geloggt und der Provider übersprungen. Die übrigen Knoten bleiben erhalten.
Jede Eigenschaft, die ein Provider ausgibt, braucht ein sichtbares Gegenstück auf der Seite. Strukturierte Daten, die Besucher nicht sehen, ignorieren Suchmaschinen, und erfundene Bewertungen können eine manuelle Maßnahme nach sich ziehen. Das Plugin erzwingt das nicht.
Product-Schema-Enricher
Ein Enricher ergänzt Felder im bestehenden Product-Knoten, statt einen eigenen Knoten zu bauen. Typisch ist eine GTIN pro Variante oder ein Energielabel.
interface ProductSchemaEnricherInterface
{
/**
* @param array<string, mixed> $node the Product node built so far
*
* @return array<string, mixed> the node to emit (the unchanged node when there is nothing to add)
*/
public function enrich(array $node, ProductInterface $product, SchemaContext $context): array;
}
<?php
declare(strict_types=1);
namespace App\Seo;
use Mmd\SyliusSeoSuiteProPlugin\Schema\ProductSchemaEnricherInterface;
use Mmd\SyliusSeoSuiteProPlugin\Schema\SchemaContext;
use Sylius\Component\Core\Model\ProductInterface;
final class ItemConditionEnricher implements ProductSchemaEnricherInterface
{
public function enrich(array $node, ProductInterface $product, SchemaContext $context): array
{
$node['itemCondition'] = 'https://schema.org/NewCondition';
return $node;
}
}
Reihenfolge: Höhere Priorität läuft zuerst, ein späterer Enricher sieht das Ergebnis der früheren und darf es ändern. Der eingebaute Enricher für Attribute (GTIN, MPN, Marke, additionalProperty) hat die Priorität 0. Wirft ein Enricher, wird er geloggt und übersprungen, der Knoten bleibt, wie er davor war.
hreflang-Provider
Ein hreflang-Provider sagt nur, in welchen Sprachen eines Channels eine Seite existiert. Gruppierung der Channels, doppelte Werte, x-default und der Hostname kommen vom Builder. Siehe auch hreflang.
interface HreflangAlternatesProviderInterface
{
public function supports(HreflangContext $context): bool;
/**
* @return list<HreflangTarget>
*/
public function targets(HreflangContext $context, ChannelInterface $channel): array;
}
HreflangContext hat subject (auf der Startseite null), homepage, channel und locale. Ein HreflangTarget besteht aus locale, route und parameters (die Parameter der Route, einschließlich _locale); der Builder erzeugt daraus die absolute URL auf dem Host des Channels. Gib nur Sprachen zurück, für die es eine nutzbare Seite gibt, nie einen Eintrag mit leerem Slug.
Eingebaut sind Provider für Produkt, Kategorie und Startseite. Der SeoDataProvider ruft den Builder nur für diese drei Seitentypen auf. Ein eigener Seitentyp muss den Builder selbst anfragen (Service mmd_sylius_seo_suite_pro.hreflang_alternates_builder).
<?php
declare(strict_types=1);
namespace App\Seo;
use App\Entity\BrandInterface;
use Mmd\SyliusSeoSuiteProPlugin\Hreflang\HreflangAlternatesProviderInterface;
use Mmd\SyliusSeoSuiteProPlugin\Hreflang\HreflangContext;
use Mmd\SyliusSeoSuiteProPlugin\Hreflang\HreflangTarget;
use Sylius\Component\Core\Model\ChannelInterface;
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
#[AsTaggedItem(priority: 10)]
final class BrandHreflangProvider implements HreflangAlternatesProviderInterface
{
public function supports(HreflangContext $context): bool
{
return $context->subject instanceof BrandInterface;
}
public function targets(HreflangContext $context, ChannelInterface $channel): array
{
$brand = $context->subject;
$targets = [];
foreach ($channel->getLocales() as $locale) {
$slug = $brand->getSlug((string) $locale->getCode());
if (null !== $slug) {
$targets[] = new HreflangTarget((string) $locale->getCode(), 'app_brand_show', ['slug' => $slug, '_locale' => (string) $locale->getCode()]);
}
}
return $targets;
}
}
BrandInterface, getSlug() und die Route app_brand_show sind Platzhalter für Deine eigenen Klassen.
Reihenfolge: Es gilt der erste Provider (höchste Priorität zuerst), dessen supports() zutrifft, alle anderen werden nicht gefragt. Die eingebauten Provider haben die Priorität 0. Mit einer Priorität über 0 kommt Dein Provider vor ihnen an die Reihe. Anders als bei Schema-Providern wird hier kein einzelner Provider übersprungen: Wirft etwas beim Aufbau der Alternativen, loggt das Plugin den Fehler und die Seite erscheint ganz ohne hreflang-Tags.
Audit-Check
Ein Check liefert Befunde zu einem einzigen Code und läuft mit jedem Audit-Lauf. Für Checks, die pro Objekt entscheiden, erbst Du von AbstractAuditCheck, das start() und finish() leer implementiert. Siehe auch Audit.
interface AuditCheckInterface
{
/** Kleinbuchstaben, Ziffern und Unterstriche, höchstens 64 Zeichen. */
public function code(): string;
public function start(string $channelCode): void;
/** @return list<AuditFinding> */
public function inspect(AuditSubject $subject): array;
/** @return list<AuditFinding> */
public function finish(): array;
}
Der Runner ruft start() einmal pro Lauf und Channel, inspect() für jedes Produkt und jede Kategorie in jeder Sprache des Channels und finish() am Ende. Ein Check, der alle Objekte kennen muss (Duplikate), sammelt in inspect() und meldet in finish(). Das Check-Objekt wird über mehrere Läufe wiederverwendet, start() muss also alten Zustand verwerfen.
AuditSubject hat type, id, code, locale, title, description, titleFromName, noIndex und hasImage (null bei Kategorien). AuditFinding::forSubject($subject, $checkCode, $severity, $detail) baut einen Befund. Die Schweregrade sind AuditIssueInterface::SEVERITY_ERROR, SEVERITY_WARNING und SEVERITY_INFO. Das detail ist ein kurzer, sprachneutraler Wert (höchstens 255 Zeichen).
<?php
declare(strict_types=1);
namespace App\Seo;
use Mmd\SyliusSeoSuiteProPlugin\Audit\AbstractAuditCheck;
use Mmd\SyliusSeoSuiteProPlugin\Audit\AuditFinding;
use Mmd\SyliusSeoSuiteProPlugin\Audit\AuditSubject;
use Mmd\SyliusSeoSuiteProPlugin\Entity\AuditIssueInterface;
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
#[AsTaggedItem(priority: 5)]
final class DescriptionTooShortCheck extends AbstractAuditCheck
{
public function code(): string
{
return 'description_too_short';
}
public function inspect(AuditSubject $subject): array
{
$length = mb_strlen($subject->description);
if ($length > 0 && $length < 50) {
return [AuditFinding::forSubject($subject, $this->code(), AuditIssueInterface::SEVERITY_INFO, (string) $length)];
}
return [];
}
}
Für die Anzeige im Admin legst Du zwei Übersetzungen in Deiner translations/messages.<locale>.yaml an. Ohne sie zeigt der Admin den Code und den rohen Detailwert.
mmd_sylius_seo_suite_pro:
audit:
check:
description_too_short: 'Beschreibung zu kurz'
detail:
description_too_short: '%detail% Zeichen'
Reihenfolge: Höhere Priorität läuft zuerst. Eingebaut sind title_missing 80, title_too_long 70, title_duplicate 60, description_missing 50, description_too_long 40, description_duplicate 30, product_without_image 20 und noindex 10. Anders als bei Providern gibt es für Checks kein Überspringen: Wirft ein Check, bricht der Lauf des Channels ab, der Fehler wird geloggt und der Lauf als fehlgeschlagen markiert.
OG-Bild-Provider
Ein OG-Bild-Provider macht einen weiteren Objekttyp (Marken, CMS-Seiten) für generierte Social Cards verfügbar. Eingebaut sind Produkt und Kategorie. Siehe auch OG-Bilder.
interface OgImageProviderInterface
{
public function getType(): string;
public function supports(object $subject): bool;
public function isEnabled(OgImageSettingsInterface $settings): bool;
public function identify(object $subject): ?string;
public function find(string $code, ChannelInterface $channel): ?object;
public function describe(object $subject, OgImageContext $context): ?OgImageSpec;
public function fallbackImageUrl(object $subject): ?string;
/** @return iterable<list<string>> */
public function codes(ChannelInterface $channel, int $pageSize): iterable;
}
| Methode | Aufgabe |
|---|---|
getType() |
Typ-Segment der Karten-URL und des Cache-Pfads: Kleinbuchstaben, Ziffern, Unterstriche, beginnt mit einem Buchstaben (product, taxon) |
supports() |
Gehört das Objekt zu diesem Provider? |
isEnabled() |
Schaltet der Channel Karten dieses Typs ein? Ein eigener Typ hat keinen eigenen Schalter in den Einstellungen, sinnvoll ist $settings->isEnabled() |
identify() |
Der stabile Code, über den die Karte adressiert wird (bei Produkten der Produkt-Code); null für Objekte ohne Karte |
find() |
Das Objekt zu einem Code, sonst null |
describe() |
Was auf der Karte steht, als OgImageSpec (Titel, Preistext, Bildquelle, Zusatzzeile). null, wenn das Objekt im Channel keine Seite hat |
fallbackImageUrl() |
Absolute URL des normalen Bildes, wenn keine Karte entstehen kann |
codes() |
Alle Codes des Channels in Seiten zu $pageSize, für das Vorrendern |
describe() läuft, während die Seite gebaut wird. Sie darf deshalb keine Bilddatei lesen, keine URL abrufen und nichts rendern. Als Basis gibt es AbstractOgImageProvider mit den Hilfsmethoden name() und firstImageSource().
<?php
declare(strict_types=1);
namespace App\Seo;
use App\Entity\BrandInterface;
use App\Repository\BrandRepositoryInterface;
use Mmd\SyliusSeoSuiteProPlugin\Entity\OgImageSettingsInterface;
use Mmd\SyliusSeoSuiteProPlugin\OgImage\AbstractOgImageProvider;
use Mmd\SyliusSeoSuiteProPlugin\OgImage\OgImageContext;
use Mmd\SyliusSeoSuiteProPlugin\OgImage\OgImageSpec;
use Sylius\Component\Core\Model\ChannelInterface;
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
#[AsTaggedItem(priority: 5)]
final class BrandOgImageProvider extends AbstractOgImageProvider
{
public function __construct(private BrandRepositoryInterface $brands)
{
}
public function getType(): string
{
return 'brand';
}
public function supports(object $subject): bool
{
return $subject instanceof BrandInterface;
}
public function isEnabled(OgImageSettingsInterface $settings): bool
{
return $settings->isEnabled();
}
public function identify(object $subject): ?string
{
return $subject instanceof BrandInterface ? $subject->getCode() : null;
}
public function find(string $code, ChannelInterface $channel): ?object
{
return $this->brands->findOneBy(['code' => $code]);
}
public function describe(object $subject, OgImageContext $context): ?OgImageSpec
{
return $subject instanceof BrandInterface ? new OgImageSpec($subject->getName()) : null;
}
public function fallbackImageUrl(object $subject): ?string
{
return null;
}
public function codes(ChannelInterface $channel, int $pageSize): iterable
{
yield array_map(static fn (BrandInterface $brand): string => $brand->getCode(), $this->brands->findAll());
}
}
BrandInterface und BrandRepositoryInterface sind Platzhalter für Deine eigenen Klassen. Die Karten-Route und das Vorrendern mit mmd:seo-suite-pro:og-image:generate arbeiten mit jedem registrierten Provider. In den <head> verdrahtet das Plugin die Karte nur für Produkt- und Kategorieseiten. Für Seiten Deines eigenen Typs fragst Du den Planner (Service mmd_sylius_seo_suite_pro.og_image.planner, Interface OgImagePlannerInterface, Methode plan(object $subject, ChannelInterface $channel)) selbst.
Reihenfolge: Bei supports() gewinnt der erste passende Provider in der Reihenfolge der Priorität (Produkt 20, Kategorie 10). Die Auflösung über den Typ in der URL (getType()) findet den ersten Provider mit diesem Typ.
Platzhalter-Provider
Ein Platzhalter-Provider löst eine Familie von {token}-Platzhaltern in SEO-Templates auf. Ein Token wird im Admin genau dann akzeptiert, wenn ein Provider es unterstützt, die Validierung stimmt also immer mit dem Rendering überein. Siehe auch SEO-Templates.
interface PlaceholderProviderInterface
{
/**
* @param string $token the text between the braces, for example `product.name`
* @param string $subjectType `product`, `taxon` or `other`
*/
public function supports(string $token, string $subjectType): bool;
/** Null or a blank string means "no value": the template is then not used at all. */
public function resolve(string $token, PlaceholderContext $context): ?string;
}
PlaceholderContext hat subject, subjectType, channel und locale. Über $context->remember($key, $factory) merkst Du Dir pro Rendering eine teure Abfrage. Gib reinen Text zurück: HTML einer Rich-Text-Quelle entfernst Du selbst (PlainText::fromHtml()), und Du maskierst nichts, das macht der Ausgabekanal.
<?php
declare(strict_types=1);
namespace App\Seo;
use Mmd\SyliusSeoSuiteProPlugin\Entity\SeoTemplateInterface;
use Mmd\SyliusSeoSuiteProPlugin\Seo\Template\PlaceholderContext;
use Mmd\SyliusSeoSuiteProPlugin\Seo\Template\PlaceholderProviderInterface;
use Sylius\Component\Core\Model\ProductInterface;
final class WarrantyPlaceholderProvider implements PlaceholderProviderInterface
{
public function supports(string $token, string $subjectType): bool
{
return 'warranty' === $token && SeoTemplateInterface::SUBJECT_PRODUCT === $subjectType;
}
public function resolve(string $token, PlaceholderContext $context): ?string
{
return $context->subject instanceof ProductInterface ? '2 Jahre Garantie' : null;
}
}
Danach kannst Du {warranty} in einem Produkt-Template verwenden.
Reihenfolge: Der erste Provider, dessen supports() zutrifft, gewinnt, höhere Priorität kommt zuerst. Die eingebauten Provider haben die Priorität 0. Mit einer Priorität über 0 überschreibst Du also einen eingebauten Platzhalter. Ein Platzhalter, der null oder einen leeren String liefert, lässt das ganze Template ausfallen.
Sitemap-Anbindung
Das Plugin baut keine XML-Sitemap. Es bietet nur die Logik, welche Seiten nicht hineingehören: Seiten mit noindex und Seiten, deren Canonical auf eine andere URL zeigt.
interface SitemapExclusionInterface
{
/**
* @param object $subject a product or taxon; anything else is never excluded
*/
public function isExcludedFromSitemap(object $subject, string $locale): bool;
}
Der Service heißt mmd_sylius_seo_suite_pro.sitemap_exclusion und ist öffentlich. Das Interface SitemapExclusionInterface ist als Alias autowirebar. Die Standardimplementierung SitemapExclusion prüft die Übersetzung des Objekts in der angegebenen Sprache: ausgeschlossen ist sie bei robotsIndex = false oder bei einem canonicalPathOverride, der nicht auf die Seite selbst zeigt. Objekte, deren Übersetzung nicht das SeoAwareTranslationInterface implementiert, schließt sie nie aus. Das zweite Interface TranslationExclusionInterface (isTranslationExcluded(TranslationInterface $translation): bool) stellt dieselbe Regel für eine bereits vorliegende Übersetzung bereit; der Alias zeigt auf denselben Service.
Das Plugin hängt von keinem Sitemap-Plugin ab, die Verdrahtung ist Sache des Shops. Das Beispiel dekoriert den Produkt-Reader von mmd/sylius-sitemap-plugin und entfernt die Sprachen, die nicht in die Sitemap gehören. Der Service mmd_sylius_sitemap.product_reader, das Interface ProductReaderInterface::read(ChannelContext $context, bool $withImages): iterable und die Felder von ProductRecord stammen aus dem Code des Sitemap-Plugins.
<?php
declare(strict_types=1);
namespace App\Sitemap;
use Mmd\SyliusSeoSuiteProPlugin\Seo\SitemapExclusionInterface;
use Mmd\SyliusSitemapPlugin\Catalog\ProductReaderInterface;
use Mmd\SyliusSitemapPlugin\Catalog\ProductRecord;
use Mmd\SyliusSitemapPlugin\Sitemap\ChannelContext;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
#[AsDecorator('mmd_sylius_sitemap.product_reader')]
final class SeoAwareProductReader implements ProductReaderInterface
{
public function __construct(
private ProductReaderInterface $inner,
private ProductRepositoryInterface $products,
private ChannelRepositoryInterface $channels,
private SitemapExclusionInterface $exclusion,
) {
}
public function read(ChannelContext $context, bool $withImages): iterable
{
$channel = $this->channels->findOneByCode($context->code);
if (null === $channel) {
yield from $this->inner->read($context, $withImages);
return;
}
foreach ($this->inner->read($context, $withImages) as $record) {
$slugs = [];
foreach ($record->slugs as $locale => $slug) {
$product = $this->products->findOneByChannelAndSlug($channel, $locale, $slug);
if (null === $product || !$this->exclusion->isExcludedFromSitemap($product, $locale)) {
$slugs[$locale] = $slug;
}
}
if ([] !== $slugs) {
yield new ProductRecord($record->updatedAt, $slugs, $record->names, $record->imagePaths);
}
}
}
}
Das kostet pro Produkt und Sprache eine Abfrage, was bei der Sitemap-Erzeugung per Konsolenbefehl nicht ins Gewicht fällt. Dieselbe Idee gilt für den Kategorie-Reader des Sitemap-Plugins.
Das Beispiel ist eine Skizze, die gegen den Quelltext des Sitemap-Plugins abgeglichen ist, aber nicht als fertiger Code mitgeliefert wird. Prüfe sie gegen die Version des Sitemap-Plugins, die Du einsetzt.
Opt-in der Übersetzungs-Klassen
Die SEO-Felder pro Produkt und Kategorie erscheinen nur, wenn die Übersetzungs-Klassen Deiner Anwendung sie mitbringen. Das Plugin kann das nicht von selbst verdrahten, weil die Spalten in Deine Tabellen gehören. Die Anleitung dazu steht auf der Seite Installation.
| Interface und Trait | Felder | Für |
|---|---|---|
Mmd\SyliusSeoSuiteProPlugin\Model\SeoAwareTranslationInterface mit SeoAwareTranslationTrait |
metaTitle, canonicalPathOverride, robotsIndex, robotsFollow |
Produkt- und Taxon-Übersetzung |
Mmd\SyliusSeoSuiteProPlugin\Model\MetaDescriptionAwareTranslationInterface mit MetaDescriptionAwareTranslationTrait |
metaDescription |
Taxon-Übersetzung (beim Produkt gibt es das Feld bereits in Sylius) |
Die Methoden des Interfaces:
interface SeoAwareTranslationInterface
{
public function getMetaTitle(): ?string;
public function setMetaTitle(?string $metaTitle): static;
public function getCanonicalPathOverride(): ?string;
public function setCanonicalPathOverride(?string $canonicalPathOverride): static;
public function isRobotsIndex(): bool;
public function setRobotsIndex(bool $robotsIndex): static;
public function isRobotsFollow(): bool;
public function setRobotsFollow(bool $robotsFollow): static;
}
Die Traits legen die Spalten mit dem Präfix mmd_seo_suite_pro_ an, geben ihnen die Validierung und hängen die Serialisierungsgruppen der Sylius Admin API an (siehe Shop API). Für die Spalten brauchst Du eine Migration in Deiner Anwendung. Standard für robotsIndex und robotsFollow ist true, bestehende Zeilen bleiben also indexierbar. Die Formularfelder im Admin fügt das Plugin nur hinzu, wenn die Klasse das Interface implementiert.
Twig Hooks
Das Plugin arbeitet mit Twig Hooks und überschreibt keine Templates.
Eingehängt in Sylius (die Hook-Namen, an denen das Plugin Inhalte anmeldet):
| Hook | Hookable | Zweck |
|---|---|---|
sylius_shop.base#metatags |
mmd_seo_suite_pro_seo |
Rendert Meta-Tags, Canonical, hreflang, Open Graph, Twitter Cards und JSON-LD im <head> |
sylius_admin.product.create.content.form.sections.translations und sylius_admin.product.update.content.form.sections.translations |
mmd_seo_suite_pro_seo_fields |
SEO-Felder in der Produkt-Übersetzung |
sylius_admin.taxon.create.content.sections.form.translations und sylius_admin.taxon.update.content.sections.form.translations |
mmd_seo_suite_pro_seo_fields, mmd_seo_suite_pro_meta_description |
SEO-Felder und Meta Description in der Taxon-Übersetzung |
Außerdem hängt das Plugin Schaltflächen und Titel-Bausteine in die Listen-Seiten seiner Ressourcen (zum Beispiel sylius_admin.seo_template.index.content.header.title_block.actions).
Vom Plugin bereitgestellt: Zwei Seiten ohne eigene Ressource setzen ihr Admin-Layout aus eigenen Hook-Namen zusammen, an die Du eigene Hookables anhängen kannst.
| Hook-Wurzel | Seite | Unterhooks |
|---|---|---|
mmd_sylius_seo_suite_pro.redirect_import.import |
CSV-Import der Redirects | .content, .content.header, .content.header.title_block |
mmd_sylius_seo_suite_pro.seo_audit.index |
Audit-Dashboard | .content, .content.header, .content.header.title_block |
Die Hookable-Schlüssel des Plugins in fremden Hooks tragen das Präfix mmd_seo_suite_pro_. Verwende für eigene Hookables einen eigenen, möglichst eindeutigen Schlüssel, denn Twig Hooks führen gleichnamige Einträge aus allen Quellen unter einem Schlüssel zusammen.
Twig-Funktionen
| Funktion | Beschreibung |
|---|---|
mmd_seo_suite_pro_seo() |
Gibt die SeoData der aktuellen Seite zurück, oder null auf Seiten, die weder Produkt, Kategorie noch Startseite sind. Das Ergebnis wird pro Anfrage zwischengespeichert. |
mmd_seo_suite_pro_json_ld(node) |
Wandelt einen JSON-LD-Knoten in HTML-sicheres JSON für ein <script type="application/ld+json">. Zeichenfolgen, die das Script-Element vorzeitig beenden würden, werden maskiert. |
Mit mmd_seo_suite_pro_seo() kannst Du die SEO-Daten auch in eigenen Templates verwenden, zum Beispiel für einen eigenen Canonical-Link. SeoData hat die Eigenschaften meta, openGraph, twitterCard, structuredData und alternates.
Die Twig-Funktion mmd_seo_suite_pro_plugin_version() und der Filter mmd_seo_channel_name sind interne Hilfen für das Admin und gehören nicht zur Erweiterungs-API.
Stabilität und interne Klassen
Das Plugin dokumentiert kein formales Versprechen zur Abwärtskompatibilität (BC Promise). Verbindlich sind die Aussagen im Quelltext: Klassen mit der Markierung @internal Not part of the extension API können sich in jedem Minor Release ändern, inklusive ihrer Konstruktoren. Die Interfaces und Modelle aus dieser Seite sind die Erweiterungs-API, auf die Du Dich stützt.
Als @internal markiert und kein Teil der Erweiterungs-API sind:
- die Formular-Typen (
SeoTemplateFormType,RedirectFormType,RedirectImportType,ChannelSettingsFormType,HreflangSettingsFormType,SchemaSettingsFormType,OgImageSettingsFormType) undChannelFilterType - die Admin-Controller für Audit-Dashboard, Audit-Start, Redirect-Import und Redirect-Export
- der
AdminMenuListener - der
RedirectResourceListener, der bei Slug-Änderungen die Redirects anlegt - die Twig-Erweiterungen
AdminVersionExtensionundChannelNameExtension RedirectPath,AuditRunStateundPrefilledRedirectFactory
Methoden, die der Quelltext als @internal for tests kennzeichnet (Rücksetzen interner Merker), sind ebenfalls nicht für den Einsatz in Deinem Code gedacht.
Wenn Du etwas ändern musst, das hier nicht als Erweiterungspunkt steht, dekoriere lieber den passenden Service, statt von einer Klasse zu erben. Konstruktoren der Plugin-Klassen können sich ändern, ohne dass es eine Ankündigung gibt.
Technische Hintergründe zum Aufbau stehen auf der Seite Technik.