Repeatable Form Element - Teil 2

Teil 2: Business Logik

Was war das Ziel?

Das folgende Video zeigt Dir - als Gedankenstütze - was wir erreichen wollen.

Die Idee oder: Anforderung an die Business Logik

Im ersten Teil des Walkthroughs haben wir definiert, dass ein Prozess implementiert wird, der beliebig viele Kopien eines RepeatableContainers erzeugen kann. Liegen in einem RepeatableContainer Formularelement ein oder mehrere Kinderelemente, welche wiederum RepeatableContainer Formularelemente sind, so wird bei einem Kopiervorgang der Zustand des 'Mutterelements' vor dem Kopiervorgang als Vorlage für die neue Kopie verwendet. Mutterelement ist dabei das original RepeatableContainer Formularelement so, wie es in der Formulardefinition deklariert wurde.

Beispiel

1
2
3
4
repeatablecontainer-1
  text-1
  repeatablecontainer-2
    text-2
Ausgangszustand des Mutterelements

Wird repeatablecontainer-2 kopiert, besteht nachfolgender Zustand.

1
2
3
4
5
6
repeatablecontainer-1
  text-1
  repeatablecontainer-2
    text-2
  repeatablecontainer-2-kopie-1
    text-2

Wird repeatablecontainer-1 kopiert, herrscht folgender Zustand.

1
2
3
4
5
6
7
8
9
10
repeatablecontainer-1
  text-1
  repeatablecontainer-2
    text-2
  repeatablecontainer-2-kopie-1
    text-2
repeatablecontainer-1-kopie-1
  text-1
  repeatablecontainer-2
    text-2

Das soeben beschrieben Beispiel soll in der Formulardefinition folgendermaßen beschrieben werden.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
type: Form
identifier: test
label: test
prototypeName: standard
renderables:
  -
    type: Page
    identifier: page-1
    label: Step
    renderables:
      -
        type: RepeatableContainer
        identifier: repeatablecontainer-1
        label: 'Repeatable container'
        properties:
          minimumCopies: 0
          maximumCopies: 2
          showRemoveButton: true
          copyButtonLabel: Copy
          removeButtonLabel: Remove
        renderables:
          -
            type: Text
            identifier: text-1
            label: Text
            defaultValue: ''
          -
            type: RepeatableContainer
            identifier: repeatablecontainer-2
            label: 'Repeatable container'
            properties:
              minimumCopies: 1
              maximumCopies: 3
              showRemoveButton: true
              copyButtonLabel: Copy
              removeButtonLabel: Remove
            renderables:
              -
                type: Text
                identifier: text-2
                label: Text
                defaultValue: ''

Für die Umsetzung kopierbarer Formularelemente machen wir uns mehrere Eigenschaften des TYPO3 Form Frameworks zu Nutze. Im Folgenden beschreiben wir diese Eigenschaften.

Funktionelle Aspekte der Eigenschaft "identifier"

Jedes Formularelement hat eine Eigenschaft namens identifier. Diese wird unter anderem dafür genutzt, um das HTML Attribut name zu generieren. Auf Basis der nachfolgenden Formulardefinition würde für das Formularelement text-1 für das HTML Attribut name 'tx_form_formframework[test-1][text-1]' generiert und für das Formularelement text-2 'tx_form_formframework[test-1][text-2]' generiert werden. Benutzen wir Punkte im identifier, so kann das HTML Attribut name verschachtelt werden.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type: Form
identifier: test
label: test
prototypeName: standard
renderables:
  -
    type: Page
    identifier: page-1
    label: Step
    renderables:
      -
        type: Text
        identifier: text-1
        defaultValue: ''
        label: Text1
      -
        type: Text
        identifier: text-2
        defaultValue: ''
        label: Text2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type: Form
identifier: test
label: test
prototypeName: standard
renderables:
  -
    type: Page
    identifier: page-1
    label: Step
    renderables:
      -
        type: Text
        identifier: fullname.text-1
        defaultValue: ''
        label: Text1
      -
        type: Text
        identifier: fullname.text-2
        defaultValue: ''
        label: Text2

Property Mapping und Validierung der Formularelemente

