DataHandler basics

Introduction

When you are using DataHandler from your backend applications you need to prepare two arrays of information which contain the instructions to DataHandler (\TYPO3\CMS\Core\DataHandling\DataHandler ) of what actions to perform. They fall into two categories: data and commands.

"Data" is when you want to write information to a database table or create a new record.

"Commands" is when you want to move, copy or delete a record in the system.

The data and commands are created as multidimensional arrays, and to understand the API of DataHandler you need to understand the hierarchy of these two arrays.

Basic usage

EXT:my_extension/Classes/DataHandling/MyClass.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\DataHandling;

use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;

final class MyClass
{
    public function basicUsage(): void
    {
        /** @var DataHandler $dataHandler */
        // Do not inject or reuse the DataHander as it holds state!
        // Do not use `new` as GeneralUtility::makeInstance handles dependencies
        $dataHandler = GeneralUtility::makeInstance(DataHandler::class);

        $cmd = [];
        $data = [];
        $dataHandler->start($data, $cmd);

        // ... do something more ...
    }
}
Copied!

After this initialization you usually want to perform the actual operations by calling one (or both) of these two methods:

$this->dataHandler->process_datamap();
$this->dataHandler->process_cmdmap();
Copied!

Commands array

Syntax:

$cmd[ tablename ][ uid ][ command ] = value
Copied!

Description of keywords in syntax:

tablename

tablename
Data type
string

Name of the database table. It must be configured in the $GLOBALS['TCA'] array, otherwise it cannot be processed.

uid

uid
Data type
integer

The UID of the record that is manipulated. This is always an integer.

command

command
Data type
string (command keyword)

The command type you want to execute.

See command keywords and values

value

value
Data type
mixed

The value for the command.

See command keywords and values

Command keywords and values

copy

copy
Data type
integer or array

The significance of the value depends on whether it is positive or negative:

Positive value
The value points to a page UID. A copy of the record (and possibly child elements/tree below) will be inserted inside that page as the first element.
Negative value
The (absolute) value points to another record from the same table as the record being copied. The new record will be inserted on the same page as that record and if $GLOBALS['TCA'][$table]['ctrl']['sortby'] is set, then it will be positioned after.
Zero value
Record is inserted on tree root level.
array

The array has to contain the integer value as in examples above and may contain field => value pairs for updates. The array is structured like:

[
    'action' => 'paste', // 'paste' is used for both move and copy commands
    'target' => $pUid,   // Defines the page to insert the record, or record uid to copy after
    'update' => $update, // Array with field => value to be updated.
]
Copied!

move

move
DataType
integer

Works like copy but moves the record instead of making a copy.

delete

delete
Data Type
integer (1)

Value should always be "1".

This action will delete the record (or mark the record "deleted", if configured in $GLOBALS['TCA'][$table]['ctrl']['delete']).

undelete

undelete
Data Type
integer (1)

Value should always be "1".

This action will set the "deleted" flag back to 0.

localize

localize
Data type
integer

The value is the languageId (defined in the site configuation) to localize the record into. Basically a localization of a record is making a copy of the record (possibly excluding certain fields defined with l10n_mode) but changing relevant fields to point to the right language ID.

Requirements for a successful localization is this:

  • [ctrl] options languageField and transOrigPointerField must be defined for the table
  • A languageId must be configured in the site configuration.
  • The record to be localized by currently be set to default language and not have any value set for the TCA transOrigPointerField either.
  • There cannot exist another localization to the given language for the record (looking in the original record PID).

Apart from this, ordinary permissions apply as if the user wants to make a copy of the record on the same page.

The localize DataHandler command should be used when translating records in "connected mode" (strict translation of records from the default language). This command is used when selecting the "Translate" strategy in the content elements translation wizard.

copyToLanguage

copyToLanguage
Data type
integer

It behaves like localize command (both record and child records are copied to given language), but does not set transOrigPointerField fields (for example, l10n_parent).

The copyToLanguage command should be used when localizing records in the "free mode". This command is used when localizing content elements using translation wizard's "Copy" strategy.

inlineLocalizeSynchronize

inlineLocalizeSynchronize
Data type
array

Performs localization or synchronization of child records. The command structure is like:

$cmd['tt_content'][13]['inlineLocalizeSynchronize'] = [ // 13 is a parent record uid
    'field' => 'tx_myfieldname', // field we want to synchronize
    'language' => 2,             // uid of the target language
    // either the key 'action' or 'ids' must be set
    'action' => 'localize'       // or 'synchronize'
    'ids' =>  [1, 2, 3]          // array of child IDs to be localized
]
Copied!

version

version
Data type
array

Versioning action.

Keys:

[action]

Keyword determining the versioning action. Options are:

"new"

Indicates that a new version of the record should be created. Additional keys, specific for "new" action:

