FormDefinition   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 544
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 63
eloc 126
dl 0
loc 544
rs 3.36
c 0
b 0
f 0

29 Methods

Rating   Name   Duplication   Size   Complexity  
A getPageByIndex() 0 6 2
A addFinisher() 0 3 1
A addPage() 0 3 1
A getPages() 0 3 1
A hasPageWithIndex() 0 3 1
A getTypeDefinitions() 0 3 1
A getElementByIdentifier() 0 3 1
A getValidatorsDefinition() 0 3 1
A movePageAfter() 0 3 1
A initializeFromFormDefaults() 0 7 2
B setOptions() 0 31 10
A __construct() 0 21 4
A movePageBefore() 0 3 1
A registerRenderable() 0 7 3
A getConditionContextDefinition() 0 3 1
A removePage() 0 3 1
B createPage() 0 44 9
A setRendererClassName() 0 3 1
A bind() 0 7 1
A getPersistenceIdentifier() 0 3 1
A getElementDefaultValueByIdentifier() 0 3 1
A getFinishers() 0 3 1
A getRendererClassName() 0 3 1
A getProcessingRule() 0 6 2
A getElements() 0 3 1
A getProcessingRules() 0 3 1
A unregisterRenderable() 0 4 2
A addElementDefaultValue() 0 7 1
B createFinisher() 0 39 9

How to fix   Complexity   

Complex Class

Complex classes like FormDefinition often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FormDefinition, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
/*
19
 * Inspired by and partially taken from the Neos.Form package (www.neos.io)
20
 */