Wird ein Formular abgesendet, so iteriert das TYPO3 Form Framework über alle in der Formulardefinition beschriebenen Formularelemente. Anhand der identifier Eigenschaften der Formularelemente wird geschaut, ob sich in den übertragenen Formulardaten ein Wert für das entsprechende Formularelement befindet. Existiert ein übertragener Formularwerten für ein Element, so wird der Wert mit diesem Formularelement verknüpft. Alle anderen übertragenen Daten werden ignoriert. Nach diesem Prozess werden alle so registrierten Formularelementwerte dem Property Mapping unterzogen und die Validierung der Werte vorgenommen. In einem Fehlerfall beim Property Mapping oder der Validierung, wird die abgeschickte Seite mit den entsprechenden Fehlermeldungen erneut angezeigt. Im Erfolgsfall werden die nächste Seite angezeigt oder die Finisher ausgeführt.

Angenommen wir haben folgende Formulardefinition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type: Form
identifier: test
label: test
prototypeName: standard
renderables:
  -
    type: Page
    identifier: page-1
    label: Step
    renderables:
      -
        type: Text
        identifier: text-1
        defaultValue: ''
        label: Text1

Wird dieses Formular abgeschickt, erhalten wir serverseitig das folgende Array.

1
2
3
4
5
[
    'text-1' => 'some value',
    // hacker values
    'something else' => 'foo'
]

Da der Array Key 'text-1' mit der Formularelementeigenschaft identifier des Formularelements text-1 zusammenpasst, wird dieser Wert vom Form Framework registriert, mit dem Formularelement verknüpft und ggf. validiert. Da der übertragene Wert 'something else' mit keiner Formularelement Eigenschaft identifier in Beziehung gesetzt werden kann, wird dieser Wert ignoriert.

Gelingt es uns also, vor dem soeben beschrieben Prozess programmatisch neue Formularelemente zu erzeugen (was equivalent dazu ist, als hätten wir diese Werte in die YAML Formulardefinition geschrieben), so werden übertragene Formularelementdaten, welche mit den identifiern der programmatisch erzeugten Formularelemente übereinstimmen automatisch vom Form Framework registriert und validiert.

Hooks

Das TYPO3 Form Framework bietet Hooks, um an verschiedenen Stellen der Formularlaufzeit das Verhalten manipulieren zu können. Es gibt z.B. Hooks, welche angesprochen werden können während ein Formularelement anhand der YAML Formulardefinition erzeugt wird. Ein solcher Hook wäre zu früh für unser Vorhaben, denn wir benötigen die "Laufzeitinformationen" des Formulars, d.h. wir benötigen Informationen über die übertragenen Formularelementwerte. Diese stehen z.B. beim afterBuildingFinished Hook noch nicht bereit. Andererseits wäre der beforeRendering Hook zu spät im Prozess, denn hier ist das Registrieren und Validieren der Formularelementwerte bereits geschehen. Wir möchten aber an einer Stelle programmatisch Formularelemente anlegen, bevor die eventuell übertragenen Formularelementwerte registriert und validiert werden, aber die übertragenen Formularelementwerte bereits zur Verfügung stehen. Dafür bietet sich der afterInitializeCurrentPage Hook an.

Der Hook

Beschreibung

Der afterInitializeCurrentPage Hook wird angesprochen bevor die übertragenen Formularelementwerte mit den Formularelementen verknüpft und diese Werte validiert werden. Das Form Framework findet anhand interner Eigenschaften heraus, welche Seite als nächstes angezeigt werden sollte, wenn später keine Validierungsfehler auftreten und stellt diese Informationen über die Methode getCurrentPage() bereit. Angenommen es gibt zwei Seiten in einem Formular und die erste Seite wird abgeschickt. Man erhält an der Stelle, an welcher der afterInitializeCurrentPage Hook abgefragt werden kann - mittels der Methode getCurrentPage() - ein Objekt, das die zweite Seite des Formulars repräsentiert. Dieser Hook soll dazu dienen, die anzuzeigende Seite, wenn nötig, selbst bestimmen zu können. Wir benutzen ihn jedoch nicht für eine solche Manipulation, sondern dazu, um Formularelementwerte zu manipulieren und dynamisch zu erzeugen. Nachfolgend werden kurz die Parameter des Hooks beschrieben.

$formRuntime