[treeLevels]

(Only pages) Integer, -1 to 4, indicating the number of levels of the page tree to version together with a page. This is also referred to as the versioning type:

  • -1 ("element") means only the page record gets versioned (default)
  • 0 ("page") means the page + content tables (defined by ctrl flag versioning_followPages )
  • >0 ("branch") means the the whole branch is versioned (full copy of all tables), down to the level indicated by the value (1 = 1 level down, 2 = 2 levels down, etc.). The treeLevel is recorded in the field t3ver_swapmode and will be observed when the record is swapped during publishing.
[label]
Indicates the version label to apply. If not given, a standard label including version number and date is added.
"swap"

Indicates that the current online version should be swapped with another. Additional keys, specific for "swap" action:

[swapWith]
Indicates the uid of the record to swap current version with!
[swapIntoWS]
Boolean, indicates that when a version is published it should be swapped into the workspace of the offline record.
"clearWSID"
Indicates that the workspace of the record should be set to zero (0). This removes versions out of workspaces without publishing them.
"flush"
Completely deletes a version without publishing it.
"setStage"

Sets the stage of an element. Special feature: The id key in the array can be a comma-separated list of ids in order to perform the stageChange over a number of records. Also, the internal variable ->generalComment (also available through `/record/commit` route as `&generalComment`) can be used to set a default comment for all stage changes of an instance of the data handler. Additional keys for this action are:

[stageId]

Values are:

  • -1 (rejected)
  • 0 (editing, default)
  • 1 (review),
  • 10 (publish)
[comment]
Comment string that goes into the log.

Examples of commands

EXT:my_extension/Classes/DataHandling/MyClass.php
$cmd['tt_content'][54]['delete'] = 1;    // Deletes tt_content record with uid=54
$cmd['tt_content'][1203]['copy'] = -303; // Copies tt_content uid=1203 to the position after tt_content uid=303 (new record will have the same pid as tt_content uid=1203)
$cmd['tt_content'][1203]['copy'] = 400;  // Copies tt_content uid=1203 to first position in page uid=400
$cmd['tt_content'][1203]['move'] = 400;  // Moves tt_content uid=1203 to the first position in page uid=400
Copied!

Accessing the uid of copied records

The DataHandler keeps track of records created by copy operations in its $copyMappingArray_merged property. This property is public but marked as @internal. So it is subject to change in future TYPO3 versions without notice.

The $copyMappingArray_merged property can be used to determine the UID of a record copy based on the UID of the copied record.

The structure of the $copyMappingArray_merged property looks like this:

EXT:my_extension/Classes/DataHandling/MyClass.php
$copyMappingArray_merged = [
   <table> => [
      <original-record-uid> => <record-copy-uid>,
   ],
];
Copied!

The property contains the names of the manipulated tables as keys and a map of original record UIDs and UIDs of record copies as values.

EXT:my_extension/Classes/DataHandling/MyClass.php
$cmd['tt_content'][1203]['copy'] = 400;  // Copies tt_content uid=1203 to first position in page uid=400
$this->dataHandler->start([], $cmd);
$this->dataHandler->process_cmdmap();

$uid = $this->dataHandler->copyMappingArray_merged['tt_content'][1203];
Copied!

Data array

Syntax: $data['<tablename>'][<uid>]['<fieldname>'] = 'value'

Description of keywords in syntax:

tablename

tablename
Data type
string

Name of the database table. There must be a configuration for the table in $GLOBALS['TCA'] array, otherwise it cannot be processed.

uid

uid
Data type
string|int

The UID of the record that is modified. If the record already exists, this is an integer.

If you are creating new records, use a random string prefixed with NEW, for example, NEW7342abc5e6d. You can use static strings (NEW1, NEW2, ...) or generate them using \TYPO3\CMS\Core\Utility\StringUtility::getUniqueId('NEW').

fieldname

fieldname
Data type
string

Name of the database field you want to set a value for. The columns of the table must be configured in $GLOBALS['TCA'][$table]['columns'].

value

value
Data type
string

Value for "fieldname".

For fields of type inline this is a comma-separated list of UIDs of referenced records.

Examples of data submission

This creates a new page titled "The page title" as the first page inside page id 45:

EXT:my_extension/Classes/DataHandling/MyClass.php
$data['pages']['NEW9823be87'] = [
    'title' => 'The page title',
    'subtitle' => 'Other title stuff',
    'pid' => '45'
];
Copied!

This creates a new page titled "The page title" right after page id 45 in the tree:

EXT:my_extension/Classes/DataHandling/MyClass.php
$data['pages']['NEW9823be87'] = [
    'title' => 'The page title',
    'subtitle' => 'Other title stuff',
    'pid' => '-45'
];
Copied!

