StructureFormGroup::formProperties()   F
last analyzed

Complexity

Conditions 20
Paths 794

Size

Total Lines 84
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 50
nc 794
nop 0
dl 0
loc 84
rs 0.2861
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Charcoal\Admin\Widget\FormGroup;
4
5
use RuntimeException;
6
use OutOfBoundsException;
7
use UnexpectedValueException;
8
use InvalidArgumentException;
9
10
// From 'charcoal-property'
11
use Charcoal\Property\ModelStructureProperty;
12
use Charcoal\Property\PropertyInterface;
13
14
// From 'charcoal-ui'
15
use Charcoal\Ui\FormGroup\AbstractFormGroup;
16
use Charcoal\Ui\FormGroup\FormGroupInterface;
17
use Charcoal\Ui\FormInput\FormInputInterface;
18
19
// From 'charcoal-admin'
20
use Charcoal\Admin\Widget\FormGroupWidget;
21
use Charcoal\Admin\Ui\ObjectContainerInterface;
22
use Charcoal\Admin\Ui\StructureContainerInterface;
23
use Charcoal\Admin\Ui\StructureContainerTrait;
24
25
/**
26
 * Form Group Structure Property
27
 *
28
 * The form group widget displays a set of form controls based on properties
29
 * assigned to the widget directly or a proxy structure property.
30
 *
31
 * ## Examples
32
 *
33
 * **Example #1 — Structure widget**
34
 *
35
 * ```json
36
 * "properties": {
37
 *     "extra_data": {
38
 *         "type": "structure",
39
 *         "structure_metadata": {
40
 *             "properties": { … },
41
 *             "admin": {
42
 *                 "form_groups": { … },
43
 *                 "default_form_group": "…"
44
 *             }
45
 *         }
46
 *     }
47
 * },
48
 * "widgets": [
49
 *     {
50
 *         "title": "Extra Data",
51
 *         "type": "charcoal/admin/widget/form-group/structure",
52
 *         "template": "charcoal/admin/widget/form-group/structure",
53
 *         "storage_property": "extra_data"
54
 *     }
55
 * ]
56
 * ```
57
 *
58
 * **Example #2 — With verbose storage declaration**
59
 *
60
 * {@todo Eventually, the form group could support other storage sources such as
61
 * file-based or a database such as an SQL server.}
62
 *
63
 * ```json
64
 * {
65
 *     "title": "Extra Data",
66
 *     "type": "charcoal/admin/widget/form-group/structure",
67
 *     "template": "charcoal/admin/widget/form-group/structure",
68
 *     "storage": {
69
 *         "type": "property",
70
 *         "property": "extra_data"
71
 *     }
72
 * }
73
 * ```
74
 *
75
 */
76
class StructureFormGroup extends FormGroupWidget implements
77
    FormInputInterface,
78
    StructureContainerInterface