21
22
namespace TYPO3\CMS\Form\Domain\Model;
23
24
use TYPO3\CMS\Core\Utility\ArrayUtility;
25
use TYPO3\CMS\Core\Utility\GeneralUtility;
26
use TYPO3\CMS\Extbase\Mvc\Request;
27
use TYPO3\CMS\Extbase\Object\ObjectManager;
28
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
29
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
30
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
31
use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotFoundException;
32
use TYPO3\CMS\Form\Domain\Finishers\FinisherInterface;
33
use TYPO3\CMS\Form\Domain\Model\Exception\DuplicateFormElementException;
34
use TYPO3\CMS\Form\Domain\Model\Exception\FinisherPresetNotFoundException;
35
use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
36
use TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface;
37
use TYPO3\CMS\Form\Domain\Model\FormElements\Page;
38
use TYPO3\CMS\Form\Domain\Model\Renderable\AbstractCompositeRenderable;
39
use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface;
40
use TYPO3\CMS\Form\Domain\Model\Renderable\VariableRenderableInterface;
41
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
42
use TYPO3\CMS\Form\Exception as FormException;
43
use TYPO3\CMS\Form\Mvc\ProcessingRule;
44
45
/**
46
 * This class encapsulates a complete *Form Definition*, with all of its pages,
47
 * form elements, validation rules which apply and finishers which should be
48
 * executed when the form is completely filled in.
49
 *
50
 * It is *not modified* when the form executes.
51
 *
52
 * The Anatomy Of A Form
53
 * =====================
54
 *
55
 * A FormDefinition consists of multiple *Page* ({@link Page}) objects. When a
56
 * form is displayed to the user, only one *Page* is visible at any given time,
57
 * and there is a navigation to go back and forth between the pages.
58
 *
59
 * A *Page* consists of multiple *FormElements* ({@link FormElementInterface}, {@link AbstractFormElement}),
60
 * which represent the input fields, textareas, checkboxes shown inside the page.
61
 *
62
 * *FormDefinition*, *Page* and *FormElement* have *identifier* properties, which
63
 * must be unique for each given type (i.e. it is allowed that the FormDefinition and
64
 * a FormElement have the *same* identifier, but two FormElements are not allowed to
65
 * have the same identifier.
66
 *
67
 * Simple Example
68
 * --------------
69
 *
70
 * Generally, you can create a FormDefinition manually by just calling the API
71
 * methods on it, or you use a *Form Definition Factory* to build the form from
72
 * another representation format such as YAML.
73
 *
74
 * /---code php
75
 * $formDefinition = GeneralUtility::makeInstance(FormDefinition::class, 'myForm');
76
 *
77
 * $page1 = GeneralUtility::makeInstance(Page::class, 'page1');
78
 * $formDefinition->addPage($page);
79
 *
80
 * $element1 = GeneralUtility::makeInstance(GenericFormElement::class, 'title', 'Textfield'); # the second argument is the type of the form element
81
 * $page1->addElement($element1);
82
 * \---
83
 *
84
 * Creating a Form, Using Abstract Form Element Types
85
 * =====================================================
86
 *
87
 * While you can use the {@link FormDefinition::addPage} or {@link Page::addElement}
88
 * methods and create the Page and FormElement objects manually, it is often better
89
 * to use the corresponding create* methods ({@link FormDefinition::createPage}
90
 * and {@link Page::createElement}), as you pass them an abstract *Form Element Type*
91
 * such as *Text* or *Page*, and the system **automatically
92
 * resolves the implementation class name and sets default values**.
93
 *
94
 * So the simple example from above should be rewritten as follows:
95
 *
96
 * /---code php
97
 * $prototypeConfiguration = []; // We'll talk about this later
98
 *
99
 * $formDefinition = GeneralUtility::makeInstance(FormDefinition::class, 'myForm', $prototypeConfiguration);
100
 * $page1 = $formDefinition->createPage('page1');
101
 * $element1 = $page1->addElement('title', 'Textfield');
102
 * \---
103
 *
104
 * Now, you might wonder how the system knows that the element *Textfield*
105
 * is implemented using a GenericFormElement: **This is configured in the $prototypeConfiguration**.
106
 *
107
 * To make the example from above actually work, we need to add some sensible
108
 * values to *$prototypeConfiguration*:
109
 *
110
 * <pre>
111
 * $prototypeConfiguration = [
112
 *   'formElementsDefinition' => [
113
 *     'Page' => [
114
 *       'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\Page'
115
 *     ],
116
 *     'Textfield' => [
117
 *       'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement'
118
 *     ]
119
 *   ]
120
 * ]
121
 * </pre>
122
 *
123
 * For each abstract *Form Element Type* we add some configuration; in the above
124
 * case only the *implementation class name*. Still, it is possible to set defaults
125
 * for *all* configuration options of such an element, as the following example
126
 * shows:
127
 *
128
 * <pre>
129
 * $prototypeConfiguration = [
130
 *   'formElementsDefinition' => [
131
 *     'Page' => [
132
 *       'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\Page',
133
 *       'label' => 'this is the label of the page if nothing is specified'
134
 *     ],
135
 *     'Textfield' => [
136
 *       'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement',
137
 *       'label' = >'Default Label',
138
 *       'defaultValue' => 'Default form element value',
139
 *       'properties' => [
140
 *         'placeholder' => 'Text which is shown if element is empty'
141
 *       ]
142
 *     ]
143
 *   ]
144
 * ]
145
 * </pre>
146
 *
147
 * Using Preconfigured $prototypeConfiguration
148
 * ---------------------------------
149
 *
150
 * Often, it is not really useful to manually create the $prototypeConfiguration array.
151
 *
152
 * Most of it comes pre-configured inside the YAML settings of the extensions,
153
 * and the {@link \TYPO3\CMS\Form\Domain\Configuration\ConfigurationService} contains helper methods
154
 * which return the ready-to-use *$prototypeConfiguration*.
155
 *
156
 * Property Mapping and Validation Rules
157
 * =====================================
158
 *
159
 * Besides Pages and FormElements, the FormDefinition can contain information
160
 * about the *format of the data* which is inputted into the form. This generally means:
161
 *
162
 * - expected Data Types
163
 * - Property Mapping Configuration to be used
164
 * - Validation Rules which should apply
165
 *
166
 * Background Info
167
 * ---------------
168
 * You might wonder why Data Types and Validation Rules are *not attached
169
 * to each FormElement itself*.
170
 *
171
 * If the form should create a *hierarchical output structure* such as a multi-
172
 * dimensional array or a PHP object, your expected data structure might look as follows:
173
 * <pre>
174
 * - person
175
 * -- firstName
176
 * -- lastName
177
 * -- address
178
 * --- street
179
 * --- city
180
 * </pre>
181
 *
182
 * Now, let's imagine you want to edit *person.address.street* and *person.address.city*,
183
 * but want to validate that the *combination* of *street* and *city* is valid
184
 * according to some address database.
185
 *
186
 * In this case, the form elements would be configured to fill *street* and *city*,
187
 * but the *validator* needs to be attached to the *compound object* *address*,
188
 * as both parts need to be validated together.
189
 *
190
 * Connecting FormElements to the output data structure
191
 * ====================================================
192
 *
193
 * The *identifier* of the *FormElement* is most important, as it determines
194
 * where in the output structure the value which is entered by the user is placed,
195
 * and thus also determines which validation rules need to apply.
196
 *
197
 * Using the above example, if you want to create a FormElement for the *street*,
198
 * you should use the identifier *person.address.street*.
199
 *
200
 * Rendering a FormDefinition
201
 * ==========================
202
 *
203
 * In order to trigger *rendering* on a FormDefinition,
204
 * the current {@link \TYPO3\CMS\Extbase\Mvc\Request} needs to be bound to the FormDefinition,
205
 * resulting in a {@link \TYPO3\CMS\Form\Domain\Runtime\FormRuntime} object which contains the *Runtime State* of the form
206
 * (such as the currently inserted values).
207
 *
208
 * /---code php
209
 * # $currentRequest and $currentResponse need to be available, f.e. inside a controller you would
210
 * # use $this->request. Inside a ViewHelper you would use $this->controllerContext->getRequest()
211
 * $form = $formDefinition->bind($currentRequest);
212
 *
213
 * # now, you can use the $form object to get information about the currently
214
 * # entered values into the form, etc.
215
 * \---
216
 *
217
 * Refer to the {@link \TYPO3\CMS\Form\Domain\Runtime\FormRuntime} API doc for further information.
218
 *
219
 * Scope: frontend
220
 * **This class is NOT meant to be sub classed by developers.**
221
 *
222
 * @internal May change any time, use FormFactoryInterface to select a different FormDefinition if needed
223
 * @todo: Declare final in v12
224
 */
