Completed
Pull Request — master (#2)
by
unknown
14:01
created

FormTrait::finalizeFormGroups()   D

Complexity

Conditions 24
Paths 41

Size

Total Lines 71

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 71
rs 4.1666
c 0
b 0
f 0
cc 24
nc 41
nop 1

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\Ui\Form;
4
5
use Charcoal\Admin\Widget\FormWidget;
6
use Charcoal\Ui\ConditionalizableInterface;
7
use Exception;
8
use InvalidArgumentException;
9
10
// From 'charcoal-factory'
11
use Charcoal\Factory\FactoryInterface;
12
13
// From 'charcoal-ui'
14
use Charcoal\Ui\Form\FormInterface;
15
use Charcoal\Ui\FormGroup\FormGroupInterface;
16
use Charcoal\Ui\PrioritizableInterface;
17
18
/**
19
 * Provides an implementation of {@see FormInterface}.
20
 */
21
trait FormTrait
22
{
23
    /**
24
     * The URI of a program that processes the form information.
25
     *
26
     * @var string
27
     */
28
    private $action = '';
29
30
    /**
31
     * The HTTP method that the browser uses to submit the form.
32
     *
33
     * @var string
34
     */
35
    private $method = 'post';
36
37
    /**
38
     * The form's display mode for multilingual fields.
39
     *
40
     * @var string
41
     */
42
    private $l10nMode = 'loop_inputs';
43
44
    /**
45
     * The form's display mode for groups.
46
     *
47
     * @var string
48
     */
49
    protected $groupDisplayMode;
50
51
    /**
52
     * The form's field groups.
53
     *
54
     * @var FormGroupInterface[]
55
     */
56
    protected $groups = [];
57
58
    /**
59
     * The form's predefined data.
60
     *
61
     * @var array $formData
62
     */
63
    private $formData = [];
64
65
    /**
66
     * Store the form's metadata instance.
67
     *
68
     * @var MetadataInterface
69
     */
70
    private $metadata;
71
72
    /**
73
     * Store the form's group factory instance.
74
     *
75
     * @var FactoryInterface
76
     */
77
    protected $formGroupFactory;
78
79
    /**
80
     * Store the form's group callback.
81
     *
82
     * @var callable
83
     */
84
    private $groupCallback;
85
86
    /**
87
     * Store the tab ident received through REQUEST.
88
     *
89
     * @var string
90
     */
91
    private $tabIdent;
92
93
    /**
94
     * Store the current form group.
95
     *
96
     * @var string
97
     */
98
    private $selectedFormGroup;
99
100
    /**
101
     * Comparison function used by {@see uasort()}.
102
     *
103
     * @param  PrioritizableInterface $a Sortable entity A.
104
     * @param  PrioritizableInterface $b Sortable entity B.
105
     * @return integer Sorting value: -1 or 1.
106
     */
107
    abstract protected function sortItemsByPriority(
108
        PrioritizableInterface $a,
109
        PrioritizableInterface $b
110
    );
111
112
    /**
113
     * @param FactoryInterface $factory A factory, to create customized form gorup objects.
114
     * @return FormInterface Chainable
115
     */
116
    public function setFormGroupFactory(FactoryInterface $factory)
117
    {
118
        $this->formGroupFactory = $factory;
119
120
        return $this;
121
    }
122
123
    /**
124
     * @throws Exception If the form group factory object was not set / injected.
125
     * @return FormInterface Chainable
126
     */
127
    protected function formGroupFactory()
128
    {
129
        if ($this->formGroupFactory === null) {
130
            throw new Exception(
131
                'Form group factory was not set.'
132
            );
133
        }
134
135
        return $this->formGroupFactory;
136
    }
137
138
    /**
139
     * @param callable $cb The group callback.
140
     * @return FormInterface Chainable
141
     */
142
    public function setGroupCallback(callable $cb)
143
    {
144
        $this->groupCallback = $cb;
145
146
        return $this;
147
    }
148
149
    /**
150
     * @param string $action The "action" value, typically a URL.
151
     * @throws InvalidArgumentException If the action argument is not a string.
152
     * @return FormInterface Chainable
153
     */
154
    public function setAction($action)
155
    {
156
        if (!is_string($action)) {
157
            throw new InvalidArgumentException(
158
                'Action must be a string'
159
            );
160
        }
161
        $this->action = $action;
162
163
        return $this;
164
    }
165
166
    /**
167
     * @return string
168
     */
169
    public function action()
170
    {
171
        return $this->action;
172
    }
173
174
    /**
175
     * Set the method (forcing lowercase) to "post" or "get".
176
     *
177
     * @param string $method Either "post" or "get".
178
     * @throws InvalidArgumentException If the method is not post or get.
179
     * @return FormInterface Chainable
180
     */
181
    public function setMethod($method)
182
    {
183
        $method = strtolower($method);
184
        if (!in_array($method, ['post', 'get'])) {
185
            throw new InvalidArgumentException(
186
                'Method must be "post" or "get"'
187
            );
188
        }
189
        $this->method = $method;
190
191
        return $this;
192
    }
193
194
    /**
195
     * @return string Either "post" or "get".
196
     */
197
    public function method()
198
    {
199
        return $this->method;
200
    }
201
202
    /**
203
     * @param string $mode The l10n mode.
204
     * @return FormInterface Chainable
205
     */
206
    public function setL10nMode($mode)
207
    {
208
        $this->l10nMode = $mode;
209
210
        return $this;
211
    }
212
213
    /**
214
     * @return string
215
     */
216
    public function l10nMode()
217
    {
218
        return $this->l10nMode;
219
    }
220
221
    /**
222
     * Set the object's form groups.
223
     *
224
     * @param array $groups A collection of group structures.
225
     * @return FormInterface Chainable
226
     */
227
    public function setGroups(array $groups)
228
    {
229
        $this->groups = [];
230
231
        foreach ($groups as $groupIdent => $group) {
232
            $this->addGroup($groupIdent, $group);
233
        }
234
235
        return $this;
236
    }
237
238
    /**
239
     * @return mixed
240
     */
241
    public function tabIdent()
242
    {
243
        return $this->tabIdent;
244
    }
245
246
    /**
247
     * @param mixed $tabIdent Store the tab ident received through REQUEST.
248
     * @return self
249
     */
250
    public function setTabIdent($tabIdent)
251
    {
252
        $this->tabIdent = $tabIdent;
253
254
        return $this;
255
    }
256
257
    /**
258
     * @return string
259
     */
260
    public function selectedFormGroup()
261
    {
262
        return $this->selectedFormGroup;
263
    }
264
265
    /**
266
     * @param mixed $selectedFormGroup Store the current form group.
267
     * @return self
268
     */
269
    public function setSelectedFormGroup($selectedFormGroup)
270
    {
271
        $this->selectedFormGroup = $selectedFormGroup;
272
273
        return $this;
274
    }
275
276
    /**
277
     * Add a form group.
278
     *
279
     * @param  string                   $groupIdent The group identifier.
280
     * @param  array|FormGroupInterface $group      The group object or structure.
281
     * @throws InvalidArgumentException If the identifier is not a string or the group is invalid.
282
     * @return FormInterface Chainable
283
     */
284
    public function addGroup($groupIdent, $group)
285
    {
286
        if ($group === false || $group === null) {
287
            return $this;
288
        }
289
290
        $group = $this->parseFormGroup($groupIdent, $group);
291
292
        if (isset($group['ident'])) {
293
            $groupIdent = $group['ident'];
294
        }
295
296
        $this->groups[$groupIdent] = $group;
297
298
        return $this;
299
    }
300
301
    /**
302
     * Parse a form group.
303
     *
304
     * @param  string                   $groupIdent The group identifier.
305
     * @param  array|FormGroupInterface $group      The group object or structure.
306
     * @throws InvalidArgumentException If the identifier is not a string or the group is invalid.
307
     * @return FormGroupInterface
308
     */
309
    protected function parseFormGroup($groupIdent, $group)
310
    {
311
        if (!is_string($groupIdent)) {
312
            throw new InvalidArgumentException(
313
                'Group identifier must be a string'
314
            );
315
        }
316
317
        if ($group instanceof FormGroupInterface) {
318
            $group = $this->updateFormGroup($group, null, $groupIdent);
319
        } elseif (is_array($group)) {
320
            $data = $group;
321
322
            if (!isset($data['ident'])) {
323
                $data['ident'] = $groupIdent;
324
            }
325
326
            $group = $this->createFormGroup($data);
327
        } else {
328
            throw new InvalidArgumentException(sprintf(
329
                'Group must be an instance of %s or an array of form group options, received %s',
330
                'FormGroupInterface',
331
                (is_object($group) ? get_class($group) : gettype($group))
332
            ));
333
        }
334
335
        return $group;
336
    }
337
338
    /**
339
     * Create a new form group widget.
340
     *
341
     * @param  array|null $data Optional. The form group data to set.
342
     * @return FormGroupInterface
343
     */
344
    protected function createFormGroup(array $data = null)
345
    {
346
        if (isset($data['type'])) {
347
            $type = $data['type'];
348
        } else {
349
            $type = $this->defaultGroupType();
350
        }
351
352
        $group = $this->formGroupFactory()->create($type);
0 ignored issues
show
Bug introduced by
The method create() does not seem to exist on object<Charcoal\Ui\Form\FormInterface>.

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...
353
        $group->setForm($this->formWidget());
354
355
        if ($data !== null) {
356
            $group->setData($data);
357
        }
358
359
        return $group;
360
    }
361
362
    /**
363
     * Update the given form group widget.
364
     *
365
     * @param  FormGroupInterface $group      The form group to update.
366
     * @param  array|null         $groupData  Optional. The new group data to apply.
367
     * @param  string|null        $groupIdent Optional. The new group identifier.
368
     * @return FormGroupInterface
369
     */
370
    protected function updateFormGroup(
371
        FormGroupInterface $group,
372
        array $groupData = null,
373
        $groupIdent = null
374
    ) {
375
        $group->setForm($this->formWidget());
376
377
        if ($groupData !== null) {
378
            $group->setData($groupData);
379
        }
380
381
        if ($groupIdent !== null) {
382
            $group->setIdent($groupIdent);
383
        }
384
385
        return $group;
386
    }
387
388
    /**
389
     * Retrieve the default form group class name.
390
     *
391
     * @return string
392
     */
393
    public function defaultGroupType()
394
    {
395
        return 'charcoal/ui/form-group/generic';
396
    }
397
398
    /**
399
     * Retrieve the form groups.
400
     *
401
     * @param callable $groupCallback Optional callback applied to each form group.
402
     * @return FormGroupInterface[]|Generator
403
     */
404
    public function groups(callable $groupCallback = null)
405
    {
406
        $groups = $this->groups;
407
        uasort($groups, [ $this, 'sortItemsByPriority' ]);
408
409
        $groupCallback = (isset($groupCallback) ? $groupCallback : $this->groupCallback);
410
411
        $groups = $this->finalizeFormGroups($groups);
412
413
        $i = 1;
414
        foreach ($groups as $group) {
415
            if ($groupCallback) {
416
                $groupCallback($group);
417
            }
418
419
            $GLOBALS['widget_template'] = $group->template();
420
421
            if (!$this->selectedFormGroup() && $this->isTabbable()) {
422
                $group->setIsHidden(false);
423
                if ($i > 1) {
424
                    $group->setIsHidden(true);
425
                }
426
            }
427
            $i++;
428
429
            yield $group;
430
431
            $GLOBALS['widget_template'] = '';
432
        }
433
    }
434
435
    /**
436
     * @param array|FormGroupInterface[] $groups Form groups to finalize.
437
     * @return array|FormGroupInterface[]
438
     */
439
    protected function finalizeFormGroups($groups)
440
    {
441
        $out = [];
442
443
        foreach ($groups as $group) {
444
            if (!$group->active()) {
445
                continue;
446
            }
447
448
            if ($group instanceof ConditionalizableInterface) {
449
                if (!$group->resolvedCondition()) {
450
                    continue;
451
                }
452
            }
453
454
            // Test formGroup vs. ACL roles
455
            if ($group->isAuthorized() === false) {
456
                continue;
457
            }
458
459
            if (!$group->l10nMode()) {
460
                $group->setL10nMode($this->l10nMode());
461
            }
462
463
            if ($this->isTabbable() && $this->tabIdent()) {
464
                $group->setIsHidden(true);
465
466
                if ($group->ident() === $this->tabIdent()) {
467
                    $group->setIsHidden(false);
468
                    $this->setSelectedFormGroup($group->ident());
469
                }
470
            }
471
472
            if ($group->rawConditionalLogic()) {
473
                if (is_callable([$this, 'obj'])) {
474
                    foreach ($group->rawConditionalLogic() as $logic) {
475
                        $valid = true;
476
                        $value = $this->obj()->get($logic['property']);
0 ignored issues
show
Bug introduced by
It seems like obj() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
477
478
                        switch ($logic['operator']) {
479
                            case '!==':
480
                            case '!=':
481
                            case '!':
482
                            case 'not':
483
                                if ($value === $logic['value']) {
484
                                   $valid = false;
485
                                }
486
                                break;
487
                            default:
488
                            case '"==="':
0 ignored issues
show
Unused Code introduced by
case '"==="': does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
489
                            case '"=="':
490
                            case '"="':
491
                            case '"is"':
492
                                if ($value !== $logic['value']) {
493
                                    $valid = false;
494
                                }
495
                                break;
496
                        }
497
498
                        if (!$valid) {
499
                            $group->setConditionalLogicUnmet(true);
500
                        }
501
                    }
502
                }
503
            }
504
505
            $out[] = $group;
506
        }
507
508
        return $out;
509
    }
510
511
    /**
512
     * Determine if the form has any groups.
513
     *
514
     * @return boolean
515
     */
516
    public function hasGroups()
517
    {
518
        return ($this->numGroups() > 0);
519
    }
520
521
    /**
522
     * Determine if the form has a given group.
523
     *
524
     * @param string $groupIdent The group identifier to look up.
525
     * @throws InvalidArgumentException If the group identifier is invalid.
526
     * @return boolean
527
     */
528
    public function hasGroup($groupIdent)
529
    {
530
        if (!is_string($groupIdent)) {
531
            throw new InvalidArgumentException(
532
                'Group identifier must be a string'
533
            );
534
        }
535
536
        return isset($this->groups[$groupIdent]);
537
    }
538
539
    /**
540
     * Count the number of form groups.
541
     *
542
     * @return integer
543
     */
544
    public function numGroups()
545
    {
546
        return count($this->groups);
547
    }
548
549
    /**
550
     * Set the widget's content group display mode.
551
     *
552
     * Currently only supports "tab".
553
     *
554
     * @param string $mode Group display mode.
555
     * @throws InvalidArgumentException If the display mode is not a string.
556
     * @return ObjectFormWidget Chainable.
557
     */
558
    public function setGroupDisplayMode($mode)
559
    {
560
        if (!is_string($mode)) {
561
            throw new InvalidArgumentException(
562
                'Display mode must be a string'
563
            );
564
        }
565
566
        if ($mode === 'tabs') {
567
            $mode = 'tab';
568
        }
569
570
        $this->groupDisplayMode = $mode;
571
572
        return $this;
573
    }
574
575
    /**
576
     * Retrieve the widget's content group display mode.
577
     *
578
     * @return string Group display mode.
579
     */
580
    public function groupDisplayMode()
581
    {
582
        return $this->groupDisplayMode;
583
    }
584
585
    /**
586
     * Determine if content groups are to be displayed as tabbable panes.
587
     *
588
     * @return boolean
589
     */
590
    public function isTabbable()
591
    {
592
        return ($this->groupDisplayMode() === 'tab');
593
    }
594
595
    /**
596
     * @param array $formData The (pre-populated) form data, as [$key=>$val] array.
597
     * @return FormInterface Chainable
598
     */
599
    public function setFormData(array $formData)
600
    {
601
        $this->formData = $formData;
602
603
        return $this;
604
    }
605
606
    /**
607
     * @param string $key The form data key, or poperty identifier.
608
     * @param mixed  $val The form data value, for a given key.
609
     * @throws InvalidArgumentException If the key argument is not a string.
610
     * @return FormInterface Chainable
611
     */
612 View Code Duplication
    public function addFormData($key, $val)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
613
    {
614
        if (!is_string($key)) {
615
            throw new InvalidArgumentException(
616
                'Can not add form data: Data key must be a string'
617
            );
618
        }
619
        $this->formData[$key] = $val;
620
621
        return $this;
622
    }
623
624
    /**
625
     * @return array
626
     */
627
    public function formData()
628
    {
629
        return $this->formData;
630
    }
631
632
    /**
633
     * @return FormWidget
634
     */
635
    protected function formWidget()
636
    {
637
        return $this;
638
    }
639
}
640