This creates two new pages right after each other, located right after the page id 45:

EXT:my_extension/Classes/DataHandling/MyClass.php
$data['pages']['NEW9823be87'] = [
    'title' => 'Page 1',
    'pid' => '-45'
];
$data['pages']['NEWbe68s587'] = [
    'title' => 'Page 2',
    'pid' => '-NEW9823be87'
];
Copied!

Notice how the second "pid" value points to the "NEW..." id placeholder of the first record. This works because the new id of the first record can be accessed by the second record. However it works only when the order in the array is as above since the processing happens in that order!

This creates a new content record with references to existing and one new system category:

EXT:my_extension/Classes/DataHandling/MyClass.php
$data['sys_category']['NEW9823be87'] = [
    'title' => 'New category',
    'pid' => 1,
];
$data['tt_content']['NEWbe68s587'] = [
    'header' => 'Look ma, categories!',
    'pid' => 45,
    'categories' => [
        1,
        2,
        'NEW9823be87', // You can also use placeholders here
    ],
];
Copied!

This updates the page with uid=9834 to a new title, "New title for this page", and no_cache checked:

EXT:my_extension/Classes/DataHandling/MyClass.php
$data['pages'][9834] = [
    'title' => 'New title for this page',
    'no_cache' => '1'
];
Copied!

Clear cache

DataHandler also has an API for clearing the cache tables of TYPO3:

EXT:my_extension/Classes/DataHandling/MyClass.php
$this->dataHandler->clear_cacheCmd($cacheCmd);
Copied!

Values for the $cacheCmd argument:

[integer]

[integer]

Clear the cache for the page ID given.

"all"

"all"

Clears all cache tables (cache_pages, cache_pagesection, cache_hash).

Only available for admin-users unless explicitly allowed by User TSconfig "options.clearCache.all".

"pages"

"pages"

Clears all pages from cache_pages.

Only available for admin-users unless explicitly allowed by User TSconfig "options.clearCache.pages".

Clear cache using cache tags

Every processing of data or commands is finalized with flushing a few caches in the pages group. Cache tags are used to specifically flush the the relevant cache entries instead of the cache as whole.

By default the following cache tags are flushed:

  • The table name of the updated record, for example, pages when updating a page or tx_myextension_mytable when updating a record of this table.
  • A combination of table name and record UID, for example, pages_10 when updating the page with UID 10 or tx_myextension_mytable_20 when updating the record with UID 20 of this table.
  • A page UID prefixed with pageID_ (pageId_<page-uid>), for example, pageId_10 when updating a page with UID 10 (additionally all related pages, see clearcache-pagegrandparent and clearcache-pagesiblingchildren) and pageId_10 when updating a record if a record of any table placed on the page with UID 10 (<table>.pid = 10) is updated.

Notice that you can also use the TypoScriptFrontendController->addCacheTags() method to register additional tags for the cache entry of the current page while it is rendered. This way you can implement an elaborate caching behavior which ensures that every record update in the TYPO3 backend (which is processed by the DataHandler) automatically flushes the cache of all pages where that record is displayed.

Following the rules mentioned above you could register cache tags from within your Extbase plugin (for example, controller or a custom ViewHelper):

EXT:my_extension/Classes/Controller/SomeController.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Model\ExampleModel;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;

final class SomeController extends ActionController
{
    public function showAction(ExampleModel $example): ResponseInterface
    {
        // ...

        /** @var TypoScriptFrontendController $frontendController */
        $frontendController = $this->request->getAttribute('frontend.controller');
        $frontendController->addCacheTags([
            sprintf('tx_myextension_example_%d', $example->getUid()),
        ]);

        // ...
    }
}
Copied!

Hook for cache post-processing

You can configure cache post-processing with a user defined PHP function. Configuration of the hook can be done from ext_localconf.php. An example might look like:

EXT:my_extension/ext_localconf.php
<?php

declare(strict_types=1);

use Vendor\SomeExtension\Hook\DataHandlerHook;

defined('TYPO3') or die();

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'][] =
    DataHandlerHook::class . '->postProcessClearCache';
Copied!

Flags in DataHandler

There are a few internal variables you can set prior to executing commands or data submission. These are the most significant:

->copyTree

->copyTree
Data type
integer
Default
0

Sets the number of branches on a page tree to copy.

0
Branch is not copied
1
Pages on the first level are copied.
2
Pages on the second level are copied.

And so on.

->reverseOrder

->reverseOrder
Data type
boolean
Default
false

If set, the data array is reversed in the order, which is a nice thing if you are creating a whole bunch of new records.

->copyWhichTables

->copyWhichTables
Data type
list of strings (tables)
Default
"*"

This list of tables decides which tables will be copied. If empty then none will. If "*" then all will (that the user has permission to, of course).