Completed
Push — master ( 109c0f...0cbdc1 )
by Chauncey
10:02 queued 04:29
created

FormTrait   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 479
Duplicated Lines 3.55 %

Coupling/Cohesion

Components 3
Dependencies 1

Importance

Changes 0
Metric Value
wmc 53
lcom 3
cbo 1
dl 17
loc 479
rs 7.4757
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A setFormGroupFactory() 0 6 1
A formGroupFactory() 0 10 2
A setGroupCallback() 0 6 1
A setAction() 0 11 2
A action() 0 4 1
A setMethod() 0 12 2
A method() 0 4 1
A setL10nMode() 0 6 1
A l10nMode() 0 4 1
A setGroups() 0 10 2
C addGroup() 0 36 8
A createFormGroup() 0 17 3
A updateFormGroup() 0 17 3
A defaultGroupType() 0 4 1
D groups() 0 38 9
A hasGroups() 0 4 1
A hasGroup() 0 10 2
A numGroups() 0 4 1
A setGroupDisplayMode() 0 16 3
A groupDisplayMode() 0 4 1
A isTabbable() 0 4 1
A setFormData() 0 6 1
A addFormData() 10 11 2
A formData() 0 4 1
A sortGroupsByPriority() 7 9 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like FormTrait 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 FormTrait, and based on these observations, apply Extract Interface, too.

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

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
321
    {
322
        $groups = $this->groups;
323
        uasort($groups, [$this, 'sortGroupsByPriority']);
324
325
        $groupCallback = (isset($groupCallback) ? $groupCallback : $this->groupCallback);
326
327
        $i = 1;
328
        foreach ($groups as $group) {
329
            if (!$group->active()) {
330
                continue;
331
            }
332
333
            // Test formGroup vs. ACL roles
334
            if ($group->isAuthorized() === false) {
335
                continue;
336
            }
337
338
            if (!$group->l10nMode()) {
339
                $group->setL10nMode($this->l10nMode());
340
            }
341
342
            if ($groupCallback) {
343
                $groupCallback($group);
344
            }
345
346
            $GLOBALS['widget_template'] = $group->template();
347
348
            if ($this->isTabbable() && $i > 1) {
349
                $group->isHidden = true;
350
            }
351
            $i++;
352
353
            yield $group;
354
355
            $GLOBALS['widget_template'] = '';
356
        }
357
    }
358
359
    /**
360
     * Determine if the form has any groups.
361
     *
362
     * @return boolean
363
     */
364
    public function hasGroups()
365
    {
366
        return (count($this->groups) > 0);
367
    }
368
369
    /**
370
     * Determine if the form has a given group.
371
     *
372
     * @param string $groupIdent The group identifier to look up.
373
     * @throws InvalidArgumentException If the group identifier is invalid.
374
     * @return boolean
375
     */
376
    public function hasGroup($groupIdent)
377
    {
378
        if (!is_string($groupIdent)) {
379
            throw new InvalidArgumentException(
380
                'Group identifier must be a string'
381
            );
382
        }
383
384
        return isset($this->groups[$groupIdent]);
385
    }
386
387
    /**
388
     * Count the number of form groups.
389
     *
390
     * @return integer
391
     */
392
    public function numGroups()
393
    {
394
        return count($this->groups);
395
    }
396
397
    /**
398
     * Set the widget's content group display mode.
399
     *
400
     * Currently only supports "tab".
401
     *
402
     * @param string $mode Group display mode.
403
     * @throws InvalidArgumentException If the display mode is not a string.
404
     * @return ObjectFormWidget Chainable.
405
     */
406
    public function setGroupDisplayMode($mode)
407
    {
408
        if (!is_string($mode)) {
409
            throw new InvalidArgumentException(
410
                'Display mode must be a string'
411
            );
412
        }
413
414
        if ($mode === 'tabs') {
415
            $mode = 'tab';
416
        }
417
418
        $this->groupDisplayMode = $mode;
419
420
        return $this;
421
    }
422
423
    /**
424
     * Retrieve the widget's content group display mode.
425
     *
426
     * @return string Group display mode.
427
     */
428
    public function groupDisplayMode()
429
    {
430
        return $this->groupDisplayMode;
431
    }
432
433
    /**
434
     * Determine if content groups are to be displayed as tabbable panes.
435
     *
436
     * @return boolean
437
     */
438
    public function isTabbable()
439
    {
440
        return ($this->groupDisplayMode() === 'tab');
441
    }
442
443
    /**
444
     * @param array $formData The (pre-populated) form data, as [$key=>$val] array.
445
     * @return FormInterface Chainable
446
     */
447
    public function setFormData(array $formData)
448
    {
449
        $this->formData = $formData;
450
451
        return $this;
452
    }
453
454
    /**
455
     * @param string $key The form data key, or poperty identifier.
456
     * @param mixed  $val The form data value, for a given key.
457
     * @throws InvalidArgumentException If the key argument is not a string.
458
     * @return FormInterface Chainable
459
     */
460 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...
461
    {
462
        if (!is_string($key)) {
463
            throw new InvalidArgumentException(
464
                'Can not add form data: Data key must be a string'
465
            );
466
        }
467
        $this->formData[$key] = $val;
468
469
        return $this;
470
    }
471
472
    /**
473
     * @return array
474
     */
475
    public function formData()
476
    {
477
        return $this->formData;
478
    }
479
480
    /**
481
     * Static comparison function used by {@see uasort()}.
482
     *
483
     * @param  FormGroupInterface $a Form Group A.
484
     * @param  FormGroupInterface $b Form Group B.
485
     * @return integer Sorting value: -1 or 1
486
     */
487 View Code Duplication
    protected static function sortGroupsByPriority(
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...
488
        FormGroupInterface $a,
489
        FormGroupInterface $b
490
    ) {
491
        $a = $a->priority();
492
        $b = $b->priority();
493
494
        return ($a < $b) ? (-1) : 1;
495
    }
496
}
497