Das TYPO3 Form-Framework bietet zahlreiche Möglichkeiten und ist gut zu erweitern. Hier stelle ich einen einfachen weg vor, wie ein individueller Finisher Einfluss auf die nachfolgenden nehmen kann.

Ein eigener Form Finisher

Im TYPO3 Form Framework (ext:form) ist ein Finisher eine Klasse, die sich um die Verarbeitung der Daten kümmert, die der Benutzer im Formular eingegeben hat. Zum Zeitpunkt, wenn der Finisher ins Spiel kommt, sind einfache Überprüfungen der Daten, wie z.B. die Validierungen der Felder, bereits abgeschlossen. Die Finisher sind nun dafür zuständig, was mit diesen Daten passieren soll.

Für viele Anwendungsfälle bringt das Form Framework bereits fertige Finisher mit. So kann z.B. eine Bestätigungs-E-Mail an den Benutzer gesendet werden, oder der Seitenbetreiber benachrichtigt oder die Daten in die Datenbank weggespeichert werden. Und auch die Bestätigungsmeldung oder die Weiterleitung auf eine andere Seite wird durch einen Finisher realisiert.

Ein schönes Konzept im Form Framework ist, dass man einzelne Finisher hintereinanderschalten kann. Man muss sich also nicht entscheiden, ob der Anwender eine E-Mail bekommt oder die Daten in die Datenbank geschrieben werden sollen. Die Finisher bleiben aus softwarearchitektonischer Sicht sehr schlank und implementieren genau das, was sie eben tun sollen - z.B. Daten an SAP weitergeben, eine API ansprechen oder was auch immer.

 

Grundsätzlich können die Finisher auch aufeinander aufbauen. So kann z.B. ein Finisher aus den Daten ein PDF erzeugen und ein weiterer Finisher dieses PDF dann als Anhang zu einer E-Mail versenden.

Die Problemstellung

Für ein Kundenprojekt sollte ich unlängst einen Finisher implementieren, der direkt als erster Finisher zum Zug kommt und die Eingaben analysiert. In diesem Beitrag soll es nun nicht darum gehen, was genau dieser Finisher tut und was nicht. Zum Problem gehört aber, dass der Finisher Einfluss auf die nachfolgenden Finisher nehmen sollte - und zwar nicht auf alle, sondern nur auf einzelne. 

Der Einfachheit halber denken wir uns einen AntiSPAM-Finisher, der nach außen hin unsichtbar sein soll. Für den Spammer sieht es also so aus, als hätten seine Versuche Erfolg, aber intern werden keine Datensätze und E-Mails erzeugt. (Ob das jetzt sonderlich sinnvoll ist, sei dahingestellt - für die Erklärung und Lösung des Problems ist das Beispiel passend). 

Workflow der Finisher

Für unsere Seite nehmen wir an, dass folgende Finisher nacheinander abgearbeitet werden: 

  1. AntiSpam-Finisher - prüft, ob die Anfragen SPAM sind
  2. SaveToDB-Finisher - speichert die Anfrage in die Datenbank
  3. EmailFinisher - sendet dem Seitenbetreiber eine E-Mail
  4. Confirmation - zeigt eine "Vielen Dank für Ihre Nachricht" - Meldung.

 

Abhängig davon, ob der AntiSpam-Finisher nun Spam erkannt hat oder nicht, sollen die Schritte 2 + 3 übersprungen werden (oder eben nicht). Schritt 4 soll auf jeden Fall ausgeführt werden. 

Und warum ist das nun schwer?

Die Finisher sind voneinander unabhängig und sie wissen nichts voneinander. Zwar können Finisher z.B. Variablen setzen, auf die andere Finiser zugreifen können - aber wir wollen ja nicht alle Standard-Finisher erweitern, nur um sie mit unserem AntiSpam-Finisher kompatibel zu machen. 

Weiterhin kann jeder Finisher die weitere Verarbeitung abbrechen. Das hat dann allerdings zur Folge, dass kein weiterer Finisher mehr ausgeführt wird. Auch nicht das, was wir wollen. 

 

In media res

Aber stürzen wir uns erstmal rein in den Code. Zunächst einmal brauchen wir einen Finisher, der unsere Logik implementiert. 

Dafür implementieren wir eine Klasse AntiSpamFinisher:

 

<?php

namespace MarcWillmann\Sitepackage\Domain\Finishers;

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher;

class AntispamFinisher extends AbstractFinisher
{
    
    /**
     * @var string
     */
    protected $shortFinisherIdentifier = 'Antispam';

    /**
     * @var mixed[]
     */
    protected array $data = [];

    protected function executeInternal(): void
    {
        // do what ever the finisher has to do
    }

}

 

und machen diese im Form Framwork bekannt: 

 

TYPO3:
  CMS:
    Form:
      prototypes:
        standard:
          finishersDefinition:
            Antispam:
              implementationClassName: MarcWillmann\Sitepackage\Domain\Finishers\AntispamFinisher
              FormEngine:
                label: 'IP Block (Anti spam)'

 

Nun können wir den Finisher schon in unseren Formularen benutzen. Dafür wird der Finisher einfach in das formular.form.yaml eingefügt, z.B. so: 

 

