Skip to main content

Creating Form Components

Most Flux Forms are declared directly in Fluid using the Form ViewHelpers. This has the advantage that the form definition and the template which uses its values are stored together. There are, however, cases where creating the Form in PHP is more practical. A shared collection of fields, a form assembled from configuration or a custom Provider are typical examples.

The PHP objects use the same hierarchy as the ViewHelpers. A Form contains fields and containers, a Sheet groups components on a tab and a Section contains repeatable objects. This means that a form can be moved from Fluid to PHP without changing the basic way its values are stored.

The following small factory creates a form with a single sheet and two fields:

<?php

declare(strict_types=1);

namespace Vendor\Site\Form;

use FluidTYPO3\Flux\Form;
use FluidTYPO3\Flux\Form\Container\Sheet;
use FluidTYPO3\Flux\Form\Field\Input;
use FluidTYPO3\Flux\Form\Field\Text;

final class TeaserFormFactory
{
    public function create(): Form
    {
        $form = Form::create([
            'name' => 'teaser',
            'label' => 'Teaser',
        ]);

        $contentSheet = $form->createContainer(
            Sheet::class,
            'content',
            'Content',
        );
        $contentSheet->createField(
            Input::class,
            'headline',
            'Headline',
        );
        $contentSheet->createField(
            Text::class,
            'text',
            'Text',
        );

        return $form;
    }
}

Form::create() is the root of the definition. createContainer() and createField() receive the component class, the component name and an optional label. The name is the technical identifier used when values are saved and later made available to the template. It should therefore be unique among sibling components and should not be changed after the form has been used for real content. The label is intended for editors and can be translated or changed without affecting stored values.

Containers and fields can be nested in the same way as their Fluid counterparts. For example, a Section can create one or more SectionObject containers, and each object can create its own fields. Keeping the hierarchy shallow where possible makes both the backend form and the resulting variable structure easier to understand.

A Provider can return the Form built by such a factory from its form-building method. Keeping the construction in a separate class is useful when several Providers share components, and also makes the structure easier to test without rendering a complete backend form.

Creating a new component class is only necessary when the existing fields and containers cannot describe the required behaviour. In most cases composing the built-in components is simpler. If a custom class is required, derive it from the closest matching Flux field or container, keep its public configuration small and make sure the generated TYPO3 field configuration is valid for every supported value.

Note: Form objects describe the editing interface and the structure of stored values. Validation and transformation which belong to the application's business rules should remain in dedicated services instead of being hidden in the form factory.