Custom finisher 

Write a custom finisher 

To make your finisher configurable by users in the backend form editor, see here.

Add a new finisher to the form configuration prototype by defining a finishersDefinition. Set the implementationClassName property to your new implementation class.

EXT:my_site_package/Configuration/Form/CustomFormSetup.yaml
prototypes:
  standard:
    finishersDefinition:
      CustomFinisher:
        implementationClassName: 'MyVendor\MySitePackage\Domain\Finishers\CustomFinisher'

Copied!

Register your custom form definition.

Add options to your finisher with the options property. Options are default values which can be overridden in the form definition.

Define default values 

EXT:my_site_package/Configuration/Form/CustomFormSetup.yaml
prototypes:
  standard:
    finishersDefinition:
      CustomFinisher:
        implementationClassName: 'MyVendor\MySitePackage\Domain\Finishers\CustomFinisher'
        options:
          yourCustomOption: 'Ralf'
Copied!

Override options using the form definition 

public/fileadmin/forms/my_form.yaml
identifier: sample-form
label: 'Simple Contact Form'
prototype: standard
type: Form

finishers:
  -
    identifier: CustomFinisher
    options:
      yourCustomOption: 'Björn'

renderables:
  # ...
Copied!

A finisher must implement FinisherInterface and should extend AbstractFinisher . In doing so, in the logic of the finisher the method executeInternal() will be called first.

Accessing finisher options 

If your finisher class extends AbstractFinisher , you can access the option values in the finisher using method parseOption():

$yourCustomOption = $this->parseOption('yourCustomOption');
Copied!

parseOption() looks for 'yourCustomOption' in your form definition.

EXT:my_site_package/Classes/Domain/Finishers/CustomFinisher.yaml
prototypes:
  standard:
    finishersDefinition:
      CustomFinisher:
        implementationClassName: 'MyVendor\MySitePackage\Domain\Finishers\CustomFinisher'
        options:
          yourCustomOption: 'Ralf'
Copied!

If it can't find it, parseOption() checks

  1. for a default value in the prototype configuration,
  2. for $defaultOptions inside your finisher class:

If it doesn't find anything, parseOption() returns null.

If it finds the option, the process checks whether the option value will access FormRuntime values. If the FormRuntime returns a positive result, it is checked whether the option value can access values of preceding finishers. At the end, it translates the finisher options.

Accessing form runtime values 

You can populate finisher options with submitted form values using the parseOption() method. You can access values of the FormRuntime and therefore values in every form element by encapsulating option values with {}. Below, if there is a form element with the identifier 'subject', you can access the value in the finisher configuration:

public/fileadmin/forms/my_form.yaml
identifier: simple-contact-form
label: 'Simple Contact Form'
prototype: standard
type: Form

finishers:
  -
    identifier: Custom
    options:
      yourCustomOption: '{subject}'

renderables:
  -
    identifier: subject
    label: 'Subject'
    type: Text
Copied!
// $yourCustomOption contains the value of the form element with the
// identifier 'subject'
$yourCustomOption = $this->parseOption('yourCustomOption');
Copied!

You can use {__currentTimestamp} as an option value to return the current UNIX timestamp.

Finisher Context 

The FinisherContext class takes care of transferring a finisher context to each finisher. If your finisher class extends AbstractFinisher the finisher context will be available via:

$this->finisherContext
Copied!

The cancel method prevents the execution of successive finishers:

$this->finisherContext->cancel();
Copied!

The method getFormValues returns the submitted form values.

$this->finisherContext->getFormValues();
Copied!

The method getFormRuntime returns the FormRuntime:

$this->finisherContext->getFormRuntime();
Copied!

Share data between finishers 

The method getFinisherVariableProvider returns an object ( FinisherVariableProvider ) which allows you to store data and transfer it to other finishers. The data can be easily accessed programmatically or inside your configuration:

$this->finisherContext->getFinisherVariableProvider();
Copied!

The data is stored in FinisherVariableProvider and is accessed by a user-defined 'finisher identifier' and a custom option value path. The name of the 'finisher identifier' should consist of the name of the finisher without the 'Finisher' appendix. If your finisher class extends AbstractFinisher , the finisher identifier name is stored in the following variable:

$this->shortFinisherIdentifier
Copied!

For example, if the name of your finisher class is 'CustomFinisher', this variable will contain 'Custom'.

