FormWidget   F
last analyzed

Complexity

Total Complexity 65

Size/Duplication

Total Lines 509
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 159
dl 0
loc 509
rs 3.2
c 0
b 0
f 0
wmc 65

23 Methods

Rating   Name   Duplication   Size   Complexity  
A defaultGroupType() 0 3 1
A acceptedRequestData() 0 9 1
A setDependencies() 0 14 1
A dataFromRequest() 0 3 1
A addHiddenProperties() 0 7 2
A sortSidebarsByPriority() 0 12 3
A sidebars() 0 18 4
A createFormProperty() 0 8 2
A formProperties() 0 16 4
A defaultSubmitLabel() 0 3 1
B addSidebar() 0 39 8
A addHiddenProperty() 0 18 5
A sortItemsByPriority() 0 12 3
A setSidebars() 0 8 2
A widgetFactory() 0 10 2
A addFormProperties() 0 7 2
A addFormProperty() 0 18 5
A hiddenProperties() 0 8 3
A setWidgetFactory() 0 3 1
A submitLabel() 0 7 2
B getOrCreateFormProperty() 0 50 10
A setHiddenProperties() 0 7 1
A setFormProperties() 0 7 1

How to fix   Complexity   

Complex Class

Complex classes like FormWidget 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 FormWidget, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Charcoal\Admin\Widget;
4
5
use Charcoal\Admin\Property\HierarchicalObjectProperty;
6
use Exception;
7
use InvalidArgumentException;
8
use RuntimeException;
9
10
// From Pimple
11
use Pimple\Container;
12
13
// From PSR-7
14
use Psr\Http\Message\RequestInterface;
15
16
// From 'charcoal-factory'
17
use Charcoal\Factory\FactoryInterface;
18
19
/// From 'charcoal-ui'
20
use Charcoal\Ui\Form\FormInterface;
21
use Charcoal\Ui\Form\FormTrait;
22
use Charcoal\Ui\Layout\LayoutAwareInterface;
23
use Charcoal\Ui\Layout\LayoutAwareTrait;
24
use Charcoal\Ui\PrioritizableInterface;
25
26
// From 'charcoal-admin'
27
use Charcoal\Admin\AdminWidget;
28
use Charcoal\Admin\Ui\FormSidebarInterface;
29
use Charcoal\Admin\Ui\ObjectContainerInterface;
30
use Charcoal\Admin\Support\HttpAwareTrait;
31
use Charcoal\Admin\Widget\FormPropertyWidget;
32
33
/**
34
 * A Basic Admin Form
35
 *
36
 * For submitting information to a web server.
37
 *
38
 * The widget is a variant of {@see \Charcoal\Ui\Form\AbstractForm}.
39
 */
40
class FormWidget extends AdminWidget implements
41
    FormInterface,
42
    LayoutAwareInterface
