Main rendering workflow 

The details of how to control or use only sub-parts of the rendering chain are explained in more detail in the following sections.

Editing a record in the backend - usually in Content > Layout or Content > Records modules - triggers the EditDocumentController from routing definitions in UriBuilder->buildUriFromRoute($moduleIdentifier) and hands over records from the tables which should be edited. This could be an existing record, or it could be a command to create a form for a new record. The EditDocumentController is the main logic that is triggered when an editor switches between records.

The EditDocumentController has two main jobs: triggering the rendering of records via FormEngine; and handing over data from a FormEngine POST to the DataHandler to persist in the database.

The rendering stage of the EditDocumentController follows these steps:

  • Initialize main FormEngine data array with POST or GET data which specifies which record(s) should be be edited.
  • Select group of DataProviders.
  • Trigger FormEngine DataCompiler to enrich the data array with extra data by calling the selected DataProviders group.
  • Hand over DataCompiler result to an entry "render container" in FormEngine and receive back a result array.
  • Convert the raw result array into a FormResult DTO using FormResultFactory .
  • Use FormResultHandler to pass assets such as CSS, JavaScript and labels to the PageRenderer
  • The PageRenderer outputs the compiled result.
Please use '!option handwritten true' to enable handwrittenControllerFormDataCompileraFormDataGroupNodeFactoryEntryContainerControllerControllerFormDataCompilerFormDataCompileraFormDataGroupaFormDataGroupNodeFactoryNodeFactoryEntryContainerEntryContainerInitialize aFormDataGroupPrepare data providersExplode on errorGive initialized data, call compile()Compile single providersReturn updated data arrayReturn full data arrayReceive full data arrayRender self, sub containersReturn result with HTML, CSS, JSReturn result array
Main FormEngine workflow

The controller does two distinct things here. First, it initializes a data array which is enriched by data providers from FormEngine that add all the information needed for the rendering stage. This data array is then passed onto FormEngine rendering to produce a result array containing all the HTML, CSS and JavaScript.

In code, the 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\FormResult;
use TYPO3\CMS\Backend\Form\FormResultFactory;
use TYPO3\CMS\Backend\Form\FormResultHandler;
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 FormResultFactory $formResultFactory,
        private readonly FormResultHandler $formResultHandler,
    ) {}

    /**
     * @throws \TYPO3\CMS\Backend\Form\Exception
     */
    public function someMethod(string $request, string $table, int $theUid, string $command): string
    {
        $formDataCompilerInput = [
            'request' => $request, // the PSR-7 request object
            'tableName' => $table,
            'vanillaUid' => $theUid,
            'command' => $command,
        ];
        $formData = $this->formDataCompiler->compile($formDataCompilerInput, $this->formDataGroup);
        $formData['renderType'] = 'outerWrapContainer';
        /** @var array $rawFormResult */
        $rawFormResult = $this->nodeFactory->create($formData)->render();
        // Convert the raw result array into a FormResult object
        /** @var FormResult $formResult */
        $formResult = $this->formResultFactory->create($rawFormResult);

        // Use FormResultHandler to pass collected assets (JS, CSS, labels) to PageRenderer
        $this->formResultHandler->addAssets($formResult);

        // Form HTML markup is accessible in the FormResult DTO
        return $formResult->html;
    }
}
Copied!

Deprecated since version 14.2

Basically, behind the FormEngine concept is a 2-step process: first create an array to gather all rendering-relevant information, then call the rendering engine using this array to produce output.

This 2-step process has a number of advantages:

  • The data compiler step can be modified by controller to only enrich the array with data relevant to the context. Data providers are encapulated into groups so that individual data providers can be omitted if not relevant in a given scope.
  • Data providing and rendering is split. Controllers can use the rendering stage of FormEngine even if all or parts of the data providers are omitted or their data comes from "elsewhere". Controllers can use the data provider stage of FormEngine and then output the result in an entirely different way than HTML, for example, for a TCA tree by an Ajax call which outputs a JSON array.
  • The functionality behind "data providing" and "rendering" can be modified to promote higher re-use and more flexibility, with only the "data array" as the main communication between them. This will become more obvious in the next sections which show that data providers are a linked list and rendering is a tree.