There are 4 methods to access and manage data in the FinisherVariableProvider:

  • Add data:

    $this->finisherContext->getFinisherVariableProvider()->add(
        $this->shortFinisherIdentifier,
        'unique.value.identifier',
        $value
    );
    Copied!
  • Get data:

    $this->finisherContext->getFinisherVariableProvider()->get(
        $this->shortFinisherIdentifier,
        'unique.value.identifier',
        'default value'
    );
    Copied!
  • Check the existence of data:

    $this->finisherContext->getFinisherVariableProvider()->exists(
        $this->shortFinisherIdentifier,
        'unique.value.identifier'
    );
    Copied!
  • Delete data:

    $this->finisherContext->getFinisherVariableProvider()->remove(
        $this->shortFinisherIdentifier,
        'unique.value.identifier'
    );
    Copied!

In this way, finishers can access FinisherVariableProvider data programmatically. However, it is also possible to access FinisherVariableProvider data using form configuration.

Assuming that a finisher called 'Custom' adds data to a FinisherVariableProvider:

$this->finisherContext->getFinisherVariableProvider()->add(
    $this->shortFinisherIdentifier,
    'unique.value.identifier',
    'Wouter'
);
Copied!

other finishers can access the value 'Wouter' by setting {Custom.unique.value.identifier} in the form definition file.

public/fileadmin/forms/my_form.yaml
identifier: sample-form
label: 'Simple Contact Form'
prototype: standard
type: Form

finishers:
  -
    identifier: Custom
    options:
      yourCustomOption: 'Frans'

  -
    identifier: SomeOtherStuff
    options:
      someOtherCustomOption: '{Custom.unique.value.identifier}'
Copied!

Add finisher to backend UI 

After registering a new finisher in the yaml form definition file, you can also add it to the backend form editor for your backend users ( formEditor: section below) to work with in the GUI:

EXT:my_site_package/Configuration/Form/CustomFormSetup.yaml
prototypes:
  standard:
    formElementsDefinition:
      Form:
        formEditor:
          editors:
            900:
              # Extend finisher drop down
              selectOptions:
                35:
                  value: 'CustomFinisher'
                  label: 'Custom Finisher'
          propertyCollections:
            finishers:
              # add finisher fields
              25:
                identifier: 'CustomFinisher'
                editors:
                  100:
                    identifier: header
                    templateName: Inspector-CollectionElementHeaderEditor
                    label: "Custom Finisher"
                  # custom field (input, required)
                  110:
                    identifier: 'customField'
                    templateName: 'Inspector-TextEditor'
                    label: 'Custom Field'
                    propertyPath: 'options.customField'
                    propertyValidators:
                      10: 'NotEmpty'
                  # email field
                  120:
                    identifier: 'email'
                    templateName: 'Inspector-TextEditor'
                    label: 'Subscribers email'
                    propertyPath: 'options.email'
                    enableFormelementSelectionButton: true
                    propertyValidators:
                      10: 'NotEmpty'
                      20: 'FormElementIdentifierWithinCurlyBracesInclusive'
                  9999:
                    identifier: removeButton
                    templateName: Inspector-RemoveElementEditor
    finishersDefinition:
      CustomFinisher:
        formEditor:
          iconIdentifier: 'form-finisher'
          label: 'Custom Finisher'
          predefinedDefaults:
            options:
              customField: ''
              email: ''
        # displayed when overriding finisher settings
        FormEngine:
          label: 'Custom Finisher'
          elements:
            customField:
              label: 'Custom Field'
              config:
                type: 'text'
            email:
              label: 'Subscribers email'
              config:
                type: 'text'
Copied!

Configuration registration 

Register the form configuration yaml file in EXT:my_extension/ext_localconf.php:

EXT:my_extension/ext_localconf.php
<?php

declare(strict_types=1);

use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;

defined('TYPO3') or die();

ExtensionManagementUtility::addTypoScriptSetup('
        # for the backend
        module.tx_form.settings.yamlConfigurations {
            123456789 = EXT:yourExtension/Configuration/Form/CustomFormSetup.yaml
        }
        # for the frontend - otherwise the custom finisher class is not found
        # because of the missing "implementationClassName"
        plugin.tx_form.settings.yamlConfigurations {
            123456789 = EXT:yourExtension/Configuration/Form/Backend.yaml
        }
    ');
Copied!

Changed in version 13.0

Registration of global TypoScript for the TYPO3 backend has to be done in an extensions ext_localconf.php using method ExtensionManagementUtility::addTypoScriptSetup.

Former methods for registering global TypoScript are not compatible with site sets.