Completed
Push — master ( 4cd325...0e7ea3 )
by Kristijan
05:44
created

FormField::getAllAttributes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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