Completed
Push — master ( f3b5cb...518d84 )
by Kristijan
05:42
created

FormField::getTemplate()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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