Das FormRuntime Objekt enthält die Laufzeitinformationen eines Formulars. Dies wären die übertragenen Formularelementwerte eines Formularelements, welche Seite aktuell angezeigt wird etc. Weiterhin kümmert es sich unter anderem um die Validierung der Formularelementwerte.

$currentPage

$currentPage ist ein Objekt, das die Seite repräsentiert, welche angezeigt werden sollte, wenn später keine Validierungsfehler auftreten. Wird die letzte Seite eines Formulars abgeschickt, enthält diese Variable den Wert null.

$lastPage

$lastPage ist ein Objekt, das die Seite repräsentiert, welche zuletzt angezeigt wurde. Wird das Formular initial angezeigt - d.h. es sind noch keine Formularelementwerte übertragen - enthält diese Variable den Wert null.

$rawRequestArguments

$rawRequestArguments ist ein Shortcut dafür, als würde man im Hook das Folgende schreiben.

1
2
3
/** @var \TYPO3\CMS\Extbase\Mvc\Web\Request $request */
$request = $formRuntime->getRequest();
$arguments = $request->getArguments();

Hierüber erhält man die übertragenen Formularelementwerte einer Seite bevor das Verknüpfen mit einem Formularelement, das Property Mapping und die Validierung stattgefunden haben. Formularelementwerte sind normalerweise über das FormRuntime Objekt verfügbar. Hierfür gibt es 2 Varianten. Zum einen ist dies über die API Methode getElementValue() möglich. Zum anderen ist die FormRuntime in der Lage, die Formularelementwerte zu liefern, indem man das Objekt wie ein Array anspricht, wobei der Array Key der identifier eines Formularelements ist.

1
2
3
4
// Variante 1: Einsatz der API Methode
$value = $formRuntime->getElementValue('text-1');
// Variante 2: Objektzugriff
$value = $formRuntime['text-1'];

Beide Varianten liefern erst sinnvolle Ergebnisse, nachdem das Verknüpfen von übertragenen Formularelementdaten mit einem Formularelement stattgefunden hat. Der afterInitializeCurrentPage Hook befindet sich allerdings vor diesem Prozess. Dadurch ist es z.B. beim allerersten Abschicken des Formulars nicht möglich, den Wert eines Formularelements via $formRuntime->getElementValue('text-1') zu erhalten, denn diese Verknüpfung fand noch nicht statt.

Ein anderer Fall, bei welchem ein $formRuntime->getElementValue('text-1') nicht das gewünschte Ergebnis liefern würde, wäre Folgender. Ein Formular wird abgeschickt und der Wert des Formularelements text-1 ist 'test'. Nun tritt ein Validierungsfehler auf und die Formularseite wird erneut angezeigt. Im Anschluss wird das Formular erneut abgeschickt, der Wert des Formularelements text-1 wird auf 'testxxx' abgeändert. Ein $formRuntime->getElementValue('text-1') würde an dieser Stelle noch den zuvor verknüpften Wert 'test' liefern, da sich der Hook vor der erneuten Verknüpfung mit dem neuen Wert stattfindet. Für diesen Fall ist es hilfreich, die aktuell abgeschickten Formularelementwerte in der Variable $rawRequestArguments vorzufinden.

Verwendung

Wie oben beschrieben, können wir uns zu Nutze machen, dass wir Punkte in der Eigenschaft identifier eines Formularelements benutzen können, um eine logische Gruppierung von Formularelementen zu erhalten. Der Hook kümmert sich deshalb als erstes darum, dass alle RepeatableContainer Formularelemente und deren Kinderelemente durch eine solche Punktnotation durch Manipulation der Eigenschaft identifier gruppiert werden. Nehmen wir an, uns liegt folgende Formulardefinition vor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type: Form
identifier: test
label: test
prototypeName: standard
renderables:
  -
    type: Page
    identifier: page-1
    label: Step
    renderables:
      -
        type: RepeatableContainer
        identifier: repeatablecontainer-1
        label: 'Repeatable container'
        renderables:
          -
            type: Text
            identifier: text-1
            label: Text
            defaultValue: ''
          -
            type: RepeatableContainer
            identifier: repeatablecontainer-2
            label: 'Repeatable container'
            renderables:
              -
                type: Text
                identifier: text-2
                label: Text
                defaultValue: ''

