Completed
Push — master ( 201375...627c5e )
by Kristijan
22:40 queued 07:25
created

FormField   C

Complexity

Total Complexity 72

Size/Duplication

Total Lines 591
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 94.51%

Importance

Changes 34
Bugs 17 Features 13
Metric Value
wmc 72
c 34
b 17
f 13
lcom 1
cbo 7
dl 0
loc 591
ccs 172
cts 182
cp 0.9451
rs 5.5667

34 Methods

Rating   Name   Duplication   Size   Complexity  
getTemplate() 0 1 ?
A getViewTemplate() 0 4 1
A __construct() 0 10 1
B setupValue() 0 15 6
A getModelValueAttribute() 0 11 4
A transformKey() 0 4 1
A getName() 0 4 1
A setName() 0 6 1
A getNameKey() 0 4 1
A getOptions() 0 4 1
A getOption() 0 4 1
A setOptions() 0 6 1
A setOption() 0 6 1
A getType() 0 4 1
A getParent() 0 4 1
A isRendered() 0 4 1
A getDefaults() 0 4 1
A getRealName() 0 4 1
A setTemplate() 0 4 1
A setDefaultOptions() 0 6 1
A needsLabel() 0 11 4
A disable() 0 6 1
A enable() 0 6 1
A getValue() 0 4 1
A getDefaultValue() 0 4 1
A isValidValue() 0 4 1
B render() 0 36 6
D prepareOptions() 0 51 12
A setType() 0 8 2
A allDefaults() 0 17 1
B setValue() 0 20 5
B addErrorClass() 0 14 5
A setupLabel() 0 14 3
A getValidationRules() 0 14 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Kris\LaravelFormBuilder\Fields;
4
5
use Illuminate\Database\Eloquent\Collection;
6
use Illuminate\Database\Eloquent\Model;
7
use Kris\LaravelFormBuilder\Form;
8
use Kris\LaravelFormBuilder\FormHelper;
9
use Kris\LaravelFormBuilder\RulesParser;
10
11
/**
12
 * Class FormField
13
 *
14
 * @package Kris\LaravelFormBuilder\Fields
15
 */