43
{
44
    use FormTrait;
45
    use HttpAwareTrait;
46
    use LayoutAwareTrait;
47
48
    /**
49
     * The form's sidebars.
50
     *
51
     * @var array
52
     */
53
    protected $sidebars = [];
54
55
    /**
56
     * The form's controls.
57
     *
58
     * @var array
59
     */
60
    protected $formProperties = [];
61
62
    /**
63
     * The form's hidden controls.
64
     *
65
     * @var array
66
     */
67
    protected $hiddenProperties = [];
68
69
    /**
70
     * Label for the form submission button.
71
     *
72
     * @var \Charcoal\Translator\Translation|string
73
     */
74
    protected $submitLabel;
75
76
    /**
77
     * Store the factory instance for the current class.
78
     *
79
     * @var FactoryInterface
80
     */
81
    private $widgetFactory;
82
83
    /**
84
     * @param array $data Optional. The form property data to set.
85
     * @return FormPropertyWidget
86
     */
87
    public function createFormProperty(array $data = null)
88
    {
89
        $p = $this->widgetFactory()->create(FormPropertyWidget::class);
90
        if ($data !== null) {
91
            $p->setData($data);
92
        }
93
94
        return $p;
95
    }
96
97
    /**
98
     * @param string $ident Property ident.
99
     * @param array  $data  Property metadata.
100
     * @return \Charcoal\Admin\Widget\FormPropertyWidget|mixed
101
     */
102
    public function getOrCreateFormProperty($ident, array $data = null)
103
    {
104
        if ($ident && isset($this->formProperties[$ident])) {
105
            $p = $this->formProperties[$ident];
106
107
            if ($data !== null) {
108
                $p->setData($data);
109
            }
110
111
            $this->formProperties[$ident] = $p;
112
113
            return $this->formProperties[$ident];
114
        }
115
116
        if ($ident && isset($this->hiddenProperties[$ident])) {
117
            $p = $this->hiddenProperties[$ident];
118
119
            if ($data !== null) {
120
                $p->setData($data);
121
            }
122
123
            $this->hiddenProperties[$ident] = $p;
124
125
            return $this->hiddenProperties[$ident];
126
        }
127
128
        $prop = $this->createFormProperty($data);
129
        $prop->setPropertyIdent($ident);
130
131
        if ($this instanceof ObjectContainerInterface) {
132
            $prop->setPropertyVal($this->obj()[$ident]);
133
134
            if ($prop->propertyType() === HierarchicalObjectProperty::class) {
135
                $prop->merge(['obj_id' => $this->obj()->id()]);
136
            }
137
        }
138
139
        $prop->setViewController($this->viewController());
140
141
        if ($prop->hidden()) {
142
            $prop->setInputType(FormPropertyWidget::HIDDEN_FORM_CONTROL);
143
144
            $this->hiddenProperties[$ident] = $prop;
145
146
            return $this->hiddenProperties[$ident];
147
        }
148
149
        $this->formProperties[$ident] = $prop;
150
151
        return $this->formProperties[$ident];
152
    }
153
154
    /**
155
     * @param array $sidebars The form sidebars.
156
     * @return self
157
     */
158
    public function setSidebars(array $sidebars)
159
    {
160
        $this->sidebars = [];
161
        foreach ($sidebars as $sidebarIdent => $sidebar) {
162
            $this->addSidebar($sidebarIdent, $sidebar);
163
        }
164
165
        return $this;
166
    }
167
168
    /**
169
     * @param string                     $sidebarIdent The sidebar identifier.
170
     * @param array|FormSidebarInterface $sidebar      The sidebar data or object.
171
     * @throws InvalidArgumentException If the ident is not a string or the sidebar is not valid.
172
     * @return self
173
     */
174
    public function addSidebar($sidebarIdent, $sidebar)
175
    {
176
        if (!is_string($sidebarIdent)) {
0 ignored issues
show
introduced by
The condition is_string($sidebarIdent) is always true.
Loading history...
177
            throw new InvalidArgumentException(
178
                'Sidebar ident must be a string'
179
            );
180
        }
181
        if (($sidebar instanceof FormSidebarInterface)) {
182
            $this->sidebars[$sidebarIdent] = $sidebar;
183
        } elseif (is_array($sidebar)) {
0 ignored issues
show
introduced by
The condition is_array($sidebar) is always true.
Loading history...
184
            if (isset($sidebar['widget_type'])) {
185
                $s = $this->widgetFactory()->create($sidebar['widget_type']);
186
                $s->setTemplate($sidebar['widget_type']);
187
            }
188
189
            if (!isset($s) || !($s instanceof FormSidebarInterface)) {
190
                $s = $this->widgetFactory()->create('charcoal/admin/widget/form-sidebar');
191
            }
192
193
            $s->setForm($this->formWidget());
194
            $s->setData($sidebar);
0 ignored issues
show
Bug introduced by
The method setData() does not exist on Charcoal\Admin\Ui\FormSidebarInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Charcoal\Admin\Ui\FormSidebarInterface. ( Ignorable by Annotation )

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

194
            $s->/** @scrutinizer ignore-call */ 
195
                setData($sidebar);
Loading history...
195
196
            // Test sidebar vs. ACL roles
197
            $authUser = $this->authenticator()->authenticate();
198
            if (!$this->authorizer()->userAllowed($authUser, $s->requiredGlobalAclPermissions())) {
0 ignored issues
show
Bug introduced by
It seems like $authUser can also be of type null; however, parameter $user of Charcoal\User\Authorizer::userAllowed() does only seem to accept Charcoal\User\UserInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

198
            if (!$this->authorizer()->userAllowed(/** @scrutinizer ignore-type */ $authUser, $s->requiredGlobalAclPermissions())) {
Loading history...
Bug introduced by
The method requiredGlobalAclPermissions() does not exist on Charcoal\Admin\Ui\FormSidebarInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Charcoal\Admin\Ui\FormSidebarInterface. ( Ignorable by Annotation )

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

198
            if (!$this->authorizer()->userAllowed($authUser, $s->/** @scrutinizer ignore-call */ requiredGlobalAclPermissions())) {
Loading history...
199
                header('HTTP/1.0 403 Forbidden');
200
                header('Location: '.$this->adminUrl().'login');
201
202
                return $this;
203
            }
204
205
            $this->sidebars[$sidebarIdent] = $s;
206
        } else {
207
            throw new InvalidArgumentException(
208
                'Sidebar must be a FormSidebarWidget object or an array'
209
            );
210
        }
211
212
        return $this;
213
    }
214
215
    /**
216
     * Yield the form sidebar(s).
217
     *
218
     * @return \Generator
219
     */
220
    public function sidebars()
221
    {
222
        $sidebars = $this->sidebars;
223
        uasort($sidebars, [$this, 'sortSidebarsByPriority']);
224
        foreach ($sidebars as $sidebarIdent => $sidebar) {
225
            if (!$sidebar->active()) {
226
                continue;
227
            }
228
229
            if ($sidebar->template()) {
230
                $template = $sidebar->template();
231
            } else {
232
                $template = 'charcoal/admin/widget/form.sidebar';
233
            }
234
235
            $GLOBALS['widget_template'] = $template;
236
            yield $sidebarIdent => $sidebar;
237
            $GLOBALS['widget_template'] = '';
238
        }
239
    }
240
241
    /**
242
     * Replace property controls to the form.
243
     *
244
     * @param  array $properties The form properties.
245
     * @return self
246
     */
247
    public function setFormProperties(array $properties)
248
    {
249
        $this->formProperties = [];
250
251
        $this->addFormProperties($properties);
252
253
        return $this;
254
    }
255
256
    /**
257
     * Add property controls to the form.
258
     *
259
     * @param  array $properties The form properties.
260
     * @return self
261
     */
262
    public function addFormProperties(array $properties)
263
    {
264
        foreach ($properties as $propertyIdent => $property) {
265
            $this->addFormProperty($propertyIdent, $property);
266
        }
267
268
        return $this;
269
    }
270
271
    /**
272
     * Add a property control to the form.
273
     *
274
     * If a given property uses a hidden form control, the form property will be
275
     * added to {@see FormWidget::$hiddenProperties}.
276
     *
277
     * @param  string                   $propertyIdent The property identifier.
278
     * @param  array|FormPropertyWidget $formProperty  The property object or structure.
279
     * @throws InvalidArgumentException If the identifier or the property is invalid.
280
     * @return FormInterface Chainable
281
     */
282
    public function addFormProperty($propertyIdent, $formProperty)
283
    {
284
        if (!is_string($propertyIdent)) {
0 ignored issues
show
introduced by
The condition is_string($propertyIdent) is always true.
Loading history...
285
            throw new InvalidArgumentException(
286
                'Property ident must be a string'
287
            );
288
        }
289
290
        if (is_array($formProperty)) {
291
            $this->getOrCreateFormProperty($propertyIdent, $formProperty);
292
        } elseif (!$formProperty instanceof FormPropertyWidget) {
0 ignored issues
show
introduced by
$formProperty is always a sub-type of Charcoal\Admin\Widget\FormPropertyWidget.
Loading history...
293
            throw new InvalidArgumentException(sprintf(
294
                'Property must be an array or an instance of FormPropertyWidget, received %s',
295
                is_object($formProperty) ? get_class($formProperty) : gettype($formProperty)
296
            ));
297
        }
298
299
        return $this;
300
    }
301
302
    /**
303
     * Yield the form's property controls.
304
     *
305
     * @return \Generator
306
     */
307
    public function formProperties()
308
    {
309
        $sidebars = $this->sidebars;
310
        if (!is_array($sidebars)) {
0 ignored issues
show
introduced by
The condition is_array($sidebars) is always true.
Loading history...
311
            yield null;
312
        } else {
313
            foreach ($this->formProperties as $formProperty) {
314
                if ($formProperty->active() === false) {
315
                    continue;
316
                }
317
318
                $GLOBALS['widget_template'] = $formProperty->inputType();
319
320
                yield $formProperty->propertyIdent() => $formProperty;
321
322
                $GLOBALS['widget_template'] = '';
323
            }
324
        }
325
    }
326
327
    /**
328
     * Replace hidden property controls to the form.
329
     *
330
     * @param  array $properties The hidden form properties.
331
     * @return FormInterface Chainable
332
     */
333
    public function setHiddenProperties(array $properties)
334
    {
335
        $this->hiddenProperties = [];
336
337
        $this->addHiddenProperties($properties);
338
339
        return $this;
340
    }
341
342
    /**
343
     * Add hidden property controls to the form.
344
     *
345
     * @param  array $properties The hidden form properties.
346
     * @return FormInterface Chainable
347
     */
348
    public function addHiddenProperties(array $properties)
349
    {
350
        foreach ($properties as $propertyIdent => $property) {
351
            $this->addHiddenProperty($propertyIdent, $property);
352
        }
353
354
        return $this;
355
    }
356
357
    /**
358
     * Add a hidden property control to the form.
359
     *
360
     * @param  string                      $propertyIdent The property identifier.
361
     * @param  array|FormPropertyInterface $formProperty  The property object or structure.
0 ignored issues
show
Bug introduced by
The type Charcoal\Admin\Widget\FormPropertyInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
362
     * @throws InvalidArgumentException If the identifier or the property is invalid.
363
     * @return FormInterface Chainable
364
     */
365
    public function addHiddenProperty($propertyIdent, $formProperty)
366
    {
367
        if (!is_string($propertyIdent)) {
0 ignored issues
show
introduced by
The condition is_string($propertyIdent) is always true.
Loading history...
368
            throw new InvalidArgumentException(
369
                'Property ident must be a string'
370
            );
371
        }
372
373
        if (is_array($formProperty)) {
374
            $this->getOrCreateFormProperty($propertyIdent, $formProperty);
375
        } elseif (!$formProperty instanceof FormPropertyWidget) {
376
            throw new InvalidArgumentException(sprintf(
377
                'Property must be an array or an instance of FormPropertyWidget, received %s',
378
                is_object($formProperty) ? get_class($formProperty) : gettype($formProperty)
379
            ));
380
        }
381
382
        return $this;
383
    }
384
385
    /**
386
     * Yield the form's hidden property controls.
387
     *
388
     * @return \Generator
389
     */
390
    public function hiddenProperties()
391
    {
392
        foreach ($this->hiddenProperties as $formProperty) {
393
            if ($formProperty->active() === false) {
394
                continue;
395
            }
396
397
            yield $formProperty->propertyIdent() => $formProperty;
398
        }
399
    }
400
401
    /**
402
     * Retrieve the label for the form submission button.
403
     *
404
     * @return \Charcoal\Translator\Translation|string|null
405
     */
406
    public function submitLabel()
407
    {
408
        if ($this->submitLabel === null) {
409
            $this->submitLabel = $this->defaultSubmitLabel();
410
        }
411
412
        return $this->submitLabel;
413
    }
414
415
    /**
416
     * Retrieve the default label for the form submission button.
417
     *
418
     * @return \Charcoal\Translator\Translation|null
419
     */
420
    public function defaultSubmitLabel()
421
    {
422
        return $this->translator()->translation('Save');
423
    }
424
425
    /**
426
     * @return string
427
     */
428
    public function defaultGroupType()
429
    {
430
        return 'charcoal/admin/widget/form-group/generic';
431
    }
432
433
    /**
434
     * @param  Container $container The DI container.
435
     * @return void
436
     */
437
    protected function setDependencies(Container $container)
438
    {
439
        parent::setDependencies($container);
440
441
        // Satisfies HttpAwareTrait dependencies
442
        $this->setHttpRequest($container['request']);
443
444
        $this->setWidgetFactory($container['widget/factory']);
445
446
        // Satisfies FormInterface
447
        $this->setFormGroupFactory($container['form/group/factory']);
448
449
        // Satisfies LayoutAwareInterface
450
        $this->setLayoutBuilder($container['layout/builder']);
451
    }
452
453
    /**
454
     * Retrieve the widget factory.
455
     *
456
     * @throws RuntimeException If the widget factory was not previously set.
457
     * @return FactoryInterface
458
     */
459
    protected function widgetFactory()
460
    {
461
        if ($this->widgetFactory === null) {
462
            throw new RuntimeException(sprintf(
463
                'Widget Factory is not defined for "%s"',
464
                get_class($this)
465
            ));
466
        }
467
468
        return $this->widgetFactory;
469
    }
470
471
    /**
472
     * Fetch metadata from the current request.
473
     *
474
     * @return array
475
     */
476
    protected function dataFromRequest()
477
    {
478
        return $this->httpRequest()->getParams($this->acceptedRequestData());
0 ignored issues
show
Bug introduced by
The method getParams() does not exist on Psr\Http\Message\RequestInterface. It seems like you code against a sub-type of Psr\Http\Message\RequestInterface such as Slim\Http\Request. ( Ignorable by Annotation )

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

478
        return $this->httpRequest()->/** @scrutinizer ignore-call */ getParams($this->acceptedRequestData());
Loading history...
479
    }
480
481
    /**
482
     * Retrieve the accepted metadata from the current request.
483
     *
484
     * @return array
485
     */
486
    protected function acceptedRequestData()
487
    {
488
        return [
489
            'form_ident',
490
            'form_data',
491
            'l10n_mode',
492
            'group_display_mode',
493
            'next_url',
494
            'tab_ident',
495
        ];
496
    }
497
498
    /**
499
     * Comparison function used by {@see uasort()}.
500
     *
501
     * @param  PrioritizableInterface $a Sortable entity A.
502
     * @param  PrioritizableInterface $b Sortable entity B.
503
     * @return integer Sorting value: -1 or 1.
504
     */
505
    protected function sortItemsByPriority(
506
        PrioritizableInterface $a,
507
        PrioritizableInterface $b
508
    ) {
509
        $priorityA = $a->priority();
510
        $priorityB = $b->priority();
511
512
        if ($priorityA === $priorityB) {
513
            return 0;
514
        }
515
516
        return ($priorityA < $priorityB) ? (-1) : 1;
517
    }
518
519
    /**
520
     * To be called with {@see uasort()}.
521
     *
522
     * @param  FormSidebarInterface $a Sortable entity A.
523
     * @param  FormSidebarInterface $b Sortable entity B.
524
     * @return integer Sorting value: -1, 0, or 1
525
     */
526
    protected function sortSidebarsByPriority(
527
        FormSidebarInterface $a,
528
        FormSidebarInterface $b
529
    ) {
530
        $a = $a->priority();
531
        $b = $b->priority();
532
533
        if ($a === $b) {
534
            return 0;
535
        }
536
537
        return ($a < $b) ? (-1) : 1;
538
    }
539
540
    /**
541
     * Set an widget factory.
542
     *
543
     * @param FactoryInterface $factory The factory to create widgets.
544
     * @return void
545
     */
546
    private function setWidgetFactory(FactoryInterface $factory)
547
    {
548
        $this->widgetFactory = $factory;
549
    }
550
}
551