Durch den Einsatz der Methode setRootRepeatableContainerIdentifiers() im Hook geschieht automatisch das Folgende.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type: Form
identifier: test
label: test
prototypeName: standard
renderables:
  -
    type: Page
    identifier: page-1
    label: Step
    renderables:
      -
        type: RepeatableContainer
        identifier: repeatablecontainer-1.0
        label: 'Repeatable container'
        renderables:
          -
            type: Text
            identifier: repeatablecontainer-1.0.text-1
            label: Text
            defaultValue: ''
          -
            type: RepeatableContainer
            identifier: repeatablecontainer-1.0.repeatablecontainer-2.0
            label: 'Repeatable container'
            renderables:
              -
                type: Text
                identifier: repeatablecontainer-1.0.repeatablecontainer-2.0.text-2
                label: Text
                defaultValue: ''

Jeder identifier eines RepeatableContainer Formularelements bekommt nach Konvention einen Suffix '.0' angehangen. Jeder identifier eines Kinderelements bekommt den Präfix 'repeatablecontainer-1.0.' vorangestellt. Das HTML Attribut name wird für das Formularelement text-1 im Frontend dadurch zu 'tx_form_formframework[test-1][repeatablecontainer-1][0][text-1]'. Das HTML Attribut name wird für das Formularelement text-2 im Frontend dadurch zu 'tx_form_formframework[test-1][repeatablecontainer-1][0][repeatablecontainer-2][0][text-2]'.

Der Kopierservice kann diese Gruppierung im Anschluss wieder sinnvoll zerlegen und erkennen, ob Kopien erstellt werden sollen. Wie dies genau geschieht, wird später erläutert.

Nach dem Prozess der Modifikation der identifier Eigenschaften geschieht der Kopierprozess - falls nötig. Hier findet eine Unterscheidung statt, ob der Benutzer des Formulars eine Seite vor oder zurück springt. Diese Unterscheidung liegt an den unter '$rawRequestArguments' beschriebenen Fallbeispielen.

Der Kopierservice fügt die Kopien automatisch dem Formular hinzu. Nachdem dies geschehen ist, verlassen wir den Hook. Das TYPO3 Form Framework wird später im Prozess versuchen, die übertragenen Formularelementdaten einem Formularelement zuzuordnen und ggf. zu validieren. Da der Kopierservice vorher anhand der übertragenen Formularelementwerte dynamisch neue Formularelemente erstellt hat, deren identifier mit den entsprechend übertragenen Formularelementdaten übereinstimmen, wird das Form Framework die übertragenen Werte richtig zuordnen und validieren.

Der Kopierservice

Kurzfassung

Der Trigger für 'erstelle eine Kopie von Formularelement X' kommt von 'außen'. Außen bedeutet, es existiert clientseitig ein JavaScript Code, welcher Formularelemente erzeugt, deren  HTML Attribut name einer gewissen Konvention folgt. Serverseitig wird geprüft, ob nach dem Abschicken eines Formulars die übertragene Datenstruktur Daten enthält, die einem identifier eines RepeatableContainers zugeordnet werden können. Dies erteilt dem Kopierservice die Erlaubnis, anhand einer gewissen Konvention und unter Berücksichtigung von Beschränkungen Kopien zu erstellen.

Langfassung

Die folgende Formulardefinition wird wie beschrieben durch den Hook manipuliert, sodass die Formularelemente im Frontend gruppiert werden. Dadurch kommt nach dem Absenden des Formulars eine verschachtelte Datenstruktur für diese Formularelemente an.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type: Form
identifier: test
label: test
prototypeName: standard
renderables:
  -
    type: Page
    identifier: page-1
    label: Step
    renderables:
      -
        type: RepeatableContainer
        identifier: repeatablecontainer-1
        label: 'Repeatable container'
        renderables:
          -
            type: Text
            identifier: text-1
            label: Text
            defaultValue: ''
          -
            type: RepeatableContainer
            identifier: repeatablecontainer-2
            label: 'Repeatable container'
            renderables:
              -
                type: Text
                identifier: text-2
                label: Text
                defaultValue: ''