16
abstract class FormField
17
{
18
    /**
19
     * Name of the field
20
     *
21
     * @var
22
     */
23
    protected $name;
24
25
    /**
26
     * Type of the field
27
     *
28
     * @var
29
     */
30
    protected $type;
31
32
    /**
33
     * All options for the field
34
     *
35
     * @var
36
     */
37
    protected $options = [];
38
39
    /**
40
     * Is field rendered
41
     *
42
     * @var bool
43
     */
44
    protected $rendered = false;
45
46
    /**
47
     * @var Form
48
     */
49
    protected $parent;
50
51
    /**
52
     * @var string
53
     */
54
    protected $template;
55
56
    /**
57
     * @var FormHelper
58
     */
59
    protected $formHelper;
60
61
    /**
62
     * Name of the property for value setting
63
     *
64
     * @var string
65
     */
66
    protected $valueProperty = 'value';
67
68
    /**
69
     * Name of the property for default value
70
     *
71
     * @var string
72
     */
73
    protected $defaultValueProperty = 'default_value';
74
75
    /**
76
     * Is default value set?
77
     * @var bool
78
     */
79
    protected $hasDefault = false;
80
81
    /**
82
     * @var \Closure|null
83
     */
84
    protected $valueClosure = null;
85
86
    /**
87
     * @param             $name
88
     * @param             $type
89
     * @param Form        $parent
90
     * @param array       $options
91
     */
92 69
    public function __construct($name, $type, Form $parent, array $options = [])
93
    {
94 69
        $this->name = $name;
95 69
        $this->type = $type;
96 69
        $this->parent = $parent;
97 69
        $this->formHelper = $this->parent->getFormHelper();
98 69
        $this->setTemplate();
99 69
        $this->setDefaultOptions($options);
100 69
        $this->setupValue();
101 64
    }
102
103 69
    protected function setupValue()
104
    {
105 69
        $value = $this->getOption($this->valueProperty);
106 69
        $isChild = $this->getOption('is_child');
107
108 69
        if ($value instanceof \Closure) {
109
            $this->valueClosure = $value;
110
        }
111
112 69
        if (($value === null || $value instanceof \Closure) && !$isChild) {
113 60
            $this->setValue($this->getModelValueAttribute($this->parent->getModel(), $this->name));
114 18
        } elseif (!$isChild) {
115 12
            $this->hasDefault = true;
116
        }
117 64
    }
118
119
    /**
120
     * Get the template, can be config variable or view path
121
     *
122
     * @return string
123
     */
124
    abstract protected function getTemplate();
125
126
    /**
127
     * @return string
128
     */
129 26
    protected function getViewTemplate()
130
    {
131 26
        return $this->parent->getTemplatePrefix() . $this->getOption('template', $this->template);
132
    }
133
134
    /**
135
     * @param array $options
136
     * @param bool  $showLabel
137
     * @param bool  $showField
138
     * @param bool  $showError
139
     * @return string
140
     */
141 26
    public function render(array $options = [], $showLabel = true, $showField = true, $showError = true)
142
    {
143 26
        $this->prepareOptions($options);
144 26
        $value = $this->getValue();
145 26
        $defaultValue = $this->getDefaultValue();
146
147 26
        if ($showField) {
148 26
            $this->rendered = true;
149
        }
150
151
        // Override default value with value
152 26
        if (!$this->isValidValue($value) && $this->isValidValue($defaultValue)) {
153
            $this->setOption($this->valueProperty, $defaultValue);
154
        }
155
156 26
        if (!$this->needsLabel()) {
157 8
            $showLabel = false;
158
        }
159
160 26
        if ($showError) {
161 25
            $showError = $this->parent->haveErrorsEnabled();
162
        }
163
164 26
        return $this->formHelper->getView()->make(
165 26
            $this->getViewTemplate(),
166
            [
167 26
                'name' => $this->name,
168 26
                'nameKey' => $this->getNameKey(),
169 26
                'type' => $this->type,
170 26
                'options' => $this->options,
171 26
                'showLabel' => $showLabel,
172 26
                'showField' => $showField,
173 26
                'showError' => $showError
174
            ]
175 26
        )->render();
176
    }
177
178
    /**
179
     * Get the attribute value from the model by name
180
     *
181
     * @param mixed $model
182
     * @param string $name
183
     * @return mixed
184
     */
185 62
    protected function getModelValueAttribute($model, $name)
186
    {
187 62
        $transformedName = $this->transformKey($name);
188 62
        if (is_string($model)) {
189
            return $model;
190 62
        } elseif (is_object($model)) {
191 2
            return object_get($model, $transformedName);
192 62
        } elseif (is_array($model)) {
193 61
            return array_get($model, $transformedName);
194
        }
195 5
    }
196
197
    /**
198
     * Transform array like syntax to dot syntax
199
     *
200
     * @param $key
201
     * @return mixed
202
     */
203 69
    protected function transformKey($key)
204
    {
205 69
        return $this->formHelper->transformToDotSyntax($key);
206
    }
207
208
    /**
209
     * Prepare options for rendering
210
     *
211
     * @param array $options
212
     * @return array
213
     */
214 69
    protected function prepareOptions(array $options = [])
215
    {
216 69
        $helper = $this->formHelper;
217 69
        $rulesParser = new RulesParser($this);
218 69
        $rules = $this->getOption('rules');
219 69
        $parsedRules = $rules ? $rulesParser->parse($rules) : [];
220
221 69
        $this->options = $helper->mergeOptions($this->options, $options);
222
223 69
        if ($this->getOption('attr.multiple') && !$this->getOption('tmp.multipleBracesSet')) {
224 2
            $this->name = $this->name.'[]';
225 2
            $this->setOption('tmp.multipleBracesSet', true);
226
        }
227
228 69
        if ($this->parent->haveErrorsEnabled()) {
229 69
            $this->addErrorClass();
230
        }
231
232 69
        if ($this->parent->clientValidationEnabled()) {
233 69
            if ($this->getOption('required') === true || isset($parsedRules['required'])) {
234 3
                $lblClass = $this->getOption('label_attr.class', '');
235 3
                $requiredClass = $helper->getConfig('defaults.required_class', 'required');
236 3
                if (!str_contains($lblClass, $requiredClass)) {
237 3
                    $lblClass .= ' ' . $requiredClass;
238 3
                    $this->setOption('label_attr.class', $lblClass);
239 3
                    $this->setOption('attr.required', 'required');
240
                }
241
            }
242
243 69
            if ($parsedRules) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parsedRules of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
244 1
                $attrs = $this->getOption('attr') + $parsedRules;
245 1
                $this->setOption('attr', $attrs);
246
            }
247
        }
248
249 69
        $this->setOption('wrapperAttrs', $helper->prepareAttributes($this->getOption('wrapper')));
250 69
        $this->setOption('errorAttrs', $helper->prepareAttributes($this->getOption('errors')));
251
252 69
        if ($this->getOption('is_child')) {
253 16
            $this->setOption('labelAttrs', $helper->prepareAttributes($this->getOption('label_attr')));
254
        }
255
256 69
        if ($this->getOption('help_block.text')) {
257 1
            $this->setOption(
258 1
                'help_block.helpBlockAttrs',
259 1
                $helper->prepareAttributes($this->getOption('help_block.attr'))
260
            );
261
        }
262
263 69
        return $this->options;
264
    }