79
{
80
    use StructureContainerTrait;
81
82
    /**
83
     * The structure entry identifier.
84
     *
85
     * @var string
86
     */
87
    private $structId;
88
89
    /**
90
     * The form group's storage model.
91
     *
92
     * @var \Charcoal\Model\ModelInterface|null
93
     */
94
    protected $obj;
95
96
    /**
97
     * The form group's storage medium.
98
     *
99
     * @var array|PropertyInterface|\Charcoal\Source\SourceInterface|null
100
     */
101
    protected $storage;
102
103
    /**
104
     * The form group's storage target. {@deprecated In favor of $storage.}
105
     *
106
     * @var ModelStructureProperty|null
107
     */
108
    protected $storageProperty;
109
110
    /**
111
     * The form group the input belongs to.
112
     *
113
     * @var FormGroupInterface|null
114
     */
115
    private $formGroup;
116
117
    /**
118
     * Whether the form is ready.
119
     *
120
     * @var boolean
121
     */
122
    protected $isStructureFinalized = false;
123
124
    /**
125
     * The form group's raw data.
126
     *
127
     * @var array|null
128
     */
129
    protected $rawData;
130
131
    /**
132
     * @return string
133
     */
134
    public function type()
135
    {
136
        return 'charcoal/admin/widget/form-group/structure';
137
    }
138
139
    /**
140
     * @param  string $structId The structure entry identifier.
141
     * @return self
142
     */
143
    public function setStructId($structId)
144
    {
145
        $this->structId = $structId;
146
        return $this;
147
    }
148
149
    /**
150
     * @return string
151
     */
152
    public function structId()
153
    {
154
        if (!$this->structId) {
155
            $this->structId = uniqid();
156
        }
157
158
        return $this->structId;
159
    }
160
161
    /**
162
     * @param  array $data Widget data.
163
     * @return self
164
     */
165
    public function setData(array $data)
166
    {
167
        if ($this->rawData === null) {
168
            $this->rawData = $data;
169
        }
170
171
        parent::setData($data);
172
173
        return $this;
174
    }
175
176
    /**
177
     * Determine if the header is to be displayed.
178
     *
179
     * @return boolean If TRUE or unset, check if there is a title.
180
     */
181
    public function showHeader()
182
    {
183
        if ($this->display() === self::SEAMLESS_STRUCT_DISPLAY) {
184
            return false;
185
        } else {
186
            return parent::showHeader();
187
        }
188
    }
189
190
    /**
191
     * Determine if the footer is to be displayed.
192
     *
193
     * @return boolean If TRUE or unset, check if there are notes.
194
     */
195
    public function showFooter()
196
    {
197
        if ($this->display() === self::SEAMLESS_STRUCT_DISPLAY) {
198
            return false;
199
        } else {
200
            return parent::showFooter();
201
        }
202
    }
203
204
    /**
205
     * Retrieve the form's object.
206
     *
207
     * @throws RuntimeException If the form doesn't have a model.
208
     * @return \Charcoal\Model\ModelInterface
209
     */
210
    public function obj()
211
    {
212
        if ($this->obj === null) {
213
            $formGroup = $this->formGroup();
214
            if ($formGroup instanceof self) {
215
                $prop = $formGroup->storageProperty();
216
                $val  = $formGroup->obj()->propertyValue($prop->ident());
0 ignored issues
show
Bug introduced by
The method propertyValue() does not exist on Charcoal\Model\ModelInterface. Did you maybe mean property()? ( Ignorable by Annotation )

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

216
                $val  = $formGroup->obj()->/** @scrutinizer ignore-call */ propertyValue($prop->ident());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
217
218
                $this->obj = $prop->structureVal($val, [ 'default_data' => true ]);
219
                if ($this->obj === null) {
220
                    $this->obj = clone $prop->structureProto();
221
                }
222
            } elseif ($this->form() instanceof ObjectContainerInterface) {
223
                $this->obj = $this->form()->obj();
224
            }
225
226
            if ($this->obj === null) {
227
                throw new RuntimeException(sprintf(
228
                    'The [%1$s] widget has no data model.',
229
                    static::CLASS
0 ignored issues
show
Bug introduced by
The constant Charcoal\Admin\Widget\Fo...ructureFormGroup::CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
230
                ));
231
            }
232
        }
233
234
        return $this->obj;
235
    }
236
237
    /**
238
     * Set the form input's parent group.
239
     *
240
     * @param  FormGroupInterface $formGroup The parent form group object.
241
     * @return self
242
     */
243
    public function setFormGroup(FormGroupInterface $formGroup)
244
    {
245
        $this->formGroup = $formGroup;
246
247
        return $this;
248
    }
249
250
    /**
251
     * Retrieve the input's parent group.
252
     *
253
     * @return FormGroupInterface|null
254
     */
255
    public function formGroup()
256
    {
257
        return $this->formGroup;
258
    }
259
260
    /**
261
     * Clear the group's parent group.
262
     *
263
     * @return self
264
     */
265
    public function clearFormGroup()
266
    {
267
        $this->formGroup = null;
268
269
        return $this;
270
    }
271
272
    /**
273
     * Set the form group's storage target.
274
     *
275
     * Must be a property of the form's object model that will receive an associative array
276
     * of the form group's data.
277
     *
278
     * @param  string|ModelStructureProperty $propertyIdent The property identifier—or instance—of a storage property.
279
     * @throws InvalidArgumentException If the property identifier is not a string.
280
     * @throws UnexpectedValueException If a property is invalid.
281
     * @return self
282
     */
283
    public function setStorageProperty($propertyIdent)
284
    {
285
        $property = null;
286
        if ($propertyIdent instanceof PropertyInterface) {
287
            $property      = $propertyIdent;
288
            $propertyIdent = $property->ident();
289
        } elseif (!is_string($propertyIdent)) {
0 ignored issues
show
introduced by
The condition is_string($propertyIdent) is always true.
Loading history...
290
            throw new InvalidArgumentException(
291
                'Storage Property identifier must be a string'
292
            );
293
        }
294
295
        $obj = $this->obj();
296
        if (!$obj->hasProperty($propertyIdent)) {
297
            throw new UnexpectedValueException(sprintf(
298
                'The "%1$s" property is not defined on [%2$s]',
299
                $propertyIdent,
300
                get_class($obj)
301
            ));
302
        }
303
304
        if ($property === null) {
305
            $property = $obj->property($propertyIdent);
306
        }
307
308
        if ($property instanceof ModelStructureProperty) {
309
            $this->storageProperty = $property;
0 ignored issues
show
Deprecated Code introduced by
The property Charcoal\Admin\Widget\Fo...Group::$storageProperty has been deprecated. ( Ignorable by Annotation )

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

309
            /** @scrutinizer ignore-deprecated */ $this->storageProperty = $property;
Loading history...
310
        } else {
311
            throw new UnexpectedValueException(sprintf(
312
                '"%s" [%s] is not a model structure property on [%s].',
313
                $propertyIdent,
314
                (is_object($property) ? get_class($property) : gettype($property)),
315
                (is_object($obj) ? get_class($obj) : gettype($obj))
316
            ));
317
        }
318
319
        return $this;
320
    }
321
322
    /**
323
     * Retrieve the form group's storage property master.
324
     *
325
     * @throws RuntimeException If the storage property was not previously set.
326
     * @return ModelStructureProperty
327
     */
328
    public function storageProperty()
329
    {
330
        if ($this->storageProperty === null) {
0 ignored issues
show
Deprecated Code introduced by
The property Charcoal\Admin\Widget\Fo...Group::$storageProperty has been deprecated. ( Ignorable by Annotation )

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

330
        if (/** @scrutinizer ignore-deprecated */ $this->storageProperty === null) {
Loading history...
331
            throw new RuntimeException(sprintf(
332
                'Storage property owner is not defined for "%s"',
333
                get_class($this)
334
            ));
335
        }
336
337
        return $this->storageProperty;
0 ignored issues
show
Deprecated Code introduced by
The property Charcoal\Admin\Widget\Fo...Group::$storageProperty has been deprecated. ( Ignorable by Annotation )

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

337
        return /** @scrutinizer ignore-deprecated */ $this->storageProperty;
Loading history...
338
    }
339
340
    /**
341
     * Retrieve the properties from the storage property's structure.
342
     *
343
     * @return array
344
     */
345
    public function structProperties()
346
    {
347
        $property = $this->storageProperty();
348
349
        if ($property) {
0 ignored issues
show
introduced by
$property is of type Charcoal\Property\ModelStructureProperty, thus it always evaluated to true.
Loading history...
350
            $struct = $property->structureMetadata();
351
352
            if (isset($struct['properties'])) {
353
                return $struct['properties'];
354
            }
355
        }
356
357
        return [];
358
    }
359
360
    /**
361
     * Finalize the form group's properies, entries, and layout.
362
     *
363
     * @param  boolean $reload Rebuild the form group's structure.
364
     * @return void
365
     */
366
    protected function finalizeStructure($reload = false)
367
    {
368
        if ($reload || !$this->isStructureFinalized) {
369
            $this->isStructureFinalized = true;
370
371
            $property = $this->storageProperty();
372
373
            $struct = $property->structureMetadata();
374
            $formGroup = null;
375
            if (isset($struct['admin']['default_form_group'])) {
376
                $groupName = $struct['admin']['default_form_group'];
377
                if (isset($struct['admin']['form_groups'][$groupName])) {
378
                    $formGroup = $struct['admin']['form_groups'][$groupName];
379
                }
380
            } elseif (isset($struct['admin']['form_group'])) {
381
                if (is_string($struct['admin']['form_group'])) {
382
                    $groupName = $struct['admin']['form_group'];
383
                    if (isset($struct['admin']['form_groups'][$groupName])) {
384
                        $formGroup = $struct['admin']['form_groups'][$groupName];
385
                    }
386
                } else {
387
                    $formGroup = $struct['admin']['form_group'];
388
                }
389
            }
390
391
            if ($formGroup) {
392
                if (is_array($this->rawData)) {
393
                    $widgetData = array_replace($formGroup, $this->rawData);
394
                    $this->setData($widgetData);
395
                } else {
396
                    $this->setData($formGroup);
397
                }
398
            }
399
        }
400
    }
401
402
    /**
403
     * Parse the form group and model properties.
404
     *
405
     * @return array
406
     */
407
    protected function parsedFormProperties()
408
    {
409
        if ($this->parsedFormProperties === null) {
410
            $this->finalizeStructure();
411
412
            $groupProperties     = $this->groupProperties();
413
            $availableProperties = $this->structProperties();
414
415
            $structProperties = [];
416
            if (!empty($groupProperties)) {
417
                foreach ($groupProperties as $propertyIdent => $propertyMetadata) {
418
                    if (is_string($propertyMetadata)) {
419
                        $propertyIdent    = $propertyMetadata;
420
                        $propertyMetadata = null;
421
                    }
422
423
                    if (!isset($availableProperties[$propertyIdent])) {
424
                        continue;
425
                    }
426
427
                    if (is_array($propertyMetadata)) {
428
                        $propertyMetadata = array_merge($propertyMetadata, $availableProperties[$propertyIdent]);
429
                    } else {
430
                        $propertyMetadata = $availableProperties[$propertyIdent];
431
                    }
432
433
                    $structProperties[$propertyIdent] = $propertyMetadata;
434
                }
435
            }
436
437
            if (empty($structProperties)) {
438
                $structProperties = $availableProperties;
439
            }
440
441
            $this->parsedFormProperties = $structProperties;
442
        }
443
444
        return $this->parsedFormProperties;
445
    }
446
447
    /**
448
     * Retrieve the object's properties from the form.
449
     *
450
     * @todo   Add support to StructureProperty and StructureFormGroup for multiple-values:
451
     *         `($store->multiple() ? '%1$s['.uniqid().'][%2$s]' : '%1$s[%2$s]' )`.
452
     * @throws UnexpectedValueException If a property data is invalid.
453
     * @return \Charcoal\Admin\Widget\FormPropertyWidget[]|\Generator
454
     */
455
    public function formProperties()
456
    {
457
        $this->finalizeStructure();
458
459
        $store = $this->storageProperty();
460
        $form  = $this->form();
461
        $obj   = $this->obj();
462
        $entry = $obj[$store->ident()];
463
464
        if (is_string($entry)) {
465
            $entry = $store->parseVal($entry);
466
        }
467
468
        $propertyIdentPattern = '%1$s[%2$s]';
469
470
        $propPreferences  = $this->propertiesOptions();
471
        $structProperties = $this->parsedFormProperties();
472
473
        foreach ($structProperties as $propertyIdent => $propertyMetadata) {
474
            if (is_array($propertyMetadata)) {
475
                $propertyMetadata['ident'] = $propertyIdent;
476
            }
477
478
            if (method_exists($obj, 'filterPropertyMetadata')) {
479
                $propertyMetadata = $obj->filterPropertyMetadata($propertyMetadata, $propertyIdent);
480
            }
481
482
            if (is_bool($propertyMetadata) && $propertyMetadata === false) {
483
                continue;
484
            }
485
486
            if (!is_array($propertyMetadata)) {
487
                throw new UnexpectedValueException(sprintf(
488
                    'Invalid property data for "%1$s", received %2$s',
489
                    $propertyIdent,
490
                    (is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata))
491
                ));
492
            }
493
494
            if (isset($propertyMetadata['active']) && $propertyMetadata['active'] === false) {
495
                continue;
496
            }
497
498
            $subPropertyIdent = sprintf(
499
                $propertyIdentPattern,
500
                ($store['input_name'] ?: $store->ident()),
501
                $propertyIdent
502
            );
503
            $propertyMetadata['input_name'] = $subPropertyIdent;
504
505
            $formProperty = $form->createFormProperty();
0 ignored issues
show
Bug introduced by
The method createFormProperty() does not exist on Charcoal\Ui\Form\FormInterface. It seems like you code against a sub-type of Charcoal\Ui\Form\FormInterface such as Charcoal\Admin\Widget\FormWidget. ( Ignorable by Annotation )

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

505
            /** @scrutinizer ignore-call */ 
506
            $formProperty = $form->createFormProperty();
Loading history...
506
            $formProperty->setViewController($this->viewController());
507
            $formProperty->setPropertyIdent($subPropertyIdent);
508
            $formProperty->setData($propertyMetadata);
509
510
            if (!empty($propPreferences[$propertyIdent])) {
511
                $propertyOptions = $propPreferences[$propertyIdent];
512
513
                if (is_array($propertyOptions)) {
514
                    $formProperty->merge($propertyOptions);
515
                }
516
            }
517
518
            if (!empty($entry) && isset($entry[$propertyIdent])) {
519
                $val = $entry[$propertyIdent];
520
                $formProperty->setPropertyVal($val);
521
            }
522
523
            if (!$formProperty->l10nMode()) {
524
                $formProperty->setL10nMode($this->l10nMode());
525
            }
526
527
            if ($formProperty instanceof FormInputInterface) {
528
                $formProperty->setFormGroup($this);
529
            }
530
531
            if ($formProperty->hidden()) {
532
                $form->addHiddenProperty($subPropertyIdent, $formProperty);
0 ignored issues
show
Bug introduced by
The method addHiddenProperty() does not exist on Charcoal\Ui\Form\FormInterface. It seems like you code against a sub-type of Charcoal\Ui\Form\FormInterface such as Charcoal\Admin\Widget\FormWidget. ( Ignorable by Annotation )

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

532
                $form->/** @scrutinizer ignore-call */ 
533
                       addHiddenProperty($subPropertyIdent, $formProperty);
Loading history...
533
            } else {
534
                yield $propertyIdent => $formProperty;
535
            }
536
537
            if ($formProperty instanceof FormInputInterface) {
538
                $formProperty->clearFormGroup();
0 ignored issues
show
Bug introduced by
The method clearFormGroup() does not exist on Charcoal\Ui\FormInput\FormInputInterface. It seems like you code against a sub-type of Charcoal\Ui\FormInput\FormInputInterface such as Charcoal\Admin\Widget\FormGroup\StructureFormGroup or Charcoal\Admin\Widget\FormPropertyWidget. ( Ignorable by Annotation )

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

538
                $formProperty->/** @scrutinizer ignore-call */ 
539
                               clearFormGroup();
Loading history...
539
            }
540
        }
541
    }
542
}
543