Werden Kopien im Frontend erzeugt, so kommt nach dem Absenden des Formulars - exemplarisch - am Server folgende Datenstruktur an.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[
    'repeatablecontainer-1' => [
        0 => [
            'text-1' => 'text 1',
            'repeatablecontainer-2' => [
                0 => [
                    'text-2' => 'text 2',
                ],
                // triggert "erstelle Kopie von 'repeatablecontainer-1.0.repeatablecontainer-2.0' mit dem neuen `identifier`
                // repeatablecontainer-1.0.repeatablecontainer-2.1'
                1 => [
                    'text-2' => 'ich bin eine kopie',
                ],
            ],
        ],
    ],
]

Es wird nun geprüft, ob repeatablecontainer-2 einem identifier aus der original Formulardefinition (vor der Manipulation der identifier durch den Hook) entspricht und ob dieses Formularelement ein Formularelement vom Typ RepeatableContainer ist. Ist das nicht der Fall, so werden die Daten verworfen und kein Kopierprozess findet statt. Ist dies der Fall, so wissen wir, dass hier prinzipiell ein Kopiervorgang gestartet werden könnte. Als nächstes wird geprüft, ob alle Kinderwerte von 'repeatablecontainer-2' in der übertragenen Datenstruktur Integer Werte sind. Ist das nicht der Fall, so werden die Daten verworfen und kein Kopierprozess findet statt. Als nächstes wird geprüft, ob es Kinderwerte in der übertragenen Datenstruktur gibt, welche größer 0 sind. Ist das nicht der Fall, so werden die Daten verworfen und kein Kopierprozess findet statt. Ist dies der Fall, werden soviele Kopien von repeatablecontainer-2 erzeugt, wie es Kinderwerte in der übertragenen Datenstruktur gibt. Wird die konfigurierte Minimal- oder Maximalanzahl an Kopien über- oder unterschritten, so wird ein Validierungsfehler an das RepeatableContainer Formularelement - von welchem kopiert wurde -  angehangen. Dieser Prozess findet rekursiv auf der gesamten übertragene Datenstruktur statt, sodass RepeatableContainer Formularelemente verschachtelt werden können. Dies alles geschieht hauptsächlich in der Methode copyRepeatableContainersFromArguments().

Sind alle Voraussetzungen zum Kopieren erfüllt, so startet der Kopiervorgang. Die geschieht in der Methode copyRepeatableContainer().

Zuerst wird das Original RepeatableContainer Formularelement kopiert. Dazu wird ein neues Formularelement auf Basis der Konfiguration des Original Formularelements erstellt, welches zu diesem Zeitpunkt noch keine Kopien der Kinderelemente enthält. Bis auf die Eigenschaft identifier - der identifier muss formularweit eindeutig sein - ist die Kopie 1:1 identisch mit dem Original Formularelement. Nach dem Erstellen der Kopie wird das kopierte Formularelement der Formularseite hinzugefügt und an die Position nach dem Original Formularelement verschoben.

Der folgende Beispielcode ist nicht 1:1 identisch mit dem Code des Kopierservice und wurde zu Lernzwecken etwas modifiziert. Inhaltlich stimmt er aber überein.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
$newIdentifier = 'copy-name';

// 'getType()' [Link-1] liefert den Formularelementtyp, in diesem Fall 'RepeatableContainer'.
$typeName = $copyFromContainer->getType();

// Liefert ein Array mit allen unter 'TYPO3.CMS.Form.prototypes.standard.formElementsDefinition' konfigurierten Werten.
$typeDefinitions = $formRuntime->getFormDefinition()->getTypeDefinitions();

// Der unter 'TYPO3.CMS.Form.prototypes.standard.formElementsDefinition.RepeatableContainer.implementationClassName'
// konfigurierte Klassenname, welcher für die Erzeugung des Objektes verwendet werden soll.
// In diesem Fall 'TRITUM\RepeatableFormElements\FormElements\RepeatableContainer'
$copyFromContainerImplementationClassName = $typeDefinitions[$typeName]['implementationClassName'];

// Erzeugen des neuen Formularelements. Es sollte hier, wenn möglich, die API Methode
// `createElement()` [Link-2] des `Page` Objektes verwendet werden.
// Zu Lernzwecken wird es hier aber komplett selbst aufgebaut.
// Später wird die Methode `createElement()` noch vorgestellt.
$containerCopy = $this->getObjectManager()->get($copyFromContainerImplementationClassName, $newIdentifier, $typeName);

