Completed
Pull Request — master (#262)
by Rudie
07:30
created

FormField::render()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 38
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 6.0026

Importance

Changes 6
Bugs 1 Features 0
Metric Value
cc 6
eloc 24
c 6
b 1
f 0
nc 16
nop 4
dl 0
loc 38
rs 8.439
ccs 23
cts 24
cp 0.9583
crap 6.0026
1
<?php
2
3
namespace Kris\LaravelFormBuilder\Fields;
4
5
use Kris\LaravelFormBuilder\Form;
6
use Illuminate\Database\Eloquent\Model;
7
use Kris\LaravelFormBuilder\FormHelper;
8
use Kris\LaravelFormBuilder\RulesParser;
9
use Illuminate\Database\Eloquent\Collection;
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 81
    public function __construct($name, $type, Form $parent, array $options = [])
93
    {
94 81
        $this->name = $name;
95 81
        $this->type = $type;
96 81
        $this->parent = $parent;
97 81
        $this->formHelper = $this->parent->getFormHelper();
98 81
        $this->setTemplate();
99 81
        $this->setDefaultOptions($options);
100 81
        $this->setupValue();
101 76
    }
102
103 81
    protected function setupValue()
104
    {
105 81
        $value = $this->getOption($this->valueProperty);
106 81
        $isChild = $this->getOption('is_child');
107
108 81
        if ($value instanceof \Closure) {
109
            $this->valueClosure = $value;
110
        }
111
112 81
        if (($value === null || $value instanceof \Closure) && !$isChild) {
113 72
            $this->setValue($this->getModelValueAttribute($this->parent->getModel(), $this->name));
114 18
        } elseif (!$isChild) {
115 12
            $this->hasDefault = true;
116
        }
117 76
    }
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 31
    protected function getViewTemplate()
130
    {
131 31
        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 31
    public function render(array $options = [], $showLabel = true, $showField = true, $showError = true)
142
    {
143 31
        $this->prepareOptions($options);
144 31
        $value = $this->getValue();
145 31
        $defaultValue = $this->getDefaultValue();
146
147 31
        if ($showField) {
148 31
            $this->rendered = true;
149
        }
150
151
        // Override default value with value
152 31
        if (!$this->isValidValue($value) && $this->isValidValue($defaultValue)) {
153
            $this->setOption($this->valueProperty, $defaultValue);
154
        }
155
156 31
        if (!$this->needsLabel()) {
157 9
            $showLabel = false;
158
        }
159
160 31
        if ($showError) {
161 30
            $showError = $this->parent->haveErrorsEnabled();
162
        }
163
164 31
        return $this->formHelper->getView()->make(
165 31
            $this->getViewTemplate(),
166
            [
167 31
                'form' => $this->parent,
168 31
                'field' => $this,
169 31
                'name' => $this->name,
170 31
                'nameKey' => $this->getNameKey(),
171 31
                'type' => $this->type,
172 31
                'options' => $this->options,
173 31
                'showLabel' => $showLabel,
174 31
                'showField' => $showField,
175 31
                'showError' => $showError
176
            ]
177 31
        )->render();
178
    }
179
180
    /**
181
     * Get the attribute value from the model by name
182
     *
183
     * @param mixed $model
184
     * @param string $name
185
     * @return mixed
186
     */
187 74
    protected function getModelValueAttribute($model, $name)
188
    {
189 74
        $transformedName = $this->transformKey($name);
190 74
        if (is_string($model)) {
191
            return $model;
192 74
        } elseif (is_object($model)) {
193 2
            return object_get($model, $transformedName);
194 74
        } elseif (is_array($model)) {
195 73
            return array_get($model, $transformedName);
196
        }
197 5
    }
198
199
    /**
200
     * Transform array like syntax to dot syntax
201
     *
202
     * @param $key
203
     * @return mixed
204
     */
205 81
    protected function transformKey($key)
206
    {
207 81
        return $this->formHelper->transformToDotSyntax($key);
208
    }
209
210
    /**
211
     * Prepare options for rendering
212
     *
213
     * @param array $options
214
     * @return array
215
     */
216 81
    protected function prepareOptions(array $options = [])
217
    {
218 81
        $helper = $this->formHelper;
219 81
        $rulesParser = new RulesParser($this);
220 81
        $rules = $this->getOption('rules');
221 81
        $parsedRules = $rules ? $rulesParser->parse($rules) : [];
222
223 81
        $this->options = $helper->mergeOptions($this->options, $options);
224
225 81
        foreach (['attr', 'label_attr', 'wrapper'] as $appendable) {
226
            // Append values to the 'class' attribute
227 81
            if ($this->getOption("{$appendable}.class_append")) {
228
                // Combine the current class attribute with the appends
229 3
                $append = $this->getOption("{$appendable}.class_append");
230 3
                $classAttribute = $this->getOption("{$appendable}.class", '').' '.$append;
231 3
                $this->setOption("{$appendable}.class", $classAttribute);
232
233
                // Then remove the class_append option to prevent it from showing up as an attribute in the HTML
234 81
                $this->setOption("{$appendable}.class_append", null);
235
            }
236
        }
237
238 81
        if ($this->getOption('attr.multiple') && !$this->getOption('tmp.multipleBracesSet')) {
239 2
            $this->name = $this->name.'[]';
240 2
            $this->setOption('tmp.multipleBracesSet', true);
241
        }
242
243 81
        if ($this->parent->haveErrorsEnabled()) {
244 81
            $this->addErrorClass();
245
        }
246
247 81
        if ($this->getOption('required') === true || isset($parsedRules['required'])) {
248 4
            $lblClass = $this->getOption('label_attr.class', '');
249 4
            $requiredClass = $helper->getConfig('defaults.required_class', 'required');
250
251 4
            if (! str_contains($lblClass, $requiredClass)) {
252 4
                $lblClass .= ' '.$requiredClass;
253 4
                $this->setOption('label_attr.class', $lblClass);
254
            }
255
256 4
            if ($this->parent->clientValidationEnabled()) {
257 3
                $this->setOption('attr.required', 'required');
258
259 3
                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...
260 1
                    $attrs = $this->getOption('attr') + $parsedRules;
261 1
                    $this->setOption('attr', $attrs);
262
                }
263
            }
264
        }
265
266 81
        $this->setOption('wrapperAttrs', $helper->prepareAttributes($this->getOption('wrapper')));
267 81
        $this->setOption('errorAttrs', $helper->prepareAttributes($this->getOption('errors')));
268
269 81
        if ($this->getOption('is_child')) {
270 16
            $this->setOption('labelAttrs', $helper->prepareAttributes($this->getOption('label_attr')));
271
        }
272
273 81
        if ($this->getOption('help_block.text')) {
274 1
            $this->setOption(
275 1
                'help_block.helpBlockAttrs',
276 1
                $helper->prepareAttributes($this->getOption('help_block.attr'))
277
            );
278
        }
279
280 81
        return $this->options;
281
    }
282
283
    /**
284
     * Get name of the field
285
     *
286
     * @return string
287
     */
288 24
    public function getName()
289
    {
290 24
        return $this->name;
291
    }
292
293
    /**
294
     * Set name of the field
295
     *
296
     * @param string $name
297
     * @return $this
298
     */
299 11
    public function setName($name)
300
    {
301 11
        $this->name = $name;
302
303 11
        return $this;
304
    }
305
306
    /**
307
     * Get dot notation key for fields
308
     *
309
     * @return string
310
     **/
311 47
    public function getNameKey()
312
    {
313 47
        return $this->transformKey($this->name);
314
    }
315
316
    /**
317
     * Get field options
318
     *
319
     * @return array
320
     */
321 12
    public function getOptions()
322
    {
323 12
        return $this->options;
324
    }
325
326
    /**
327
     * Get single option from options array. Can be used with dot notation ('attr.class')
328
     *
329
     * @param        $option
330
     * @param mixed  $default
331
     *
332
     * @return mixed
333
     */
334 81
    public function getOption($option, $default = null)
335
    {
336 81
        return array_get($this->options, $option, $default);
337
    }
338
339
    /**
340
     * Set field options
341
     *
342
     * @param array $options
343
     * @return $this
344
     */
345 11
    public function setOptions($options)
346
    {
347 11
        $this->options = $this->prepareOptions($options);
348
349 11
        return $this;
350
    }
351
352
    /**
353
     * Set single option on the field
354
     *
355
     * @param string $name
356
     * @param mixed $value
357
     * @return $this
358
     */
359 81
    public function setOption($name, $value)
360
    {
361 81
        array_set($this->options, $name, $value);
362
363 81
        return $this;
364
    }
365
366
    /**
367
     * Get the type of the field
368
     *
369
     * @return string
370
     */
371 48
    public function getType()
372
    {
373 48
        return $this->type;
374
    }
375
376
    /**
377
     * Set type of the field
378
     *
379
     * @param mixed $type
380
     * @return $this
381
     */
382 1
    public function setType($type)
383
    {
384 1
        if ($this->formHelper->getFieldType($type)) {
385 1
            $this->type = $type;
386
        }
387
388 1
        return $this;
389
    }
390
391
    /**
392
     * @return Form
393
     */
394 81
    public function getParent()
395
    {
396 81
        return $this->parent;
397
    }
398
399
    /**
400
     * Check if the field is rendered
401
     *
402
     * @return bool
403
     */
404 4
    public function isRendered()
405
    {
406 4
        return $this->rendered;
407
    }
408
409
    /**
410
     * Default options for field
411
     *
412
     * @return array
413
     */
414 61
    protected function getDefaults()
415
    {
416 61
        return [];
417
    }
418
419
    /**
420
     * Defaults used across all fields
421
     *
422
     * @return array
423
     */
424 81
    private function allDefaults()
425
    {
426
        return [
427 81
            'wrapper' => ['class' => $this->formHelper->getConfig('defaults.wrapper_class')],
428 81
            'attr' => ['class' => $this->formHelper->getConfig('defaults.field_class')],
429 81
            'help_block' => ['text' => null, 'tag' => 'p', 'attr' => [
430 81
                'class' => $this->formHelper->getConfig('defaults.help_block_class')
431
            ]],
432
            'value' => null,
433
            'default_value' => null,
434
            'label' => null,
435
            'label_show' => true,
436
            'is_child' => false,
437 81
            'label_attr' => ['class' => $this->formHelper->getConfig('defaults.label_class')],
438 81
            'errors' => ['class' => $this->formHelper->getConfig('defaults.error_class')],
439
            'rules' => [],
440
            'error_messages' => []
441
        ];
442
    }
443
444
    /**
445
     * Get real name of the field without form namespace
446
     *
447
     * @return string
448
     */
449 80
    public function getRealName()
450
    {
451 80
        return $this->getOption('real_name', $this->name);
452
    }
453
454
    /**
455
     * @param $value
456
     * @return $this
457
     */
458 75
    public function setValue($value)
459
    {
460 75
        if ($this->hasDefault) {
461 1
            return $this;
462
        }
463
464 75
        $closure = $this->valueClosure;
465
466 75
        if ($closure instanceof \Closure) {
467
            $value = $closure($value ?: null);
468
        }
469
470 75
        if (!$this->isValidValue($value)) {
471 73
            $value = $this->getOption($this->defaultValueProperty);
472
        }
473
474 75
        $this->options[$this->valueProperty] = $value;
475
476 75
        return $this;
477
    }
478
479
    /**
480
     * Set the template property on the object
481
     */
482 81
    private function setTemplate()
483
    {
484 81
        $this->template = $this->formHelper->getConfig($this->getTemplate(), $this->getTemplate());
485 81
    }
486
487
    /**
488
     * Add error class to wrapper if validation errors exist
489
     */
490 81
    protected function addErrorClass()
491
    {
492 81
        $errors = $this->parent->getRequest()->session()->get('errors');
493
494 81
        if ($errors && $errors->has($this->getNameKey())) {
495
            $errorClass = $this->formHelper->getConfig('defaults.wrapper_error_class');
496
            $wrapperClass = $this->getOption('wrapper.class');
497
498
            if ($this->getOption('wrapper') && !str_contains($wrapperClass, $errorClass)) {
499
                $wrapperClass .= ' ' . $errorClass;
500
                $this->setOption('wrapper.class', $wrapperClass);
501
            }
502
        }
503 81
    }
504
505
506
    /**
507
     * Merge all defaults with field specific defaults and set template if passed
508
     *
509
     * @param array $options
510
     */
511 81
    protected function setDefaultOptions(array $options = [])
512
    {
513 81
        $this->options = $this->formHelper->mergeOptions($this->allDefaults(), $this->getDefaults());
514 81
        $this->options = $this->prepareOptions($options);
515
516 81
        $defaults = $this->setDefaultClasses($options);
517 81
        $this->options = $this->formHelper->mergeOptions($this->options, $defaults);
518
519 81
        $this->setupLabel();
520 81
    }
521
522
    /**
523
     * Creates default wrapper classes for the form element.
524
     *
525
     * @param array $options
526
     * @return array
527
     */
528 81
    protected function setDefaultClasses(array $options = [])
529
    {
530 81
        $wrapper_class = $this->formHelper->getConfig('defaults.' . $this->type . '.wrapper_class', '');
531 81
        $label_class = $this->formHelper->getConfig('defaults.' . $this->type . '.label_class', '');
532 81
        $field_class = $this->formHelper->getConfig('defaults.' . $this->type . '.field_class', '');
533
534 81
        $defaults = [];
535 81
        if ($wrapper_class) {
536
            $defaults['wrapper']['class'] = $wrapper_class;
537
        }
538 81
        if ($label_class) {
539
            $defaults['label_attr']['class'] = $label_class;
540
        }
541 81
        if ($field_class) {
542
            $defaults['attr']['class'] = $field_class;
543
        }
544 81
        return $defaults;
545
    }
546
547 81
    protected function setupLabel()
548
    {
549 81
        if ($this->getOption('label') !== null) {
550 20
            return;
551
        }
552
553 79
        if ($langName = $this->parent->getLanguageName()) {
554 4
            $label = sprintf('%s.%s', $langName, $this->getRealName());
555
        } else {
556 76
            $label = $this->getRealName();
557
        }
558
559 79
        $this->setOption('label', $this->formHelper->formatLabel($label));
560 79
    }
561
562
    /**
563
     * Check if fields needs label
564
     *
565
     * @return bool
566
     */
567 31
    protected function needsLabel()
568
    {
569
        // If field is <select> and child of choice, we don't need label for it
570 31
        $isChildSelect = $this->type == 'select' && $this->getOption('is_child') === true;
571
572 31
        if ($this->type == 'hidden' || $isChildSelect) {
573 9
            return false;
574
        }
575
576 27
        return true;
577
    }
578
579
    /**
580
     * Disable field
581
     *
582
     * @return $this
583
     */
584 1
    public function disable()
585
    {
586 1
        $this->setOption('attr.disabled', 'disabled');
587
588 1
        return $this;
589
    }
590
591
    /**
592
     * Enable field
593
     *
594
     * @return $this
595
     */
596 1
    public function enable()
597
    {
598 1
        array_forget($this->options, 'attr.disabled');
599
600 1
        return $this;
601
    }
602
603
    /**
604
     * Get validation rules for a field if any with label for attributes
605
     *
606
     * @return array|null
607
     */
608 7
    public function getValidationRules()
609
    {
610 7
        $rules = $this->getOption('rules', []);
611 7
        $name = $this->getNameKey();
612 7
        $messages = $this->getOption('error_messages', []);
613 7
        $formName = $this->parent->getName();
614
615 7
        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...
616 1
            $newMessages = [];
617 1
            foreach ($messages as $messageKey => $message) {
618 1
                $messageKey = sprintf('%s.%s', $formName, $messageKey);
619 1
                $newMessages[$messageKey] = $message;
620
            }
621 1
            $messages = $newMessages;
622
        }
623
624 7
        if (!$rules) {
625 1
            return [];
626
        }
627
628
        return [
629 7
            'rules' => [$name => $rules],
630 7
            'attributes' => [$name => $this->getOption('label')],
631 7
            'error_messages' => $messages
632
        ];
633
    }
634
635
    /**
636
     * Get this field's attributes, probably just one.
637
     *
638
     * @return array
639
     */
640 1
    public function getAllAttributes()
641
    {
642 1
        return [$this->getNameKey()];
643
    }
644
645
    /**
646
     * Get value property
647
     *
648
     * @param mixed|null $default
649
     * @return mixed
650
     */
651 34
    public function getValue($default = null)
652
    {
653 34
        return $this->getOption($this->valueProperty, $default);
654
    }
655
656
    /**
657
     * Get default value property
658
     *
659
     * @param mixed|null $default
660
     * @return mixed
661
     */
662 31
    public function getDefaultValue($default = null)
663
    {
664 31
        return $this->getOption($this->defaultValueProperty, $default);
665
    }
666
667
    /**
668
     * Check if provided value is valid for this type
669
     *
670
     * @return bool
671
     */
672 76
    protected function isValidValue($value)
673
    {
674 76
        return $value !== null;
675
    }
676
}
677