Registering Services
Note
The options presented below are merely suggestions or examples — for your own work, you should develop the optimal configuration for your needs. More on the topic can be found in the Symfony documentation.
MetaModels comes with many functions that only need to be activated or configured in the backend. However, not every conceivable setting and function can be covered. For individual project tasks, the built-in options may not be sufficient and must be supplemented with custom adjustments.
Various MM and DC_General (DCG) methods are available here to accomplish these tasks in just a few lines.
In particular, the provided events offer a simple way to implement custom logic or hook into the existing logic. An introduction to working with the MetaModels Reference and API is provided e.g. by the CK23 talk by Ingolf Steinhardt.
The following presents various implementation approaches using the PrePersistModelEvent as an example. The event is called by the input mask “just before saving to the DB”, provided that a field value has changed. With this event, entered data can e.g. be manipulated or new data dynamically generated.
Event listeners and other services are registered analogously to Contao hooks.
Note
Requires at least Contao 4.13 and PHP 8
1. Registration via Attribute
Registration via attribute is the simplest implementation option — only the following file needs to be created and the cache cleared.
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}
After clearing the cache, the registration can be verified as follows:
php vendor/bin/contao-console debug:event-dispatcher dc-general.model.pre-persist
The key dc-general.model.pre-persist is defined in the respective class and
can also be used as a parameter in the attribute. If registration was successful,
the marked entry should be found.

If this is not yet the case, running composer install may resolve the issue.
If the executing method is named __invoke, the attribute key can be written
at the class name as in the example — if you want to use a custom method name,
e.g. when multiple methods for different events exist in one class, the attribute
key must be placed on the respective method name.
This approach works in this simple form only if no further events or similar are
registered via services.yml. If this is the case, you can either switch
entirely to registration via services.yml — see item 2 — or add the
following lines to services.yml to enable automatic loading:
1# config/services.yml
2services:
3 _defaults:
4 autowire: true
5 autoconfigure: true
6 public: false
7
8 App\:
9 resource: '../src/*'
2. Registration Without Attribute via services.yml
As an alternative to registration via attribute, the call can be included via
services.yml — especially if you have various settings and do not want to
rely on automatic registration.
The class then looks as follows:
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}
The following entry must also be added to services.yml:
1# config/services.yml
2services:
3 App\EventListener\PrePersistModelEventListener:
4 tags:
5 - { name: kernel.event_listener, event: dc-general.model.pre-persist }
If the method is not named __invoke, the method name must be added to the
tags in services.yml — a priority can also be specified. More at
Symfony.
3. Registration via Attribute with Additional Services
If access to further services is needed in the class, they can be automatically
injected via the constructor.
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. Registration Without Attribute via services.yml with Additional Services
If access to further services is needed in the class, they can be injected via
the constructor by passing the service as an argument in services.yml.
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. All Files in src/ with Namespace App
If you want to keep all files — including e.g. service.yml — compactly in
the src/ folder while still working with the App namespace, you can look
at the example from the CK23 talk by Ingolf Steinhardt
or download the src/ folder for testing and adjust composer.json
accordingly.
Note the entry foo — it is required to work around some “Contao magic” for the namespace…
6. All Files in src/ with Custom Bundles
If you want to work with your own namespace and less Contao/Symfony magic, more
files need to be created in src/. This can be useful e.g. when working with
multiple separate bundles and their namespaces. In that case, additional
subfolders such as src/ProjectOneBundle would be created.
If this is not the case, all files can be placed directly in src/ with a
namespace such as AppBundle.
More on this: coming soon…