225
class FormDefinition extends AbstractCompositeRenderable implements VariableRenderableInterface
226
{
227
    /**
228
     * The finishers for this form
229
     *
230
     * @var \TYPO3\CMS\Form\Domain\Finishers\FinisherInterface[]
231
     */
232
    protected $finishers = [];
233
234
    /**
235
     * Property Mapping Rules, indexed by element identifier
236
     *
237
     * @var \TYPO3\CMS\Form\Mvc\ProcessingRule[]
238
     */
239
    protected $processingRules = [];
240
241
    /**
242
     * Contains all elements of the form, indexed by identifier.
243
     * Is used as internal cache as we need this really often.
244
     *
245
     * @var \TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface[]
246
     */
247
    protected $elementsByIdentifier = [];
248
249
    /**
250
     * Form element default values in the format ['elementIdentifier' => 'default value']
251
     *
252
     * @var array
253
     */
254
    protected $elementDefaultValues = [];
255
256
    /**
257
     * Renderer class name to be used.
258
     *
259
     * @var string
260
     */
261
    protected $rendererClassName = '';
262
263
    /**
264
     * @var array
265
     */
266
    protected $typeDefinitions;
267
268
    /**
269
     * @var array
270
     */
271
    protected $validatorsDefinition;
272
273
    /**
274
     * @var array
275
     */
276
    protected $finishersDefinition;
277
278
    /**
279
     * @var array
280
     */
281
    protected $conditionContextDefinition;
282
283
    /**
284
     * The persistence identifier of the form
285
     *
286
     * @var string
287
     */
288
    protected $persistenceIdentifier;
289
290
    /**
291
     * Constructor. Creates a new FormDefinition with the given identifier.
292
     *
293
     * @param string $identifier The Form Definition's identifier, must be a non-empty string.
294
     * @param array $prototypeConfiguration overrides form defaults of this definition
295
     * @param string $type element type of this form
296
     * @param string $persistenceIdentifier the persistence identifier of the form
297
     * @throws IdentifierNotValidException if the identifier was not valid
298
     */
299
    public function __construct(
300
        string $identifier,
301
        array $prototypeConfiguration = [],
302
        string $type = 'Form',
303
        string $persistenceIdentifier = null
304
    ) {
305
        $this->typeDefinitions = $prototypeConfiguration['formElementsDefinition'] ?? [];
306
        $this->validatorsDefinition = $prototypeConfiguration['validatorsDefinition'] ?? [];
307
        $this->finishersDefinition = $prototypeConfiguration['finishersDefinition'] ?? [];
308
        $this->conditionContextDefinition = $prototypeConfiguration['conditionContextDefinition'] ?? [];
309
310
        if (!is_string($identifier) || strlen($identifier) === 0) {
0 ignored issues
show
introduced by
The condition is_string($identifier) is always true.
Loading history...
311
            throw new IdentifierNotValidException('The given identifier was not a string or the string was empty.', 1477082503);
312
        }
313
314
        $this->identifier = $identifier;
315
        $this->type = $type;
316
        $this->persistenceIdentifier = (string)$persistenceIdentifier;
317
318
        if ($prototypeConfiguration !== []) {
319
            $this->initializeFromFormDefaults();
320
        }
321
    }
322
323
    /**
324
     * Initialize the form defaults of the current type
325
     *
326
     * @throws TypeDefinitionNotFoundException
327
     * @internal
328
     */
329
    protected function initializeFromFormDefaults()
330
    {
331
        if (!isset($this->typeDefinitions[$this->type])) {
332
            throw new TypeDefinitionNotFoundException(sprintf('Type "%s" not found. Probably some configuration is missing.', $this->type), 1474905835);
333
        }
334
        $typeDefinition = $this->typeDefinitions[$this->type];
335
        $this->setOptions($typeDefinition);
336
    }
337
338
    /**
339
     * Set multiple properties of this object at once.
340
     * Every property which has a corresponding set* method can be set using
341
     * the passed $options array.
342
     *
343
     * @param array $options
344
     * @param bool $resetFinishers
345
     * @internal
346
     */
347
    public function setOptions(array $options, bool $resetFinishers = false)
348
    {
349
        if (isset($options['rendererClassName'])) {
350
            $this->setRendererClassName($options['rendererClassName']);
351
        }
352
        if (isset($options['label'])) {
353
            $this->setLabel($options['label']);
354
        }
355
        if (isset($options['renderingOptions'])) {
356
            foreach ($options['renderingOptions'] as $key => $value) {
357
                $this->setRenderingOption($key, $value);
358
            }
359
        }
360
        if (isset($options['finishers'])) {
361
            if ($resetFinishers) {
362
                $this->finishers = [];
363
            }
364
            foreach ($options['finishers'] as $finisherConfiguration) {
365
                $this->createFinisher($finisherConfiguration['identifier'], $finisherConfiguration['options'] ?? []);
366
            }
367
        }
368
369
        if (isset($options['variants'])) {
370
            foreach ($options['variants'] as $variantConfiguration) {
371
                $this->createVariant($variantConfiguration);
372
            }
373
        }
374
375
        ArrayUtility::assertAllArrayKeysAreValid(
376
            $options,
377
            ['rendererClassName', 'renderingOptions', 'finishers', 'formEditor', 'label', 'variants']
378
        );
379
    }
380
381
    /**
382
     * Create a page with the given $identifier and attach this page to the form.
383
     *
384
     * - Create Page object based on the given $typeName
385
     * - set defaults inside the Page object
386
     * - attach Page object to this form
387
     * - return the newly created Page object
388
     *
389
     * @param string $identifier Identifier of the new page
390
     * @param string $typeName Type of the new page
391
     * @return Page the newly created page
392
     * @throws TypeDefinitionNotFoundException
393
     */
394
    public function createPage(string $identifier, string $typeName = 'Page'): Page
395
    {
396
        if (!isset($this->typeDefinitions[$typeName])) {
397
            throw new TypeDefinitionNotFoundException(sprintf('Type "%s" not found. Probably some configuration is missing.', $typeName), 1474905953);
398
        }
399
400
        $typeDefinition = $this->typeDefinitions[$typeName];
401
402
        if (!isset($typeDefinition['implementationClassName'])) {
403
            throw new TypeDefinitionNotFoundException(sprintf('The "implementationClassName" was not set in type definition "%s".', $typeName), 1477083126);
404
        }
405
        $implementationClassName = $typeDefinition['implementationClassName'];
406
407
        $classSchema = GeneralUtility::makeInstance(ReflectionService::class)->getClassSchema($implementationClassName);
408
        if ($classSchema->hasInjectMethods() || $classSchema->hasInjectProperties() || $classSchema->hasMethod('initializeObject')) {
409
            // @deprecated since v11, will be removed in v12 - Fallback for Page implementations that have
410
            // inject* or initializeObject methods, since Page prototype needs manual constructor arguments
411
            // which can't be mixed with injection in symfony DI. Deprecation is logged by ObjectManager->get().
412
            // Drop everything except the makeInstance call below in v12.
413
            $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
414
            /** @var Page $page */
415
            $page = $objectManager->get($implementationClassName, $identifier, $typeName);
416
        } else {
417
            /** @var Page $page */
418
            $page = GeneralUtility::makeInstance($implementationClassName, $identifier, $typeName);
419
        }
420
421
        if (isset($typeDefinition['label'])) {
422
            $page->setLabel($typeDefinition['label']);
423
        }
424
425
        if (isset($typeDefinition['renderingOptions'])) {
426
            foreach ($typeDefinition['renderingOptions'] as $key => $value) {
427
                $page->setRenderingOption($key, $value);
428
            }
429
        }
430
431
        ArrayUtility::assertAllArrayKeysAreValid(
432
            $typeDefinition,
433
            ['implementationClassName', 'label', 'renderingOptions', 'formEditor']
434
        );
435
436
        $this->addPage($page);
437
        return $page;
438
    }
439
440
    /**
441
     * Add a new page at the end of the form.
442
     *
443
     * Instead of this method, you should often use {@link createPage} instead.
444
     *
445
     * @param Page $page
446
     * @throws FormDefinitionConsistencyException if Page is already added to a FormDefinition
447
     * @see createPage
448
     */
449
    public function addPage(Page $page)
450
    {
451
        $this->addRenderable($page);
452
    }
453
454
    /**
455
     * Get the Form's pages
456
     *
457
     * @return array|Page[] The Form's pages in the correct order
458
     */
459
    public function getPages(): array
460
    {
461
        return $this->renderables;
462
    }
463
464
    /**
465
     * Check whether a page with the given $index exists
466
     *
467
     * @param int $index
468
     * @return bool TRUE if a page with the given $index exists, otherwise FALSE
469
     */
470
    public function hasPageWithIndex(int $index): bool
471
    {
472
        return isset($this->renderables[$index]);
473
    }
474
475
    /**
476
     * Get the page with the passed index. The first page has index zero.
477
     *
478
     * If page at $index does not exist, an exception is thrown. @see hasPageWithIndex()
479
     *
480
     * @param int $index
481
     * @return Page the page, or NULL if none found.
482
     * @throws FormException if the specified index does not exist
483
     */
484
    public function getPageByIndex(int $index)
485
    {
486
        if (!$this->hasPageWithIndex($index)) {
487
            throw new FormException(sprintf('There is no page with an index of %d', $index), 1329233627);
488
        }
489
        return $this->renderables[$index];
490
    }
491
492
    /**
493
     * Adds the specified finisher to this form
494
     *
495
     * @param FinisherInterface $finisher
496
     */
497
    public function addFinisher(FinisherInterface $finisher)
498
    {
499
        $this->finishers[] = $finisher;
500
    }
501
502
    /**
503
     * @param string $finisherIdentifier identifier of the finisher as registered in the current form (for example: "Redirect")
504
     * @param array $options options for this finisher in the format ['option1' => 'value1', 'option2' => 'value2', ...]
505
     * @return FinisherInterface
506
     * @throws FinisherPresetNotFoundException
507
     */
508
    public function createFinisher(string $finisherIdentifier, array $options = []): FinisherInterface
509
    {
510
        if (isset($this->finishersDefinition[$finisherIdentifier]) && is_array($this->finishersDefinition[$finisherIdentifier]) && isset($this->finishersDefinition[$finisherIdentifier]['implementationClassName'])) {
511
            $implementationClassName = $this->finishersDefinition[$finisherIdentifier]['implementationClassName'];
512
            $defaultOptions = $this->finishersDefinition[$finisherIdentifier]['options'] ?? [];
513
            ArrayUtility::mergeRecursiveWithOverrule($defaultOptions, $options);
514
515
            $classSchema = GeneralUtility::makeInstance(ReflectionService::class)->getClassSchema($implementationClassName);
516
            if (!$classSchema->hasMethod('setFinisherIdentifier')
517
                || ($classSchema->hasMethod('__construct')
518
                    && count($classSchema->getMethod('__construct')->getParameters()) >= 1
519
                    && (string)$classSchema->getMethod('__construct')->getFirstParameter()->getType() === 'string')
520
            ) {
521
                // @deprecated since v11, will be removed in v12 - Fallback for Finishers that do not
522
                // extend AbstractFinisher and have no setFinisherIdentifier() method or still have a
523
                // constructor argument to set the finisher name: Mixing manual constructor arguments
524
                // with injection is not allowed by symfony DI, so we dropped the constructor argument
525
                // for v11 to keep the injection.
526
                // The above if detects "old" finisher and falls back to ObjectManager, which will log
527
                // a deprecation.
528
                // Drop the if with this body in v12, keep else body, clean up
529
                // AbstractFinisher->setFinisherIdentifier() and enable the method in FinisherInterface.
530
                $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
531
                /** @var FinisherInterface $finisher */
532
                $finisher = $objectManager->get($implementationClassName, $finisherIdentifier);
533
                if ($classSchema->hasMethod('setFinisherIdentifier')) {
534
                    $finisher->setFinisherIdentifier($finisherIdentifier);
0 ignored issues
show
Bug introduced by
The method setFinisherIdentifier() does not exist on TYPO3\CMS\Form\Domain\Finishers\FinisherInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to TYPO3\CMS\Form\Domain\Finishers\FinisherInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

534
                    $finisher->/** @scrutinizer ignore-call */ 
535
                               setFinisherIdentifier($finisherIdentifier);
Loading history...
535
                }
536
            } else {
537
                /** @var FinisherInterface $finisher */
538
                $finisher = GeneralUtility::makeInstance($implementationClassName);
539
                $finisher->setFinisherIdentifier($finisherIdentifier);
540
            }
541
542
            $finisher->setOptions($defaultOptions);
543
            $this->addFinisher($finisher);
544
            return $finisher;
545
        }
546
        throw new FinisherPresetNotFoundException('The finisher preset identified by "' . $finisherIdentifier . '" could not be found, or the implementationClassName was not specified.', 1328709784);
547
    }
548
549
    /**
550
     * Gets all finishers of this form
551
     *
552
     * @return \TYPO3\CMS\Form\Domain\Finishers\FinisherInterface[]
553
     */
554
    public function getFinishers(): array
555
    {
556
        return $this->finishers;
557
    }
558
559
    /**
560
     * Add an element to the ElementsByIdentifier Cache.
561
     *
562
     * @param RenderableInterface $renderable
563
     * @throws DuplicateFormElementException
564
     * @internal
565
     */
566
    public function registerRenderable(RenderableInterface $renderable)
567
    {
568
        if ($renderable instanceof FormElementInterface) {
569
            if (isset($this->elementsByIdentifier[$renderable->getIdentifier()])) {
570
                throw new DuplicateFormElementException(sprintf('A form element with identifier "%s" is already part of the form.', $renderable->getIdentifier()), 1325663761);
571
            }
572
            $this->elementsByIdentifier[$renderable->getIdentifier()] = $renderable;
573
        }
574
    }
575
576
    /**
577
     * Remove an element from the ElementsByIdentifier cache
578
     *
579
     * @param RenderableInterface $renderable
580
     * @internal
581
     */
582
    public function unregisterRenderable(RenderableInterface $renderable)
583
    {
584
        if ($renderable instanceof FormElementInterface) {
585
            unset($this->elementsByIdentifier[$renderable->getIdentifier()]);
586
        }
587
    }
588
589
    /**
590
     * Get all form elements with their identifiers as keys
591
     *
592
     * @return FormElementInterface[]
593
     */
594
    public function getElements(): array
595
    {
596
        return $this->elementsByIdentifier;
597
    }
598
599
    /**
600
     * Get a Form Element by its identifier
601
     *
602
     * If identifier does not exist, returns NULL.
603
     *
604
     * @param string $elementIdentifier
605
     * @return FormElementInterface The element with the given $elementIdentifier or NULL if none found
606
     */
607
    public function getElementByIdentifier(string $elementIdentifier)
608
    {
609
        return $this->elementsByIdentifier[$elementIdentifier] ?? null;
610
    }
611
612
    /**
613
     * Sets the default value of a form element
614
     *
615
     * @param string $elementIdentifier identifier of the form element. This supports property paths!
616
     * @param mixed $defaultValue
617
     * @internal
618
     */
619
    public function addElementDefaultValue(string $elementIdentifier, $defaultValue)
620
    {
621
        $this->elementDefaultValues = ArrayUtility::setValueByPath(
622
            $this->elementDefaultValues,
623
            $elementIdentifier,
624
            $defaultValue,
625
            '.'
626
        );
627
    }
628
629
    /**
630
     * returns the default value of the specified form element
631
     * or NULL if no default value was set
632
     *
633
     * @param string $elementIdentifier identifier of the form element. This supports property paths!
634
     * @return mixed The elements default value
635
     * @internal
636
     */
637
    public function getElementDefaultValueByIdentifier(string $elementIdentifier)
638
    {
639
        return ObjectAccess::getPropertyPath($this->elementDefaultValues, $elementIdentifier);
640
    }
641
642
    /**
643
     * Move $pageToMove before $referencePage
644
     *
645
     * @param Page $pageToMove
646
     * @param Page $referencePage
647
     */
648
    public function movePageBefore(Page $pageToMove, Page $referencePage)
649
    {
650
        $this->moveRenderableBefore($pageToMove, $referencePage);
651
    }
652
653
    /**
654
     * Move $pageToMove after $referencePage
655
     *
656
     * @param Page $pageToMove
657
     * @param Page $referencePage
658
     */
659
    public function movePageAfter(Page $pageToMove, Page $referencePage)
660
    {
661
        $this->moveRenderableAfter($pageToMove, $referencePage);
662
    }
663
664
    /**
665
     * Remove $pageToRemove from form
666
     *
667
     * @param Page $pageToRemove
668
     */
669
    public function removePage(Page $pageToRemove)
670
    {
671
        $this->removeRenderable($pageToRemove);
672
    }
673
674
    /**
675
     * Bind the current request & response to this form instance, effectively creating
676
     * a new "instance" of the Form.
677
     *
678
     * @param Request $request
679
     * @return FormRuntime
680
     */
681
    public function bind(Request $request): FormRuntime
682
    {
683
        $formRuntime = GeneralUtility::makeInstance(FormRuntime::class);
684
        $formRuntime->setFormDefinition($this);
685
        $formRuntime->setRequest($request);
686
        $formRuntime->initialize();
687
        return $formRuntime;
688
    }
689
690
    /**
691
     * @param string $propertyPath
692
     * @return ProcessingRule
693
     */
694
    public function getProcessingRule(string $propertyPath): ProcessingRule
695
    {
696
        if (!isset($this->processingRules[$propertyPath])) {
697
            $this->processingRules[$propertyPath] = GeneralUtility::makeInstance(ProcessingRule::class);
698
        }
699
        return $this->processingRules[$propertyPath];
700
    }
701
702
    /**
703
     * Get all mapping rules
704
     *
705
     * @return \TYPO3\CMS\Form\Mvc\ProcessingRule[]
706
     * @internal
707
     */
708
    public function getProcessingRules(): array
709
    {
710
        return $this->processingRules;
711
    }
712
713
    /**
714
     * @return array
715
     * @internal
716
     */
717
    public function getTypeDefinitions(): array
718
    {
719
        return $this->typeDefinitions;
720
    }
721
722
    /**
723
     * @return array
724
     * @internal
725
     */
726
    public function getValidatorsDefinition(): array
727
    {
728
        return $this->validatorsDefinition;
729
    }
730
731
    /**
732
     * @return array
733
     * @internal
734
     */
735
    public function getConditionContextDefinition(): array
736
    {
737
        return $this->conditionContextDefinition;
738
    }
739
740
    /**
741
     * Get the persistence identifier of the form
742
     *
743
     * @return string
744
     * @internal
745
     */
746
    public function getPersistenceIdentifier(): string
747
    {
748
        return $this->persistenceIdentifier;
749
    }
750
751
    /**
752
     * Set the renderer class name
753
     *
754
     * @param string $rendererClassName
755
     */
756
    public function setRendererClassName(string $rendererClassName)
757
    {
758
        $this->rendererClassName = $rendererClassName;
759
    }
760
761
    /**
762
     * Get the classname of the renderer
763
     *
764
     * @return string
765
     */
766
    public function getRendererClassName(): string
767
    {
768
        return $this->rendererClassName;
769
    }
770
}
771