Custom finisher
Important
Finishers are executed in the order defined in your form definition.
Table of contents
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
finishers. Set the implementation property to your new implementation class.
prototypes:
standard:
finishersDefinition:
CustomFinisher:
implementationClassName: 'MyVendor\MySitePackage\Domain\Finishers\CustomFinisher'
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
prototypes:
standard:
finishersDefinition:
CustomFinisher:
implementationClassName: 'MyVendor\MySitePackage\Domain\Finishers\CustomFinisher'
options:
yourCustomOption: 'Ralf'
Override options using the form definition
identifier: sample-form
label: 'Simple Contact Form'
prototype: standard
type: Form
finishers:
-
identifier: CustomFinisher
options:
yourCustomOption: 'Björn'
renderables:
# ...
A finisher must implement
Finisher
and should extend
Abstract.
In doing so, in the logic of the
finisher the method execute will be called first.
Accessing finisher options
If your finisher class extends
Abstract,
you can access the option values in the finisher using method parse:
$yourCustomOption = $this->parseOption('yourCustomOption');
parse looks for 'yourCustomOption' in your
form definition.
prototypes:
standard:
finishersDefinition:
CustomFinisher:
implementationClassName: 'MyVendor\MySitePackage\Domain\Finishers\CustomFinisher'
options:
yourCustomOption: 'Ralf'
If it can't find it, parse checks
- for a default value in the
prototypeconfiguration, - for
$defaultinside your finisher class:Options
If it doesn't find anything, parse returns null.
If it finds the option, the process checks whether the option value will
access FormRuntime values.
If the Form 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 parse method.
You can access values of the Form 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:
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
// $yourCustomOption contains the value of the form element with the
// identifier 'subject'
$yourCustomOption = $this->parseOption('yourCustomOption');
You can use {__ as an option value to return the
current UNIX timestamp.
Display values and submitted values
Changed in version 14.3.7 Important: #106903 - Form element value resolution in finishers
Before, the built-in finishers used the submitted value, for example the option key, in all options.
parse replaces {<element with the submitted value of
the form element. For a select, radio button, multi checkbox, or country
select element, this value is the option key, for example mr.
parse replaces the placeholder with the display value
instead. The form element provides this value, for example the translated
option label Mister. Use it for options that are read by a human. Never use
it for options that end up in a query, a stored record, or a URL.
// "Request from Mister" instead of "Request from mr"
$subject = $this->parseOptionAsDisplayValue('subject');
The built-in finishers use display values for the following options:
Email:Finisher subject,title,message, andsenderName Confirmation:Finisher messageFlash:Message Finisher messageandBody messageTitle
All other options of the built-in finishers keep the submitted value. This
includes the email addresses, the redirect targets, and all options of the
Save.
Both methods join an array value inside a longer string with , , for example
the values of a multi select element in Request for .
Accessing uploaded files
New in version 14.2 Feature: #105708 - Multiple file upload for EXT:form elements
Before, File and Image elements accepted only one file.
The value of a File or Image element depends on its
multiple property:
- Single upload: a
FileReference - Multiple upload: an
ObjectofStorage FileobjectsReference
Handle both cases in a custom finisher that processes uploaded files:
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher;
use TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload;
class CustomFinisher extends AbstractFinisher
{
protected function executeInternal(): void
{
$formRuntime = $this->finisherContext->getFormRuntime();
$elements = $formRuntime->getFormDefinition()
->getRenderablesRecursively();
foreach ($elements as $element) {
if (!$element instanceof FileUpload) {
continue;
}
$value = $formRuntime[$element->getIdentifier()];
if ($value instanceof FileReference) {
$this->processFile($value->getOriginalResource());
}
if ($value instanceof ObjectStorage) {
foreach ($value as $fileReference) {
if ($fileReference instanceof FileReference) {
$this->processFile(
$fileReference->getOriginalResource()
);
}
}
}
}
}
private function processFile(FileInterface $file): void
{
// Your custom logic, for example move, copy, or attach the file
}
}
Finisher Context
The
Finisher class takes care of
transferring a finisher context to each finisher. If your finisher class extends
Abstract the
finisher context will be available via:
$this->finisherContext
The cancel method prevents the execution of successive finishers:
$this->finisherContext->cancel();
The method get returns the submitted form values.
$this->finisherContext->getFormValues();
The method get returns the Form:
$this->finisherContext->getFormRuntime();
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 ( form
section below) to work with in the GUI:
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'
Important
Make sure to define an icon in the finishers of your
finisher, otherwise the button to remove the finisher from the
form will not be visible.
Configuration registration
Place your YAML files in a form set directory — no PHP registration needed:
EXT:my_extension/
Configuration/
Form/
MyFinisher/
config.yaml
See also