265
266
    /**
267
     * Get name of the field
268
     *
269
     * @return string
270
     */
271 23
    public function getName()
272
    {
273 23
        return $this->name;
274
    }
275
276
    /**
277
     * Set name of the field
278
     *
279
     * @param string $name
280
     * @return $this
281
     */
282 11
    public function setName($name)
283
    {
284 11
        $this->name = $name;
285
286 11
        return $this;
287
    }
288
289
    /**
290
     * Get dot notation key for fields
291
     *
292
     * @return string
293
     **/
294 37
    public function getNameKey()
295
    {
296 37
        return $this->transformKey($this->name);
297
    }
298
299
    /**
300
     * Get field options
301
     *
302
     * @return array
303
     */
304 9
    public function getOptions()
305
    {
306 9
        return $this->options;
307
    }
308
309
    /**
310
     * Get single option from options array. Can be used with dot notation ('attr.class')
311
     *
312
     * @param        $option
313
     * @param mixed  $default
314
     *
315
     * @return mixed
316
     */
317 69
    public function getOption($option, $default = null)
318
    {
319 69
        return array_get($this->options, $option, $default);
320
    }
321
322
    /**
323
     * Set field options
324
     *
325
     * @param array $options
326
     * @return $this
327
     */
328 11
    public function setOptions($options)
329
    {
330 11
        $this->options = $this->prepareOptions($options);
331
332 11
        return $this;
333
    }
334
335
    /**
336
     * Set single option on the field
337
     *
338
     * @param string $name
339
     * @param mixed $value
340
     * @return $this
341
     */
342 69
    public function setOption($name, $value)
343
    {
344 69
        array_set($this->options, $name, $value);
345
346 69
        return $this;
347
    }
348
349
    /**
350
     * Get the type of the field
351
     *
352
     * @return string
353
     */
354 40
    public function getType()
355
    {
356 40
        return $this->type;
357
    }
358
359
    /**
360
     * Set type of the field
361
     *
362
     * @param mixed $type
363
     * @return $this
364
     */
365 1
    public function setType($type)
366
    {
367 1
        if ($this->formHelper->getFieldType($type)) {
368 1
            $this->type = $type;
369
        }
370
371 1
        return $this;
372
    }
373
374
    /**
375
     * @return Form
376
     */
377 69
    public function getParent()
378
    {
379 69
        return $this->parent;
380
    }
381
382
    /**
383
     * Check if the field is rendered
384
     *
385
     * @return bool
386
     */
387 4
    public function isRendered()
388
    {
389 4
        return $this->rendered;
390
    }
391
392
    /**
393
     * Default options for field
394
     *
395
     * @return array
396
     */
397 49
    protected function getDefaults()
398
    {
399 49
        return [];
400
    }
401
402
    /**
403
     * Defaults used across all fields
404
     *
405
     * @return array
406
     */
407 69
    private function allDefaults()
408
    {
409
        return [
410 69
            'wrapper' => ['class' => $this->formHelper->getConfig('defaults.wrapper_class')],
411 69
            'attr' => ['class' => $this->formHelper->getConfig('defaults.field_class')],
412 69
            'help_block' => ['text' => null, 'tag' => 'p', 'attr' => [
413 69
                'class' => $this->formHelper->getConfig('defaults.help_block_class')
414
            ]],
415
            'value' => null,
416
            'default_value' => null,
417
            'label' => null,
418
            'is_child' => false,
419 69
            'label_attr' => ['class' => $this->formHelper->getConfig('defaults.label_class')],
420 69
            'errors' => ['class' => $this->formHelper->getConfig('defaults.error_class')],
421
            'rules' => []
422
        ];
423
    }
424
425
    /**
426
     * Get real name of the field without form namespace
427
     *
428
     * @return string
429
     */
430 69
    public function getRealName()
431
    {
432 69
        return $this->getOption('real_name', $this->name);
433
    }
434
435
    /**
436
     * @param $value
437
     * @return $this
438
     */
439 63
    public function setValue($value)
