Main rendering workflow

This is done by example. The details to steer and how to use only sub-parts of the rendering chain are explained in more detail in the following sections.

Editing a record in the backend - often from within the Page or List module - triggers the EditDocumentController by routing definitions using UriBuilder->buildUriFromRoute($moduleIdentifier) and handing over which record of which table should be edited. This can be an existing record, or it could be a command to create the form for a new record. The EditDocumentController is the main logic triggered whenever an editor changes a record!

The EditDocumentController has two main jobs: Trigger rendering of one or multiple records via FormEngine, and hand over any given data by a FormEngine POST result over to the DataHandler to persist stuff in the database.

The rendering part of the EditDocumentController job splits into these parts:

  • Initialize main FormEngine data array using POST or GET data to specify which specific record(s) should be edited.
  • Select which group of DataProviders should be used.
  • Trigger FormEngine DataCompiler to enrich the initialized data array with further data by calling all data providers specified by selected data provider group.
  • Hand over DataCompiler result to an entry "render container" of FormEngine and receive a result array.
  • Take result array containing HTML, CSS and JavaScript details and put them into FormResultCompiler which hands them over to the PageRenderer.
  • Let the PageRenderer output its compiled result.
Main FormEngine workflow

The controller does two distinct things here: First, it initializes a data array and lets it get enriched by data providers of FormEngine which add all information needed for the rendering part. Then feed this data array to the rendering part of FormEngine to end up with a result array containing all HTML, CSS and JavaScript.

In code, this basic workflow looks like this:

EXT:my_extension/Classes/SomeClass.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension;

use TYPO3\CMS\Backend\Form\FormDataCompiler;
use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
use TYPO3\CMS\Backend\Form\FormResultCompiler;
use TYPO3\CMS\Backend\Form\NodeFactory;

final class SomeClass
{
    public function __construct(
        private readonly TcaDatabaseRecord $formDataGroup,
        private readonly FormDataCompiler $formDataCompiler,
        private readonly NodeFactory $nodeFactory,
        private readonly FormResultCompiler $formResultCompiler,
    ) {}

    /**
     * @throws \TYPO3\CMS\Backend\Form\Exception
     */
    public function someMethod(string $request, string $table, int $theUid, string $command): void
    {
        $formDataCompilerInput = [
            'request' => $request, // the PSR-7 request object
            'tableName' => $table,
            'vanillaUid' => $theUid,
            'command' => $command,
        ];
        $formData = $this->formDataCompiler->compile($formDataCompilerInput, $this->formDataGroup);
        $formData['renderType'] = 'outerWrapContainer';
        $formResult = $this->nodeFactory->create($formData)->render();
        $this->formResultCompiler->mergeResult($formResult);
    }
}
Copied!

Deprecated since version 12.4

Using the FormEngine data provider to render records without passing the current request object is deprecated. Additionally, when instantiating the backend FormEngine-related FormDataCompiler, the constructor argument FormDataGroupInterface should be omitted, the form data group should be provided as second argument to compile() instead. Failing to do so will stop working with TYPO3 v13.

This basically means the main FormEngine concept is a two-fold process: First create an array to gather all render-relevant information, then call the render engine using this array to come up with output.

This two-fold process has a number of advantages:

  • The data compiler step can be regulated by a controller to only enrich with stuff that is needed in any given context. This part is supported by encapsulating single data providers in data groups, single data providers can be omitted if not relevant in given scope.
  • Data providing and rendering is split: Controllers could re-use the rendering part of FormEngine while all or parts of the data providers are omitted, or their data comes from "elsewhere". Furthermore, controllers can re-use the data providing part of FormEngine and output the result in an entirely different way than HTML. The latter is for instance used when FormEngine is triggered for a TCA tree by an Ajax call and thus outputs a JSON array.
  • The code constructs behind "data providing" and "rendering" can be different to allow higher re-use and more flexibility with having the "data array" as main communication base in between. This will become more obvious in the next sections where it is shown that data providers are a linked list, while rendering is a tree.