Registrierung von Services

Bemerkung

Die im Folgenden vorgestellten Möglichkeiten sind lediglich Vorschläge bzw. Beispiele - für die eigene Arbeit sollte man sich seine optimale Konfiguration entwickeln - mehr zu dem Thema ist in der Symfony-Dokumentation zu finden.

MetaModels bringt viele Funktionen mit, die man lediglich im Backend aktivieren oder konfigurieren muss. Dennoch können damit nicht alle erdenklichen Einstellungen und Funktionen abgedeckt werden. Bei individuellen Projektaufgaben können die implementierten Möglichkeiten nicht ausreichen und müssen um eigene Anpassungen ergänzt werden.

Hier stehen verschiedene Methoden von MM oder auch von DC_General (DCG) zur Verfügung, um mit wenigen Zeilen diese Aufgaben umzusetzen.

Insbesondere die zur Verfügung gestellten Events bieten dabei eine einfache Möglichkeit, eine eigene Logik zu implementieren bzw. in die vorhandene einzugreifen. Ein Einstieg in die Arbeit mit der MetaModels Referenz und API bietet z. B. der Vortrag von Ingolf Steinhardt zur CK23

Folgend werden verschiedene Varianten zur Implementierung anhand des PrePersistModelEvents aufgeführt. Das Event wird von der Eingabemaske „kurz vor dem Speichern in die DB“ aufgerufen, sofern sich ein Feldwert geändert hat. Mit dem Event können z. B. eingegebene Daten manipuliert oder neue dynamisch generiert werden.

Die EventListener oder auch andere Services werden analog den Contao-Hooks registriert.

Bemerkung

Vorausgesetzt wird mind. Contao 4.13 und PHP 8

1. Registrierung per Attribut

Die Registrierung per Attribut bietet die einfachste Variante der Implementierung - es muss lediglich folgende Datei angelegt und der Cache geleert werden.

 1<?php
 2// src/EventListener/PrePersistModelEventListener.php
 3namespace App\EventListener;
 4
 5use ContaoCommunityAlliance\DcGeneral\Event\PrePersistModelEvent;
 6use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
 7
 8#[AsEventListener(PrePersistModelEvent::NAME)]
 9class PrePersistModelEventListener
10{
11    public function __invoke(PrePersistModelEvent $event)
12    {
13        if ('mm_employees' !== $event->getEnvironment()->getDataDefinition()?->getName()) {
14            return;
15        }
16
17        $model = $event->getModel();
18    }
19}

Nach dem leeren des Caches kann die Registrierung wie folgt überprüft werden

php vendor/bin/contao-console debug:event-dispatcher dc-general.model.pre-persist

Der Key dc-general.model.pre-persist steht in der jeweiligen Klasse und kann ebenfalls als Parameter in dem Attribut verwendet werden. War die Registrierung erfolgreich, sollte der markierte Eintrag zu finden sein.

img_register-services_01.png

Sollte das noch nicht der Fall sein, kann die Ausführung eines composer install Abhilfe schaffen.

Wenn die ausführende Methode den Namen __invoke hat, kann der Attributsschlüssel wie in dem Beispiel an den Klassennamen geschrieben werden - wenn man einen individuellen Methodennamen einsetzen möchte z. B. wenn mehrere Methoden in einer Klasse zu verschiedenen Events vorhanden sind, muss der Attributsschlüssel an den jeweiligen Methodennamen.

Diese Variante funktioniert in dieser einfachen Form nur, wenn nicht weitere Events o. ä. über die services.yml registiert werden. Ist dies der Fall, kann man entweder ganz auf die Registrierung über die services.yml umsteigen - siehe Punkt 2 - oder man fügt folgende Zeilen in die services.yml, um ein automatisches Laden zu erwirken:

1# config/services.yml
2services:
3  _defaults:
4    autowire: true
5    autoconfigure: true
6    public: false
7
8  App\:
9    resource: '../src/*'

2 Registrierung ohne Attribut per services.yml

Als Alternative zur Registrierung per Attribut kann man den Aufruf über die services.yml einbinden - insbesondere, wenn man verschiedene Einstellungen hat und sich auf die automatische Registrierung nicht verlassen möchte.