440
    {
441 63
        if ($this->hasDefault) {
442 1
            return $this;
443
        }
444
445 63
        $closure = $this->valueClosure;
446
447 63
        if ($closure instanceof \Closure) {
448
            $value = $closure($value ?: null);
449
        }
450
451 63
        if (!$this->isValidValue($value)) {
452 61
            $value = $this->getOption($this->defaultValueProperty);
453
        }
454
455 63
        $this->options[$this->valueProperty] = $value;
456
457 63
        return $this;
458
    }
459
460
    /**
461
     * Set the template property on the object
462
     */
463 69
    private function setTemplate()
464
    {
465 69
        $this->template = $this->formHelper->getConfig($this->getTemplate(), $this->getTemplate());
466 69
    }
467
468
    /**
469
     * Add error class to wrapper if validation errors exist
470
     */
471 69
    protected function addErrorClass()
472
    {
473 69
        $errors = $this->parent->getRequest()->session()->get('errors');
474
475 69
        if ($errors && $errors->has($this->getNameKey())) {
476
            $errorClass = $this->formHelper->getConfig('defaults.wrapper_error_class');
477
            $wrapperClass = $this->getOption('wrapper.class');
478
479
            if ($this->getOption('wrapper') && !str_contains($wrapperClass, $errorClass)) {
480
                $wrapperClass .= ' ' . $errorClass;
481
                $this->setOption('wrapper.class', $wrapperClass);
482
            }
483
        }
484 69
    }
485
486
487
    /**
488
     * Merge all defaults with field specific defaults and set template if passed
489
     *
490
     * @param array $options
491
     */
492 69
    protected function setDefaultOptions(array $options = [])
493
    {
494 69
        $this->options = $this->formHelper->mergeOptions($this->allDefaults(), $this->getDefaults());
495 69
        $this->options = $this->prepareOptions($options);
496 69
        $this->setupLabel();
497 69
    }
498
499 69
    protected function setupLabel()
500
    {
501 69
        if ($this->getOption('label') !== null) {
502 16
            return;
503
        }
504
505 68
        if ($langName = $this->parent->getLanguageName()) {
506 4
            $label = sprintf('%s.%s', $langName, $this->getRealName());
507
        } else {
508 65
            $label = $this->getRealName();
509
        }
510
511 68
        $this->setOption('label', $this->formHelper->formatLabel($label));
512 68
    }
513
514
    /**
515
     * Check if fields needs label
516
     *
517
     * @return bool
518
     */
519 26
    protected function needsLabel()
520
    {
521
        // If field is <select> and child of choice, we don't need label for it
522 26
        $isChildSelect = $this->type == 'select' && $this->getOption('is_child') === true;
523
524 26
        if ($this->type == 'hidden' || $isChildSelect) {
525 8
            return false;
526
        }
527
528 23
        return true;
529
    }
530
531
    /**
532
     * Disable field
533
     *
534
     * @return $this
535
     */
536 1
    public function disable()
537
    {
538 1
        $this->setOption('attr.disabled', 'disabled');
539
540 1
        return $this;
541
    }
542
543
    /**
544
     * Enable field
545
     *
546
     * @return $this
547
     */
548 1
    public function enable()
549
    {
550 1
        array_forget($this->options, 'attr.disabled');
551
552 1
        return $this;
553
    }
554
555
    /**
556
     * Get validation rules for a field if any with label for attributes
557
     *
558
     * @return array|null
559
     */
560 3
    public function getValidationRules()
561
    {
562 3
        $rules = $this->getOption('rules', []);
563 3
        $name = $this->getNameKey();
564
565 3
        if (!$rules) {
566
            return [];
567
        }
568
569
        return [
570 3
            'rules' => [$name => $rules],
571 3
            'attributes' => [$name => $this->getOption('label')]
572
        ];
573
    }
574
575
    /**
576
     * Get value property
577
     *
578
     * @param mixed|null $default
579
     * @return mixed
580
     */
581 29
    public function getValue($default = null)
582
    {
583 29
        return $this->getOption($this->valueProperty, $default);
584
    }
585
586
    /**
587
     * Get default value property
588
     *
589
     * @param mixed|null $default
590
     * @return mixed
591
     */
592 26
    public function getDefaultValue($default = null)
593
    {
594 26
        return $this->getOption($this->defaultValueProperty, $default);
595
    }
596
597
    /**
598
     * Check if provided value is valid for this type
599
     *
600
     * @return bool
601
     */
602 64
    protected function isValidValue($value)
603
    {
604 64
        return $value !== null;
605
    }
606
}
607