Contact Requests: Technische Details
Sylius Contact Requests Plugin - Architektur der beiden Mailer-Decorators, Plugin-Struktur, Tabelle und Migration, Verhalten beim Protokollieren von Speicherfehlern
Contact Requests: Technische Details
Live Demo: Teste dieses und weitere Plugins live — ohne Installation, ohne Risiko: demo.markus-michalski.net
Architektur
graph LR
A["Twig-Kontaktformular (Shop)"] --> D["sylius_shop.mailer.contact_email_manager"]
B["Shop API SendContactRequest"] --> E["sylius.mailer.contact_email_manager"]
D --> F["PersistingContactEmailManager (Decorator)"]
E --> F
F --> G["ContactRequest speichern"]
F --> H["Ursprünglicher Manager: E-Mail versenden"]
Sylius bringt für den Versand der Kontaktanfragen zwei voneinander unabhängige Services desselben Mailers (ContactEmailManager, Interface ContactEmailManagerInterface) mit: sylius.mailer.contact_email_manager nutzt der Shop-API-Pfad, sylius_shop.mailer.contact_email_manager das klassische Twig-Kontaktformular, das ShopBundle eigenständig registriert. Das Plugin dekoriert beide:
sylius.mailer.contact_email_managerüberconfig/services/mailer.xml, als normale Service-Dekoration.sylius_shop.mailer.contact_email_managerüber einen eigenen Compiler-Pass (DecorateShopContactEmailManagerPass). InExtension::load()ist nicht garantiert, dassShopBundlediesen Service schon definiert hat. Compiler-Passes laufen erst, nachdem jede Extension geladen wurde.
Der zweite Service ist ein Implementierungsdetail des
ShopBundleund kein offizieller Vertrag. Der Compiler-Pass prüft deshalb mithasDefinition(), ob er existiert. Fehlt er in einer künftigen Sylius-Version, speichert das Plugin nur noch Anfragen über die Shop API, statt den Container-Build scheitern zu lassen.
Ausfallsicheres Speichern
Schlägt das Speichern fehl, schließt Doctrine den Entity Manager (das passiert nach jedem gescheiterten flush()). Der Decorator ruft deshalb ManagerRegistry::resetManager() auf. Sonst würde jeder spätere Schreibzugriff in derselben Anfrage mit „The EntityManager is closed“ scheitern.
Protokolliert werden nur Exception-Klasse und Code, nie getMessage() und nie das Exception-Objekt:
Failed to persist contact request: Doctrine\DBAL\Exception\... (code 0)
Der Grund: Bei Konvertierungsfehlern enthält die Doctrine-Meldung oft den fehlerhaften Feldwert im Klartext, und für dieses Log gibt es keine eigene Aufbewahrungsfrist.
Plugin-Struktur
src/
├── Command/ # PurgeContactRequestsCommand, SetupCommand
├── DependencyInjection/
│ └── Compiler/ # DecorateShopContactEmailManagerPass
├── Entity/ # ContactRequest, ContactRequestInterface
├── Mailer/ # PersistingContactEmailManager (für beide Services)
├── Menu/ # Eintrag in der Admin-Sidebar
├── Migrations/ # Eine Doctrine-Migration
├── Repository/ # ContactRequestRepository (purgeOlderThan)
└── Twig/ # ContactRequestsExtension (Versionsanzeige im Admin)
config/
├── grids/ # Ein Admin-Grid
├── routes/ # admin.yaml (shop.yaml ist leer, es gibt keine eigenen Shop-Routen)
└── twig_hooks/ # Titelzeile und Detailansicht im Admin
templates/
└── admin/contact_request/ # Templates für Titelzeile und Detailansicht
Tabelle und Migration
Eine Tabelle, eine Migration: ContactRequest liegt in mmd_sylius_contact_requests_contact_request (Version20260904121117). Die Spalten sind id, createdAt (mit Index), name (optional), email, subject (optional), message (LONGTEXT) und consent (BOOLEAN, Standard false).
Versionsanzeige im Admin
Die Titelzeile im Admin zeigt die installierte Plugin-Version (mmd_contact_requests_plugin_version(), liest Composer\InstalledVersions). Bei einem getaggten Packeton-Release ist das die Semver-Nummer, bei einer lokalen Pfad-Installation dev-main plus die ersten sieben Zeichen des Commit-Hashes.