Data Container Array
Data Container Arrays (DCAs) are used to describe a Data Container for holding any type of record. The metadata in these DCAs is used by the Contao core engine to determine how to list records, how to render back end forms for these records and how to edit and save each record.
The actual data container can be anything and is implemented using a data container Driver. The three drivers available in the Contao core are Table (used for any database records), File (used for the system configuration for example) and Folder (used for file listings). Each driver also offers specific configuration options, e.g. with the Table driver you are able to define relations to other database tables. All examples in the Contao documentation usually refer to the Table driver, as this is the most common use-case.
The DCA files of all active modules are loaded one after the other (according to the bundle configuration), so that every module can override the existing configuration.
Creating a DCA
To create a DCA you must first decide on a table name. This table name is then used throughout the configuration as well as within other places of the Contao framework (e.g. models).
All table names managed by a DCA always start with the prefix tl_
(which officially
stands for tüdelü). The name of the file containing the DCA configuration needs
to be the same name as the table and must be put in the contao/dca/
folder (see
Contao Configuration & Translations).
Within that file you configure your Data Container Array within the $GLOBALS['TL_DCA']
array. So if your table name is tl_example
, then your DCA will be created
like this:
// contao/dca/tl_example.php
$GLOBALS['TL_DCA']['tl_example'] = [
'config' => […],
'list' => […],
'fields' => […],
'palettes' => […],
];
See the DCA reference for all available configuration possibilities.
Registering callbacks
Registering DCA callbacks is similar to registering to hooks. See the callback reference for all available callbacks in the DCA. Depending on the callback you are subscribing to, your callback will receive a certain sets of arguments and is expected to return certain data - or void.
As of Contao 4.13, there are four different ways of subscribing to a callback. The recommended way is using PHP attributes together with invokable services. Which one you use depends on your setup. For example, if you still need to support PHP 7 you can use annotations. If you still develop callbacks for Contao 4.4 then you still need to use the PHP array configuration.
Tip
Using attributes or annotations means it is only necessary to create one file for the respective adaptation when using Contao’s default
way of automatically registering services under the App\
namespace within the src/
folder.
For a list.label.group
callback for example a callback might look like this:
since 4.13 Contao implements PHP attributes (available since PHP 8) with which you can tag your service to be registered as a callback.
// src/EventListener/DataContainer/ModuleCallbackListener.php
namespace App\EventListener\DataContainer;
use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback;
use Contao\DataContainer;
class ModuleCallbackListener
{
#[AsCallback(table: 'tl_module', target: 'list.label.group', priority: 100)]
public function onGroupCallback(string $group, string $mode, string $field, array $record, DataContainer $dc): string
{
// Do something …
return $group;
}
}
since 4.8 Contao also supports its own annotation formats via the Service Annotation Bundle.
// src/EventListener/DataContainer/ModuleCallbackListener.php
namespace App\EventListener\DataContainer;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\DataContainer;
class ModuleCallbackListener
{
/**
* @Callback(table="tl_module", target="list.label.group", priority=100)
*/
public function onGroupCallback(string $group, string $mode, string $field, array $record, DataContainer $dc): string
{
// Do something …
return $group;
}
}
since 4.5 Since Contao 4.5 callbacks can be registered using the contao.callback
service tag.
# config/services.yaml
services:
App\EventListener\DataContainer\ModuleCallbackListener:
tags:
- { name: contao.callback, table: tl_module, target: list.label.group, method: onGroupCallback, priority: 100 }
The service tag can have the following options:
Option | Type | Description |
---|---|---|
name | string | Must be contao.callback . |
table | string | The name of the data container for which the callback should be registered. |
target | string | Which type of callback should be registered (see reference). |
method | string | Optional: the method name in the service - otherwise infered from the target (e.g. onGroupCallback ). |
priority | integer | Optional: priority of the callback. By default it will be executed before all legacy callbacks according to the loading order of the bundles. Anything with higher than 0 will be executed before legacy callbacks. Anything with lower than 0 will be executed after legacy callbacks. In Contao 5.0 this has changed though so that these callbacks are executed after all legacy callbacks according to the loading order of the bundles, when using a default priority of 0 . |
This is the old way of using DCA callbacks prior to Contao 4.7. The table
tl_module
and target definition list.label.group
translates to a PHP
array configuration in the following way for example:
// contao/dca/tl_module.php
use App\EventListener\DataContainer\ModuleCallbackListener;
$GLOBALS['TL_DCA']['tl_module']['list']['label']['group_callback'] = [
ModuleCallbackListener::class, 'onGroupCallback'
];
Note that you have to add the suffix _callback
to the last key - except for
these callbacks:
fields.field.wizard
fields.field.xlabel
list.sorting.panel_callback.subpanel
Also note that the above example is a singular callback, i.e. it only supports one subscriber. For callbacks that support multiple callback subscribers you will have to append the callable:
// contao/dca/tl_content.php
use App\EventListener\DataContainer\ContentCallbackListener;
$GLOBALS['TL_DCA']['tl_content']['config']['onload_callback'][] = [
ContentCallbackListener::class, 'onLoadCallback'
];
See the reference for which callbacks support multiple subscribers and which do not.
Invokable Services
since 4.9 You can also use invokable classes for your services. If a service is
tagged with contao.callback
and no method name is given, the __invoke
method will
be called automatically. This also means that you can define the service annotation
on the class, instead of a method:
// src/EventListener/DataContainer/ModuleCallbackListener.php
namespace App\EventListener\DataContainer;
use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback;
use Contao\DataContainer;
#[AsCallback(table: 'tl_module', target: 'list.label.group', priority: 100)]
class ModuleCallbackListener
{
public function __invoke(string $group, string $mode, string $field, array $record, DataContainer $dc): string
{
// Do something …
return $group;
}
}
// src/EventListener/DataContainer/ModuleCallbackListener.php
namespace App\EventListener\DataContainer;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\DataContainer;
/**
* @Callback(table="tl_module", target="list.label.group", priority=100)
*/
class ModuleCallbackListener
{
public function __invoke(string $group, string $mode, string $field, array $record, DataContainer $dc): string
{
// Do something …
return $group;
}
}
# config/services.yaml
services:
App\EventListener\DataContainer\ModuleCallbackListener:
tags:
- { name: contao.callback, table: tl_module, target: list.label.group, priority: 100 }
// src/EventListener/DataContainer/ModuleCallbackListener.php
namespace App\EventListener\DataContainer;
use Contao\DataContainer;
class ModuleCallbackListener
{
public function __invoke(string $group, string $mode, string $field, array $record, DataContainer $dc): string
{
// Do something …
return $group;
}
}
// contao/dca/tl_module.php
use App\EventListener\DataContainer\ModuleCallbackListener;
$GLOBALS['TL_DCA']['tl_module']['list']['label']['group_callback'] = [
ModuleCallbackListener::class, '__invoke'
];
// src/EventListener/DataContainer/ModuleCallbackListener.php
namespace App\EventListener\DataContainer;
use Contao\DataContainer;
class ModuleCallbackListener
{
public function __invoke(string $group, string $mode, string $field, array $record, DataContainer $dc): string
{
// Do something …
return $group;
}
}
Custom Drivers
It is possible to create custom DCA drivers by following these requirements:
- The driver’s class must start with
DC_
. 1 - The driver’s class must be available in the global namespace. 1
- The driver must extend from
\Contao\DataContainer
.
Info
1 Since Contao 4.9.17 the driver is not required to be in the global namespace and does not need to be
prefixed with DC_
anymore. Instead you can reference the FQCN of the driver in your DCA:
// contao/tl_example.php
use Vendor\Driver\FoobarDriver;
$GLOBALS['TL_DCA']['tl_example'] = [
'config' => [
'dataContainer' => FoobarDriver::class,
],
];
The driver can implement any of the following interfaces:
\listable
\editable
When creating your own driver it is probably best to just have a look at the existing drivers in order to get an idea on what is possible and how it needs to be done: