Completed
Push — master ( 6a996d...b41795 )
by Mathieu
13:00 queued 10:14
created

FormTrait   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 479
Duplicated Lines 2.09 %

Coupling/Cohesion

Components 3
Dependencies 1

Importance

Changes 0
Metric Value
wmc 53
lcom 3
cbo 1
dl 10
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
A addGroup() 0 16 4
B parseFormGroup() 0 28 6
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

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
        $group = $this->parseFormGroup($groupIdent, $group);
224
225
        if (isset($group['ident'])) {
226
            $groupIdent = $group['ident'];
227
        }
228
229
        $this->groups[$groupIdent] = $group;
230
231
        return $this;
232
    }
233
234
    /**
235
     * Parse a form group.
236
     *
237
     * @param  string                   $groupIdent The group identifier.
238
     * @param  array|FormGroupInterface $group      The group object or structure.
239
     * @throws InvalidArgumentException If the identifier is not a string or the group is invalid.
240
     * @return FormGroupInterface
241
     */
242
    protected function parseFormGroup($groupIdent, $group)
243
    {
244
        if (!is_string($groupIdent)) {
245
            throw new InvalidArgumentException(
246
                'Group identifier must be a string'
247
            );
248
        }
249
250
        if ($group instanceof FormGroupInterface) {
251
            $group = $this->updateFormGroup($group, null, $groupIdent);
252
        } elseif (is_array($group)) {
253
            $data = $group;
254
255
            if (!isset($data['ident'])) {
256
                $data['ident'] = $groupIdent;
257
            }
258
259
            $group = $this->createFormGroup($data);
260
        } else {
261
            throw new InvalidArgumentException(sprintf(
262
                'Group must be an instance of %s or an array of form group options, received %s',
263
                'FormGroupInterface',
264
                (is_object($group) ? get_class($group) : gettype($group))
265
            ));
266
        }
267
268
        return $group;
269
    }
270
271
    /**
272
     * Create a new form group widget.
273
     *
274
     * @param  array|null $data Optional. The form group data to set.
275
     * @return FormGroupInterface
276
     */
277
    protected function createFormGroup(array $data = null)
278
    {
279
        if (isset($data['type'])) {
280
            $type = $data['type'];
281
        } else {
282
            $type = $this->defaultGroupType();
283
        }
284
285
        $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...
286
        $group->setForm($this);
287
288
        if ($data !== null) {
289
            $group->setData($data);
290
        }
291
292
        return $group;
293
    }
294
295
    /**
296
     * Update the given form group widget.
297
     *
298
     * @param  FormGroupInterface $group      The form group to update.
299
     * @param  array|null         $groupData  Optional. The new group data to apply.
300
     * @param  string|null        $groupIdent Optional. The new group identifier.
301
     * @return FormGroupInterface
302
     */
303
    protected function updateFormGroup(
304
        FormGroupInterface $group,
305
        array $groupData = null,
306
        $groupIdent = null
307
    ) {
308
        $group->setForm($this);
309
310
        if ($groupData !== null) {
311
            $group->setData($groupData);
312
        }
313
314
        if ($groupIdent !== null) {
315
            $group->setIdent($groupIdent);
316
        }
317
318
        return $group;
319
    }
320
321
    /**
322
     * Retrieve the default form group class name.
323
     *
324
     * @return string
325
     */
326
    public function defaultGroupType()
327
    {
328
        return 'charcoal/ui/form-group/generic';
329
    }
330
331
    /**
332
     * Retrieve the form groups.
333
     *
334
     * @param callable $groupCallback Optional callback applied to each form group.
335
     * @return FormGroupInterface[]|Generator
336
     */
337
    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...
338
    {
339
        $groups = $this->groups;
340
        uasort($groups, [$this, 'sortItemsByPriority']);
341
342
        $groupCallback = (isset($groupCallback) ? $groupCallback : $this->groupCallback);
343
344
        $i = 1;
345
        foreach ($groups as $group) {
346
            if (!$group->active()) {
347
                continue;
348
            }
349
350
            // Test formGroup vs. ACL roles
351
            if ($group->isAuthorized() === false) {
352
                continue;
353
            }
354
355
            if (!$group->l10nMode()) {
356
                $group->setL10nMode($this->l10nMode());
357
            }
358
359
            if ($groupCallback) {
360
                $groupCallback($group);
361
            }
362
363
            $GLOBALS['widget_template'] = $group->template();
364
365
            if ($this->isTabbable() && $i > 1) {
366
                $group->isHidden = true;
367
            }
368
            $i++;
369
370
            yield $group;
371
372
            $GLOBALS['widget_template'] = '';
373
        }
374
    }
375
376
    /**
377
     * Determine if the form has any groups.
378
     *
379
     * @return boolean
380
     */
381
    public function hasGroups()
382
    {
383
        return (count($this->groups) > 0);
384
    }
385
386
    /**
387
     * Determine if the form has a given group.
388
     *
389
     * @param string $groupIdent The group identifier to look up.
390
     * @throws InvalidArgumentException If the group identifier is invalid.
391
     * @return boolean
392
     */
393
    public function hasGroup($groupIdent)
394
    {
395
        if (!is_string($groupIdent)) {
396
            throw new InvalidArgumentException(
397
                'Group identifier must be a string'
398
            );
399
        }
400
401
        return isset($this->groups[$groupIdent]);
402
    }
403
404
    /**
405
     * Count the number of form groups.
406
     *
407
     * @return integer
408
     */
409
    public function numGroups()
410
    {
411
        return count($this->groups);
412
    }
413
414
    /**
415
     * Set the widget's content group display mode.
416
     *
417
     * Currently only supports "tab".
418
     *
419
     * @param string $mode Group display mode.
420
     * @throws InvalidArgumentException If the display mode is not a string.
421
     * @return ObjectFormWidget Chainable.
422
     */
423
    public function setGroupDisplayMode($mode)
424
    {
425
        if (!is_string($mode)) {
426
            throw new InvalidArgumentException(
427
                'Display mode must be a string'
428
            );
429
        }
430
431
        if ($mode === 'tabs') {
432
            $mode = 'tab';
433
        }
434
435
        $this->groupDisplayMode = $mode;
436
437
        return $this;
438
    }
439
440
    /**
441
     * Retrieve the widget's content group display mode.
442
     *
443
     * @return string Group display mode.
444
     */
445
    public function groupDisplayMode()
446
    {
447
        return $this->groupDisplayMode;
448
    }
449
450
    /**
451
     * Determine if content groups are to be displayed as tabbable panes.
452
     *
453
     * @return boolean
454
     */
455
    public function isTabbable()
456
    {
457
        return ($this->groupDisplayMode() === 'tab');
458
    }
459
460
    /**
461
     * @param array $formData The (pre-populated) form data, as [$key=>$val] array.
462
     * @return FormInterface Chainable
463
     */
464
    public function setFormData(array $formData)
465
    {
466
        $this->formData = $formData;
467
468
        return $this;
469
    }
470
471
    /**
472
     * @param string $key The form data key, or poperty identifier.
473
     * @param mixed  $val The form data value, for a given key.
474
     * @throws InvalidArgumentException If the key argument is not a string.
475
     * @return FormInterface Chainable
476
     */
477 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...
478
    {
479
        if (!is_string($key)) {
480
            throw new InvalidArgumentException(
481
                'Can not add form data: Data key must be a string'
482
            );
483
        }
484
        $this->formData[$key] = $val;
485
486
        return $this;
487
    }
488
489
    /**
490
     * @return array
491
     */
492
    public function formData()
493
    {
494
        return $this->formData;
495
    }
496
}
497