// Nun werden wichtigen Eigenschaften des Original Formularelements
// in die Kopie geschrieben [Link-3].
$containerCopy->setLabel($copyFromContainer->getLabel());
$containerCopy->setDefaultValue($copyFromContainer->getDefaultValue());
foreach ($copyFromContainer->getProperties() as $key => $value) {
    $containerCopy->setProperty($key, $value);
}
foreach ($copyFromContainer->getRenderingOptions() as $key => $value) {
    $containerCopy->setRenderingOption($key, $value);
}
foreach ($copyFromContainer->getValidators() as $validator) {
    $containerCopy->addValidator($validator);
}

// $copyFromContainer ist vom Typ `RepeatableContainer`. Dieser Formularelementtyp implementiert
// das Interface \TYPO3\CMS\Form\Domain\Model\Renderable\CompositeRenderableInterface.
// Das Elternelement eines CompositeRenderableInterface ist immer ein Formularelement,
// welches ebenfalls CompositeRenderableInterface implementiert.
// Dadurch ist bekannt, dass das Elternelement gewisse Methoden zur Verfügung stellt [Link-4].
$parentRenderableForNewContainer = $copyFromContainer->getParentRenderable();

// Füge die Kopie ans Ende des Elternelements an.
$parentRenderableForNewContainer->addElement($containerCopy);

// Verschiebe die Kopie an die Position nach dem original Formularelement.
$parentRenderableForNewContainer->moveElementAfter($containerCopy, $copyFromContainer);

Weiterführende Links zur Dokumentation des TYPO3 Form Frameworks, die im o.g. Quellcode benannt sind: [Link-1], [Link-2],[Link-3] und [Link-4].

Somit haben wir eine Kopie des Original RepeatableContainers erstellt. Nun müssen die Kinderelemente auf ähnliche Weise erstellt werden. Zuerst iterieren wir über alle Kinderelemente des Original RepeatableContainers und übergeben die Kinder an die Methode createNestedElements().

1
2
3
foreach ($copyFromContainer->getElements() as $originalFormElement) {
    $this->createNestedElements($originalFormElement, $containerCopy);
}

Die Methode createNestedElements() liefert mehr oder weniger das Gleiche, wie der Code, um eine Kopie des RepeatableContainers zu erstellen. Hier wird allerdings die API Methode createElement() verwendet. Die Methode ruft sich rekursiv auf, wenn das zu kopierende Formularelement das Interface TYPO3CMSFormDomainModelRenderableCompositeRenderableInterface implementiert, was soviel bedeutet wie "ich enthalte Kinderelemente".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Eine neues Formularelement des selben Typs wie das Original Formularelement erstellen.
// `createElement()` fügt das Formularelement automatisch dem Formularelement `$containerCopy` hinzu (siehe `addElement()` [Link-5]).
$newFormElement = $containerCopy->createElement(
    $newIdentifier,
    $originalFormElement->getType()
);

// Eigenschaften kopieren
$newFormElement->setLabel($originalFormElement->getLabel());
$newFormElement->setDefaultValue($originalFormElement->getDefaultValue());
foreach ($originalFormElement->getProperties() as $key => $value) {
    $newFormElement->setProperty($key, $value);
}
foreach ($originalFormElement->getRenderingOptions() as $key => $value) {
    $newFormElement->setRenderingOption($key, $value);
}
foreach ($originalFormElement->getValidators() as $validator) {
    $newFormElement->addValidator($validator);
}

if ($originalFormElement instanceof CompositeRenderableInterface) {
    // Kopien aller Kinderelemente erzeugen
    foreach ($originalFormElement->getElements() as $originalChildFormElement) {
        $this->createNestedElements($originalChildFormElement, $newFormElement);
    }
}

Weiterführender Link zur Dokumentation des TYPO3 Form Frameworks, die im o.g. Quellcode benannt ist: [Link-5].

Mit all diesen Schritten haben wir das komplette RepeatableContainer Formularelement inklusive aller Eigenschaften und aller Kinderelementen kopiert. Well done!

Fertig

Die ist das Ende des ultimativen Walkthroughs zum Thema Repeatable Form Elemente. Wir hoffen, Du konntest viel für Dich mitnehmen. Wir freuen uns auf Dein Feedback. All the best and happy coding!