Completed
Pull Request — master (#255)
by Shaun
06:13
created

FormField::prepareOptions()   D

Complexity

Conditions 13
Paths 448

Size

Total Lines 57
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 13.0343

Importance

Changes 13
Bugs 6 Features 8
Metric Value
cc 13
eloc 34
c 13
b 6
f 8
nc 448
nop 1
dl 0
loc 57
ccs 32
cts 34
cp 0.9412
crap 13.0343
rs 4.6284

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 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 71
    public function __construct($name, $type, Form $parent, array $options = [])
93
    {
94 71
        $this->name = $name;
95 71
        $this->type = $type;
96 71
        $this->parent = $parent;
97 71
        $this->formHelper = $this->parent->getFormHelper();
98 71
        $this->setTemplate();
99 71
        $this->setDefaultOptions($options);
100 71
        $this->setupValue();
101 66
    }
102
103 71
    protected function setupValue()
104
    {
105 71
        $value = $this->getOption($this->valueProperty);
106 71
        $isChild = $this->getOption('is_child');
107
108 71
        if ($value instanceof \Closure) {
109
            $this->valueClosure = $value;
110
        }
111
112 71
        if (($value === null || $value instanceof \Closure) && !$isChild) {
113 62
            $this->setValue($this->getModelValueAttribute($this->parent->getModel(), $this->name));
114 18
        } elseif (!$isChild) {
115 12
            $this->hasDefault = true;
116
        }
117 66
    }
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 27
    protected function getViewTemplate()
130
    {
131 27
        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 27
    public function render(array $options = [], $showLabel = true, $showField = true, $showError = true)
142
    {
143 27
        $this->prepareOptions($options);
144 27
        $value = $this->getValue();
145 27
        $defaultValue = $this->getDefaultValue();
146
147 27
        if ($showField) {
148 27
            $this->rendered = true;
149
        }
150
151
        // Override default value with value
152 27
        if (!$this->isValidValue($value) && $this->isValidValue($defaultValue)) {
153
            $this->setOption($this->valueProperty, $defaultValue);
154
        }
155
156 27
        if (!$this->needsLabel()) {
157 8
            $showLabel = false;
158
        }
159
160 27
        if ($showError) {
161 26
            $showError = $this->parent->haveErrorsEnabled();
162
        }
163
164 27
        return $this->formHelper->getView()->make(
165 27
            $this->getViewTemplate(),
166
            [
167 27
                'name' => $this->name,
168 27
                'nameKey' => $this->getNameKey(),
169 27
                'type' => $this->type,
170 27
                'options' => $this->options,
171 27
                'showLabel' => $showLabel,
172 27
                'showField' => $showField,
173 27
                'showError' => $showError
174
            ]
175 27
        )->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 64
    protected function getModelValueAttribute($model, $name)
186
    {
187 64
        $transformedName = $this->transformKey($name);
188 64
        if (is_string($model)) {
189
            return $model;
190 64
        } elseif (is_object($model)) {
191 2
            return object_get($model, $transformedName);
192 64
        } elseif (is_array($model)) {
193 63
            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 71
    protected function transformKey($key)
204
    {
205 71
        return $this->formHelper->transformToDotSyntax($key);
206
    }
207
208
    /**
209
     * Prepare options for rendering
210
     *
211
     * @param array $options
212
     * @return array
213
     */
214 71
    protected function prepareOptions(array $options = [])
215
    {
216 71
        $helper = $this->formHelper;
217 71
        $rulesParser = new RulesParser($this);
218 71
        $rules = $this->getOption('rules');
219 71
        $parsedRules = $rules ? $rulesParser->parse($rules) : [];
220
221 71
        $this->options = $helper->mergeOptions($this->options, $options);
222
223
        // Append class attribute
224 71
        if ($this->getOption('attr.class_append')) {
225
            $appendedClass = $this->getOption('attr.class', '') . ' ' . $this->getOption('attr.class_append');
226
            $this->setOption('attr.class', $appendedClass);
227
        }
228
        
229 71
        if ($this->getOption('attr.multiple') && !$this->getOption('tmp.multipleBracesSet')) {
230 2
            $this->name = $this->name.'[]';
231 2
            $this->setOption('tmp.multipleBracesSet', true);
232
        }
233
234 71
        if ($this->parent->haveErrorsEnabled()) {
235 71
            $this->addErrorClass();
236
        }
237
238 71
        if ($this->parent->clientValidationEnabled()) {
239 71
            if ($this->getOption('required') === true || isset($parsedRules['required'])) {
240 3
                $lblClass = $this->getOption('label_attr.class', '');
241 3
                $requiredClass = $helper->getConfig('defaults.required_class', 'required');
242 3
                if (!str_contains($lblClass, $requiredClass)) {
243 3
                    $lblClass .= ' ' . $requiredClass;
244 3
                    $this->setOption('label_attr.class', $lblClass);
245 3
                    $this->setOption('attr.required', 'required');
246
                }
247
            }
248
249 71
            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...
250 1
                $attrs = $this->getOption('attr') + $parsedRules;
251 1
                $this->setOption('attr', $attrs);
252
            }
253
        }
254
255 71
        $this->setOption('wrapperAttrs', $helper->prepareAttributes($this->getOption('wrapper')));
256 71
        $this->setOption('errorAttrs', $helper->prepareAttributes($this->getOption('errors')));
257
258 71
        if ($this->getOption('is_child')) {
259 16
            $this->setOption('labelAttrs', $helper->prepareAttributes($this->getOption('label_attr')));
260
        }
261
262 71
        if ($this->getOption('help_block.text')) {
263 1
            $this->setOption(
264 1
                'help_block.helpBlockAttrs',
265 1
                $helper->prepareAttributes($this->getOption('help_block.attr'))
266
            );
267
        }
268
269 71
        return $this->options;
270
    }
271
272
    /**
273
     * Get name of the field
274
     *
275
     * @return string
276
     */
277 23
    public function getName()
278
    {
279 23
        return $this->name;
280
    }
281
282
    /**
283
     * Set name of the field
284
     *
285
     * @param string $name
286
     * @return $this
287
     */
288 11
    public function setName($name)
289
    {
290 11
        $this->name = $name;
291
292 11
        return $this;
293
    }
294
295
    /**
296
     * Get dot notation key for fields
297
     *
298
     * @return string
299
     **/
300 39
    public function getNameKey()
301
    {
302 39
        return $this->transformKey($this->name);
303
    }
304
305
    /**
306
     * Get field options
307
     *
308
     * @return array
309
     */
310 10
    public function getOptions()
311
    {
312 10
        return $this->options;
313
    }
314
315
    /**
316
     * Get single option from options array. Can be used with dot notation ('attr.class')
317
     *
318
     * @param        $option
319
     * @param mixed  $default
320
     *
321
     * @return mixed
322
     */
323 71
    public function getOption($option, $default = null)
324
    {
325 71
        return array_get($this->options, $option, $default);
326
    }
327
328
    /**
329
     * Set field options
330
     *
331
     * @param array $options
332
     * @return $this
333
     */
334 11
    public function setOptions($options)
335
    {
336 11
        $this->options = $this->prepareOptions($options);
337
338 11
        return $this;
339
    }
340
341
    /**
342
     * Set single option on the field
343
     *
344
     * @param string $name
345
     * @param mixed $value
346
     * @return $this
347
     */
348 71
    public function setOption($name, $value)
349
    {
350 71
        array_set($this->options, $name, $value);
351
352 71
        return $this;
353
    }
354
355
    /**
356
     * Get the type of the field
357
     *
358
     * @return string
359
     */
360 41
    public function getType()
361
    {
362 41
        return $this->type;
363
    }
364
365
    /**
366
     * Set type of the field
367
     *
368
     * @param mixed $type
369
     * @return $this
370
     */
371 1
    public function setType($type)
372
    {
373 1
        if ($this->formHelper->getFieldType($type)) {
374 1
            $this->type = $type;
375
        }
376
377 1
        return $this;
378
    }
379
380
    /**
381
     * @return Form
382
     */
383 71
    public function getParent()
384
    {
385 71
        return $this->parent;
386
    }
387
388
    /**
389
     * Check if the field is rendered
390
     *
391
     * @return bool
392
     */
393 4
    public function isRendered()
394
    {
395 4
        return $this->rendered;
396
    }
397
398
    /**
399
     * Default options for field
400
     *
401
     * @return array
402
     */
403 51
    protected function getDefaults()
404
    {
405 51
        return [];
406
    }
407
408
    /**
409
     * Defaults used across all fields
410
     *
411
     * @return array
412
     */
413 71
    private function allDefaults()
414
    {
415
        return [
416 71
            'wrapper' => ['class' => $this->formHelper->getConfig('defaults.wrapper_class')],
417 71
            'attr' => ['class' => $this->formHelper->getConfig('defaults.field_class')],
418 71
            'help_block' => ['text' => null, 'tag' => 'p', 'attr' => [
419 71
                'class' => $this->formHelper->getConfig('defaults.help_block_class')
420
            ]],
421
            'value' => null,
422
            'default_value' => null,
423
            'label' => null,
424
            'label_show' => true,
425
            'is_child' => false,
426 71
            'label_attr' => ['class' => $this->formHelper->getConfig('defaults.label_class')],
427 71
            'errors' => ['class' => $this->formHelper->getConfig('defaults.error_class')],
428
            'rules' => [],
429
            'error_messages' => []
430
        ];
431
    }
432
433
    /**
434
     * Get real name of the field without form namespace
435
     *
436
     * @return string
437
     */
438 70
    public function getRealName()
439
    {
440 70
        return $this->getOption('real_name', $this->name);
441
    }
442
443
    /**
444
     * @param $value
445
     * @return $this
446
     */
447 65
    public function setValue($value)
448
    {
449 65
        if ($this->hasDefault) {
450 1
            return $this;
451
        }
452
453 65
        $closure = $this->valueClosure;
454
455 65
        if ($closure instanceof \Closure) {
456
            $value = $closure($value ?: null);
457
        }
458
459 65
        if (!$this->isValidValue($value)) {
460 63
            $value = $this->getOption($this->defaultValueProperty);
461
        }
462
463 65
        $this->options[$this->valueProperty] = $value;
464
465 65
        return $this;
466
    }
467
468
    /**
469
     * Set the template property on the object
470
     */
471 71
    private function setTemplate()
472
    {
473 71
        $this->template = $this->formHelper->getConfig($this->getTemplate(), $this->getTemplate());
474 71
    }
475
476
    /**
477
     * Add error class to wrapper if validation errors exist
478
     */
479 71
    protected function addErrorClass()
480
    {
481 71
        $errors = $this->parent->getRequest()->session()->get('errors');
482
483 71
        if ($errors && $errors->has($this->getNameKey())) {
484
            $errorClass = $this->formHelper->getConfig('defaults.wrapper_error_class');
485
            $wrapperClass = $this->getOption('wrapper.class');
486
487
            if ($this->getOption('wrapper') && !str_contains($wrapperClass, $errorClass)) {
488
                $wrapperClass .= ' ' . $errorClass;
489
                $this->setOption('wrapper.class', $wrapperClass);
490
            }
491
        }
492 71
    }
493
494
495
    /**
496
     * Merge all defaults with field specific defaults and set template if passed
497
     *
498
     * @param array $options
499
     */
500 71
    protected function setDefaultOptions(array $options = [])
501
    {
502 71
        $this->options = $this->formHelper->mergeOptions($this->allDefaults(), $this->getDefaults());
503 71
        $this->options = $this->prepareOptions($options);
504 71
        $this->setupLabel();
505 71
    }
506
507 71
    protected function setupLabel()
508
    {
509 71
        if ($this->getOption('label') !== null) {
510 18
            return;
511
        }
512
513 69
        if ($langName = $this->parent->getLanguageName()) {
514 4
            $label = sprintf('%s.%s', $langName, $this->getRealName());
515
        } else {
516 66
            $label = $this->getRealName();
517
        }
518
519 69
        $this->setOption('label', $this->formHelper->formatLabel($label));
520 69
    }
521
522
    /**
523
     * Check if fields needs label
524
     *
525
     * @return bool
526
     */
527 27
    protected function needsLabel()
528
    {
529
        // If field is <select> and child of choice, we don't need label for it
530 27
        $isChildSelect = $this->type == 'select' && $this->getOption('is_child') === true;
531
532 27
        if ($this->type == 'hidden' || $isChildSelect) {
533 8
            return false;
534
        }
535
536 24
        return true;
537
    }
538
539
    /**
540
     * Disable field
541
     *
542
     * @return $this
543
     */
544 1
    public function disable()
545
    {
546 1
        $this->setOption('attr.disabled', 'disabled');
547
548 1
        return $this;
549
    }
550
551
    /**
552
     * Enable field
553
     *
554
     * @return $this
555
     */
556 1
    public function enable()
557
    {
558 1
        array_forget($this->options, 'attr.disabled');
559
560 1
        return $this;
561
    }
562
563
    /**
564
     * Get validation rules for a field if any with label for attributes
565
     *
566
     * @return array|null
567
     */
568 4
    public function getValidationRules()
569
    {
570 4
        $rules = $this->getOption('rules', []);
571 4
        $name = $this->getNameKey();
572 4
        $messages = $this->getOption('error_messages', []);
573 4
        $formName = $this->parent->getName();
574
575 4
        if ($messages && $formName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $formName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
576 1
            $newMessages = [];
577 1
            foreach ($messages as $messageKey => $message) {
578 1
                $messageKey = sprintf('%s.%s', $formName, $messageKey);
579 1
                $newMessages[$messageKey] = $message;
580
            }
581 1
            $messages = $newMessages;
582
        }
583
584 4
        if (!$rules) {
585 1
            return [];
586
        }
587
588
        return [
589 4
            'rules' => [$name => $rules],
590 4
            'attributes' => [$name => $this->getOption('label')],
591 4
            'error_messages' => $messages
592
        ];
593
    }
594
595
    /**
596
     * Get value property
597
     *
598
     * @param mixed|null $default
599
     * @return mixed
600
     */
601 30
    public function getValue($default = null)
602
    {
603 30
        return $this->getOption($this->valueProperty, $default);
604
    }
605
606
    /**
607
     * Get default value property
608
     *
609
     * @param mixed|null $default
610
     * @return mixed
611
     */
612 27
    public function getDefaultValue($default = null)
613
    {
614 27
        return $this->getOption($this->defaultValueProperty, $default);
615
    }
616
617
    /**
618
     * Check if provided value is valid for this type
619
     *
620
     * @return bool
621
     */
622 66
    protected function isValidValue($value)
623
    {
624 66
        return $value !== null;
625
    }
626
}
627