Die Klasse sieht dann wie folgt aus:

 1<?php
 2// src/EventListener/PrePersistModelEventListener.php
 3namespace App\EventListener;
 4
 5use ContaoCommunityAlliance\DcGeneral\Event\PrePersistModelEvent;
 6
 7class PrePersistModelEventListener
 8{
 9    public function __invoke(PrePersistModelEvent $event)
10    {
11        if ('mm_employees' !== $event->getEnvironment()->getDataDefinition()?->getName()) {
12            return;
13        }
14
15        $model = $event->getModel();
16    }
17}

Zudem muss in die services.yml folgender Eintrag:

1# config/services.yml
2services:
3  App\EventListener\PrePersistModelEventListener:
4    tags:
5      - { name: kernel.event_listener, event: dc-general.model.pre-persist }

Sofern die Methode nicht die Bezeichnung __invoke hat, muss bei den Tags der services.yml der Methodenname ergänzt werden - zudem ist die Angabe einer Priorität möglich. Mehr dazu bei Symfony

3. Registrierung per Attribut und Einbindung weiterer Services

Benötigt man in seiner Klasse den Zugriff auf weitere Services, kann man die über den constructor automatisch einbinden.

 1<?php
 2// src/EventListener/PrePersistModelEventListener.php
 3namespace App\EventListener;
 4
 5use ContaoCommunityAlliance\DcGeneral\Event\PrePersistModelEvent;
 6use MetaModels\IFactory;
 7use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
 8
 9#[AsEventListener(PrePersistModelEvent::NAME)]
10class PrePersistModelEventListener
11{
12    public function __construct(private readonly IFactory $factory)
13    {
14    }
15
16    public function __invoke(PrePersistModelEvent $event)
17    {
18        if ('mm_employees' !== $event->getEnvironment()->getDataDefinition()?->getName()) {
19            return;
20        }
21
22        $model = $event->getModel();
23
24        $anotherMetaModel = $this->factory->getMetaModel('mm_another_model');
25    }
26}

4. Registrierung ohne Attribut per services.yml und Einbindung weiterer Services

Benötigt man in seiner Klasse den Zugriff auf weitere Services, kann man die über den constructor einbinden, indem man den Service in der services.yml als Argument übergibt.

 1<?php
 2// src/EventListener/PrePersistModelEventListener.php
 3namespace App\EventListener;
 4
 5use ContaoCommunityAlliance\DcGeneral\Event\PrePersistModelEvent;
 6use MetaModels\IFactory;
 7
 8class PrePersistModelEventListener
 9{
10    public function __construct(private readonly IFactory $factory)
11    {
12    }
13
14    public function __invoke(PrePersistModelEvent $event)
15    {
16        if ('mm_employees' !== $event->getEnvironment()->getDataDefinition()?->getName()) {
17            return;
18        }
19
20        $model = $event->getModel();
21
22        $anotherMetaModel = $this->factory->getMetaModel('mm_another_model');
23    }
24}
1# config/services.yml
2services:
3  App\EventListener\PrePersistModelEventListener:
4  arguments:
5    - '@metamodels.factory'
6    tags:
7      - { name: kernel.event_listener, event: dc-general.model.pre-persist }

5. Alle Dateien in src/ und Namespace App

Möchte man zur einfacheren Datenpflege alle Dateien - also auch z. B. die service.yml - kompakt im Ordner src/ haben aber dennoch mit dem Namespace App arbeiten, so kann man sich das Beispiel vom Vortrag von Ingolf Steinhardt zur CK23 ansehen bzw. den Ordner src/ zum Testen downloaden und die composer.json entsprechend anpassen.

Zu beachten ist der Eintrag foo - der ist notwendig um einige „Contao-Magic“ für den Namespace zu umgehen…

6. Alle Dateien in src/ und eigene Bundles

Möchte man mit einem eigenen Namespace arbeiten und weniger Contao- bzw. Symfony-Magic, so müssen einige Dateien mehr in src/ angelegt werden. Das kann z. B. dann sinnvoll sein, wenn man mit mehreren separaten Bundles und ihren Namespaces arbeiten möchte. In dem Fall, würde man weitere Unterordner z. B. src/ProjectOneBundle anlegen.

Ist dies nicht der Fall, können alle Dateien direkt in src/ mit dem Namespace z. B. AppBundle.

Mehr dazu: demnächst…