prototypeName: standard
finishers:
  - identifier: Antispam
  - identifier: EmailToReceiver
    options:
      subject: 'neue Bestellung'
      recipientAddress: 'sales@domain.tld'
      recipientName: '1001 TYPO3-Lösungen GmbH & Co. KG'
      senderAddress: 'no-reply@domain.tld'
      senderName: '{firstname} {lastname}'
  -
    options:
      message: 'Vielen Dank für Ihre Nachricht!'
      contentElementUid: ''
    identifier: Confirmation

 

 

Des Rätsels Lösung

An dieser Stelle steckte ich dann einige Zeit fest. Ich habe einige Ansätze versucht und wieder verworfen. Aber keine Sorge, es ist viel einfacher als gedacht: 

Zunächst setzen wir in der AntiSpamFinisher-Klasse eine Finisher-Variable, abhängig davon ob wir SPAM erkannt haben oder nicht: 

 

protected function executeInternal(): void
    {
        // do what ever the finisher has to do

        if ($isSpam) {
            $this->finisherContext->getFinisherVariableProvider()->add(
                $this->shortFinisherIdentifier,
                'enabled',
                false
            );
        } else {
            $this->finisherContext->getFinisherVariableProvider()->add(
                $this->shortFinisherIdentifier,
                'enabled',
                true
             );
    }

 

das schaut auf den ersten Blick ein wenig merkwürdig aus. Aber es wird klarer mit dem kleinen Kniff, der die Lösung vervollständigt. In der formular.form.yaml: 

 

prototypeName: standard
finishers:
  - identifier: Antispam
  - identifier: EmailToReceiver
    options:
      subject: 'neue Bestellung'
      recipientAddress: 'sales@domain.tld'
      recipientName: '1001 TYPO3-Lösungen GmbH & Co. KG'
      senderAddress: 'no-reply@domain.tld'
      senderName: '{firstname} {lastname}'
      renderingOptions:
        enabled: '{Antispam.enabled}'
  -
    options:
      message: 'Vielen Dank für Ihre Nachricht!'
      contentElementUid: ''
    identifier: Confirmation

 

überlassen wir die Entscheidung, ob die einzelnen Finisher ausgeführt werden sollen oder nicht, genau dieser Variable, die wir zu Beginn setzen. Eigentlich ganz simpel, oder? :) 

Kommentare (5)

  • Karsten Soll
    Karsten Soll
    am 07.06.2022
    Hallo Marc,

    ich hätte mal eine Frage an jemanden, der sich offensichtlich damit auskennt :) Ich möchte mit der Typo3 EXT:form die Inhalte eines Formulars an unser CRM senden. Dazu muss eigentlich "nur" die entsprechende URL in das action-Attribut rein - Fertig! Ich kann aber nichts dazu finden, wie man einen eigenen Finisher dahingehend erstellt. - Hättest Du einen Hinweis für mich?

    Gruß
    Karsten
    • Marc Willmann
      Marc Willmann
      am 07.06.2022
      Hallo Karsten,

      die Action-URL des Formulars willst Du eigentlich nicht verändern, weil sonst alle Deine Validatoren, andere Finisher usw. nicht mehr funktionieren und Du auch nicht zurückmelden kannst, welche Pflichtfelder ggf. nicht korrekt ausgefüllt wurden - die gesamten Daten würde dann ja nur das CRM-Formular bekommen.

      Ich würde daher das Form komplett in ext:form lassen, und einen Finisher einbauen, der die gesammelten und validierten Daten (das macht ja alles ext:form) als "Client" an das CRM schickt. Im Prinzip kannst Du den Artikel hier weitestgehend dafür verwenden; in der executeInternal()-Methode nimmst Du dann die Daten, die Du haben willst und schiebst sie via PHP-curl an Dein externes CRM-Formular.

      Falls gewünscht, kannst Du danach noch weitere Finisher nutzen (send Mail, store to DB, ...) oder auch nicht.

      Marc
  • Alex
    Alex
    am 02.09.2022
    Hallo Marc,

    könnte man auf die Weise irgendwie einem Element Textfeld den Eintrag ... list="xyz" hinzufügen?
    Gebe zu, bin da etwas verloren, welche Schritte notwendig wären.
    Auf die Art könnte aber ein Textfeld mit einer Datalist arbeiten.
    Prinzipiell funktioniert das auch, da hab ich mich die Tage durchgebissen. Nur eine echte Verbindung zu Form, da ist mir dann dezent der Kopf durchgeraucht ;)

    Gruß, Alex
    • Marc Willmann
      Marc Willmann
      am 03.09.2022
      Du kannst in dem Finisher machen, was Du möchtest - natürlich auch Inhalte in der DB hinzufügen.

      Was Du mit der DataList vorhat, hab ich auf Anhieb nicht verstanden. Hast Du ein Anwendungsszenario, das Du lösen möchtest?
  • Max
    Max
    am 29.09.2023
    Hallo Herr Willmann,

    ich habe schwierigkeiten meinen Finisher anzubinden.
    Bei mir kommt dieser Fehler:

    he finisher preset identified by "CustomFinisher" could not be found, or the implementationClassName was not specified.

    Ich weiß auch nicht genau in welchem Ordner ich die Dateien erstellen soll.

    Viele Grüße
    Max

Neue Antwort auf Kommentar schreiben