Completed
Push — master ( f2fcb0...d4b67c )
by Song
03:48 queued 51s
created

Field::setWidgetForm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Form;
4
5
use Closure;
6
use Encore\Admin\Admin;
7
use Encore\Admin\Form;
8
use Encore\Admin\Widgets\Form as WidgetForm;
9
use Illuminate\Contracts\Support\Arrayable;
10
use Illuminate\Contracts\Support\Renderable;
11
use Illuminate\Support\Arr;
12
use Illuminate\Support\Str;
13
use Illuminate\Support\Traits\Macroable;
14
15
/**
16
 * Class Field.
17
 */
18
class Field implements Renderable
19
{
20
    use Macroable;
21
22
    const FILE_DELETE_FLAG = '_file_del_';
23
    const FILE_SORT_FLAG = '_file_sort_';
24
25
    /**
26
     * Element id.
27
     *
28
     * @var array|string
29
     */
30
    protected $id;
31
32
    /**
33
     * Element value.
34
     *
35
     * @var mixed
36
     */
37
    protected $value;
38
39
    /**
40
     * Data of all original columns of value.
41
     *
42
     * @var mixed
43
     */
44
    protected $data;
45
46
    /**
47
     * Field original value.
48
     *
49
     * @var mixed
50
     */
51
    protected $original;
52
53
    /**
54
     * Field default value.
55
     *
56
     * @var mixed
57
     */
58
    protected $default;
59
60
    /**
61
     * Element label.
62
     *
63
     * @var string
64
     */
65
    protected $label = '';
66
67
    /**
68
     * Column name.
69
     *
70
     * @var string|array
71
     */
72
    protected $column = '';
73
74
    /**
75
     * Form element name.
76
     *
77
     * @var string
78
     */
79
    protected $elementName = [];
80
81
    /**
82
     * Form element classes.
83
     *
84
     * @var array
85
     */
86
    protected $elementClass = [];
87
88
    /**
89
     * Variables of elements.
90
     *
91
     * @var array
92
     */
93
    protected $variables = [];
94
95
    /**
96
     * Options for specify elements.
97
     *
98
     * @var array
99
     */
100
    protected $options = [];
101
102
    /**
103
     * Checked for specify elements.
104
     *
105
     * @var array
106
     */
107
    protected $checked = [];
108
109
    /**
110
     * Validation rules.
111
     *
112
     * @var array|\Closure
113
     */
114
    protected $rules = [];
115
116
    /**
117
     * The validation rules for creation.
118
     *
119
     * @var array|\Closure
120
     */
121
    public $creationRules = [];
122
123
    /**
124
     * The validation rules for updates.
125
     *
126
     * @var array|\Closure
127
     */
128
    public $updateRules = [];
129
130
    /**
131
     * @var \Closure
132
     */
133
    protected $validator;
134
135
    /**
136
     * Validation messages.
137
     *
138
     * @var array
139
     */
140
    protected $validationMessages = [];
141
142
    /**
143
     * Css required by this field.
144
     *
145
     * @var array
146
     */
147
    protected static $css = [];
148
149
    /**
150
     * Js required by this field.
151
     *
152
     * @var array
153
     */
154
    protected static $js = [];
155
156
    /**
157
     * Script for field.
158
     *
159
     * @var string
160
     */
161
    protected $script = '';
162
163
    /**
164
     * Element attributes.
165
     *
166
     * @var array
167
     */
168
    protected $attributes = [];
169
170
    /**
171
     * Parent form.
172
     *
173
     * @var Form
174
     */
175
    protected $form = null;
176
177
    /**
178
     * View for field to render.
179
     *
180
     * @var string
181
     */
182
    protected $view = '';
183
184
    /**
185
     * Help block.
186
     *
187
     * @var array
188
     */
189
    protected $help = [];
190
191
    /**
192
     * Key for errors.
193
     *
194
     * @var mixed
195
     */
196
    protected $errorKey;
197
198
    /**
199
     * Placeholder for this field.
200
     *
201
     * @var string|array
202
     */
203
    protected $placeholder;
204
205
    /**
206
     * Width for label and field.
207
     *
208
     * @var array
209
     */
210
    protected $width = [
211
        'label' => 2,
212
        'field' => 8,
213
    ];
214
215
    /**
216
     * If the form horizontal layout.
217
     *
218
     * @var bool
219
     */
220
    protected $horizontal = true;
221
222
    /**
223
     * column data format.
224
     *
225
     * @var \Closure
226
     */
227
    protected $customFormat = null;
228
229
    /**
230
     * @var bool
231
     */
232
    protected $display = true;
233
234
    /**
235
     * @var array
236
     */
237
    protected $labelClass = [];
238
239
    /**
240
     * @var array
241
     */
242
    protected $groupClass = [];
243
244
    /**
245
     * @var \Closure
246
     */
247
    protected $callback;
248
249
    /**
250
     * @var bool
251
     */
252
    public $isJsonType = false;
253
254
    /**
255
     * Field constructor.
256
     *
257
     * @param       $column
258
     * @param array $arguments
259
     */
260
    public function __construct($column = '', $arguments = [])
261
    {
262
        $this->column = $this->formatColumn($column);
263
        $this->label = $this->formatLabel($arguments);
264
        $this->id = $this->formatId($column);
265
    }
266
267
    /**
268
     * Get assets required by this field.
269
     *
270
     * @return array
271
     */
272
    public static function getAssets()
273
    {
274
        return [
275
            'css' => static::$css,
276
            'js'  => static::$js,
277
        ];
278
    }
279
280
    /**
281
     * Format the field column name.
282
     *
283
     * @param string $column
284
     *
285
     * @return mixed|string
286
     */
287
    protected function formatColumn($column = '')
288
    {
289
        if (Str::contains($column, '->')) {
290
            $this->isJsonType = true;
291
292
            $column = str_replace('->', '.', $column);
293
        }
294
295
        return $column;
296
    }
297
298
    /**
299
     * Format the field element id.
300
     *
301
     * @param string|array $column
302
     *
303
     * @return string|array
304
     */
305
    protected function formatId($column)
306
    {
307
        return str_replace('.', '_', $column);
308
    }
309
310
    /**
311
     * Format the label value.
312
     *
313
     * @param array $arguments
314
     *
315
     * @return string
316
     */
317
    protected function formatLabel($arguments = []): string
318
    {
319
        $column = is_array($this->column) ? current($this->column) : $this->column;
320
321
        $label = $arguments[0] ?? ucfirst($column);
322
323
        return str_replace(['.', '_', '->'], ' ', $label);
324
    }
325
326
    /**
327
     * Format the name of the field.
328
     *
329
     * @param string $column
330
     *
331
     * @return array|mixed|string
332
     */
333
    protected function formatName($column)
334
    {
335
        if (is_string($column)) {
336
            if (Str::contains($column, '->')) {
337
                $name = explode('->', $column);
338
            } else {
339
                $name = explode('.', $column);
340
            }
341
342
            if (count($name) === 1) {
343
                return $name[0];
344
            }
345
346
            $html = array_shift($name);
347
            foreach ($name as $piece) {
348
                $html .= "[$piece]";
349
            }
350
351
            return $html;
352
        }
353
354
        if (is_array($this->column)) {
355
            $names = [];
356
            foreach ($this->column as $key => $name) {
357
                $names[$key] = $this->formatName($name);
358
            }
359
360
            return $names;
361
        }
362
363
        return '';
364
    }
365
366
    /**
367
     * Set form element name.
368
     *
369
     * @param string $name
370
     *
371
     * @return $this
372
     *
373
     * @author Edwin Hui
374
     */
375
    public function setElementName($name): self
376
    {
377
        $this->elementName = $name;
378
379
        return $this;
380
    }
381
382
    /**
383
     * Fill data to the field.
384
     *
385
     * @param array $data
386
     *
387
     * @return void
388
     */
389 View Code Duplication
    public function fill($data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
390
    {
391
        $this->data = $data;
392
393
        if (is_array($this->column)) {
394
            foreach ($this->column as $key => $column) {
395
                $this->value[$key] = Arr::get($data, $column);
396
            }
397
398
            return;
399
        }
400
401
        $this->value = Arr::get($data, $this->column);
402
403
        $this->formatValue();
404
    }
405
406
    /**
407
     * Format value by passing custom formater.
408
     */
409
    protected function formatValue()
410
    {
411
        if (isset($this->customFormat) && $this->customFormat instanceof \Closure) {
412
            $this->value = call_user_func($this->customFormat, $this->value);
413
        }
414
    }
415
416
    /**
417
     * custom format form column data when edit.
418
     *
419
     * @param \Closure $call
420
     *
421
     * @return $this
422
     */
423
    public function customFormat(\Closure $call): self
424
    {
425
        $this->customFormat = $call;
426
427
        return $this;
428
    }
429
430
    /**
431
     * Set original value to the field.
432
     *
433
     * @param array $data
434
     *
435
     * @return void
436
     */
437 View Code Duplication
    public function setOriginal($data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
438
    {
439
        if (is_array($this->column)) {
440
            foreach ($this->column as $key => $column) {
441
                $this->original[$key] = Arr::get($data, $column);
442
            }
443
444
            return;
445
        }
446
447
        $this->original = Arr::get($data, $this->column);
448
    }
449
450
    /**
451
     * @param Form $form
452
     *
453
     * @return $this
454
     */
455
    public function setForm(Form $form = null)
456
    {
457
        $this->form = $form;
458
459
        return $this;
460
    }
461
462
    /**
463
     * Set Widget/Form as field parent.
464
     *
465
     * @param WidgetForm $form
466
     * @return $this
467
     */
468
    public function setWidgetForm(WidgetForm $form)
469
    {
470
        $this->form = $form;
0 ignored issues
show
Documentation Bug introduced by
It seems like $form of type object<Encore\Admin\Widgets\Form> is incompatible with the declared type object<Encore\Admin\Form> of property $form.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
471
472
        return $this;
473
    }
474
475
    /**
476
     * Set width for field and label.
477
     *
478
     * @param int $field
479
     * @param int $label
480
     *
481
     * @return $this
482
     */
483
    public function setWidth($field = 8, $label = 2): self
484
    {
485
        $this->width = [
486
            'label' => $label,
487
            'field' => $field,
488
        ];
489
490
        return $this;
491
    }
492
493
    /**
494
     * Set the field options.
495
     *
496
     * @param array $options
497
     *
498
     * @return $this
499
     */
500 View Code Duplication
    public function options($options = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
501
    {
502
        if ($options instanceof Arrayable) {
503
            $options = $options->toArray();
504
        }
505
506
        $this->options = array_merge($this->options, $options);
507
508
        return $this;
509
    }
510
511
    /**
512
     * Set the field option checked.
513
     *
514
     * @param array $checked
515
     *
516
     * @return $this
517
     */
518 View Code Duplication
    public function checked($checked = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
519
    {
520
        if ($checked instanceof Arrayable) {
521
            $checked = $checked->toArray();
522
        }
523
524
        $this->checked = array_merge($this->checked, $checked);
525
526
        return $this;
527
    }
528
529
    /**
530
     * Add `required` attribute to current field if has required rule,
531
     * except file and image fields.
532
     *
533
     * @param array $rules
534
     */
535
    protected function addRequiredAttribute($rules)
536
    {
537
        if (!is_array($rules)) {
538
            return;
539
        }
540
541
        if (!in_array('required', $rules, true)) {
542
            return;
543
        }
544
545
        $this->setLabelClass(['asterisk']);
546
547
        // Only text field has `required` attribute.
548
        if (!$this instanceof Form\Field\Text) {
549
            return;
550
        }
551
552
        //do not use required attribute with tabs
553
        if ($this->form && $this->form->getTab()) {
554
            return;
555
        }
556
557
        $this->required();
558
    }
559
560
    /**
561
     * If has `required` rule, add required attribute to this field.
562
     */
563
    protected function addRequiredAttributeFromRules()
564
    {
565
        if ($this->data === null) {
566
            // Create page
567
            $rules = $this->creationRules ?: $this->rules;
568
        } else {
569
            // Update page
570
            $rules = $this->updateRules ?: $this->rules;
571
        }
572
573
        $this->addRequiredAttribute($rules);
0 ignored issues
show
Bug introduced by
It seems like $rules can also be of type object<Closure>; however, Encore\Admin\Form\Field::addRequiredAttribute() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
574
    }
575
576
    /**
577
     * Format validation rules.
578
     *
579
     * @param array|string $rules
580
     *
581
     * @return array
582
     */
583
    protected function formatRules($rules): array
584
    {
585
        if (is_string($rules)) {
586
            $rules = array_filter(explode('|', $rules));
587
        }
588
589
        return array_filter((array) $rules);
590
    }
591
592
    /**
593
     * @param string|array|Closure $input
594
     * @param string|array         $original
595
     *
596
     * @return array|Closure
597
     */
598
    protected function mergeRules($input, $original)
599
    {
600
        if ($input instanceof Closure) {
601
            $rules = $input;
602
        } else {
603
            if (!empty($original)) {
604
                $original = $this->formatRules($original);
605
            }
606
607
            $rules = array_merge($original, $this->formatRules($input));
608
        }
609
610
        return $rules;
611
    }
612
613
    /**
614
     * Set the validation rules for the field.
615
     *
616
     * @param array|callable|string $rules
617
     * @param array                 $messages
618
     *
619
     * @return $this
620
     */
621 View Code Duplication
    public function rules($rules = null, $messages = []): self
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
622
    {
623
        $this->rules = $this->mergeRules($rules, $this->rules);
0 ignored issues
show
Documentation introduced by
$rules is of type callable|null, but the function expects a string|array|object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
It seems like $this->rules can also be of type object<Closure>; however, Encore\Admin\Form\Field::mergeRules() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
624
625
        $this->setValidationMessages('default', $messages);
626
627
        return $this;
628
    }
629
630
    /**
631
     * Set the update validation rules for the field.
632
     *
633
     * @param array|callable|string $rules
634
     * @param array                 $messages
635
     *
636
     * @return $this
637
     */
638 View Code Duplication
    public function updateRules($rules = null, $messages = []): self
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
639
    {
640
        $this->updateRules = $this->mergeRules($rules, $this->updateRules);
0 ignored issues
show
Documentation introduced by
$rules is of type callable|null, but the function expects a string|array|object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
It seems like $this->updateRules can also be of type object<Closure>; however, Encore\Admin\Form\Field::mergeRules() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
641
642
        $this->setValidationMessages('update', $messages);
643
644
        return $this;
645
    }
646
647
    /**
648
     * Set the creation validation rules for the field.
649
     *
650
     * @param array|callable|string $rules
651
     * @param array                 $messages
652
     *
653
     * @return $this
654
     */
655
    public function creationRules($rules = null, $messages = []): self
656
    {
657
        $this->creationRules = $this->mergeRules($rules, $this->creationRules);
0 ignored issues
show
Documentation introduced by
$rules is of type callable|null, but the function expects a string|array|object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
It seems like $this->creationRules can also be of type object<Closure>; however, Encore\Admin\Form\Field::mergeRules() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
658
659
        $this->setValidationMessages('creation', $messages);
660
661
        return $this;
662
    }
663
664
    /**
665
     * Set validation messages for column.
666
     *
667
     * @param string $key
668
     * @param array  $messages
669
     *
670
     * @return $this
671
     */
672
    public function setValidationMessages($key, array $messages): self
673
    {
674
        $this->validationMessages[$key] = $messages;
675
676
        return $this;
677
    }
678
679
    /**
680
     * Get validation messages for the field.
681
     *
682
     * @return array|mixed
683
     */
684
    public function getValidationMessages()
685
    {
686
        // Default validation message.
687
        $messages = $this->validationMessages['default'] ?? [];
688
689
        if (request()->isMethod('POST')) {
690
            $messages = $this->validationMessages['creation'] ?? $messages;
691
        } elseif (request()->isMethod('PUT')) {
692
            $messages = $this->validationMessages['update'] ?? $messages;
693
        }
694
695
        return $messages;
696
    }
697
698
    /**
699
     * Get field validation rules.
700
     *
701
     * @return string
702
     */
703
    protected function getRules()
704
    {
705
        if (request()->isMethod('POST')) {
706
            $rules = $this->creationRules ?: $this->rules;
707
        } elseif (request()->isMethod('PUT')) {
708
            $rules = $this->updateRules ?: $this->rules;
709
        } else {
710
            $rules = $this->rules;
711
        }
712
713
        if ($rules instanceof \Closure) {
714
            $rules = $rules->call($this, $this->form);
715
        }
716
717
        if (is_string($rules)) {
718
            $rules = array_filter(explode('|', $rules));
719
        }
720
721
        if (!$this->form) {
722
            return $rules;
723
        }
724
725
        if (!$id = $this->form->model()->getKey()) {
726
            return $rules;
727
        }
728
729
        if (is_array($rules)) {
730
            foreach ($rules as &$rule) {
731
                if (is_string($rule)) {
732
                    $rule = str_replace('{{id}}', $id, $rule);
733
                }
734
            }
735
        }
736
737
        return $rules;
738
    }
739
740
    /**
741
     * Remove a specific rule by keyword.
742
     *
743
     * @param string $rule
744
     *
745
     * @return void
746
     */
747
    protected function removeRule($rule)
748
    {
749
        if (is_array($this->rules)) {
750
            array_delete($this->rules, $rule);
751
752
            return;
753
        }
754
755
        if (!is_string($this->rules)) {
756
            return;
757
        }
758
759
        $pattern = "/{$rule}[^\|]?(\||$)/";
760
        $this->rules = preg_replace($pattern, '', $this->rules, -1);
0 ignored issues
show
Documentation Bug introduced by
It seems like preg_replace($pattern, '', $this->rules, -1) of type string is incompatible with the declared type array|object<Closure> of property $rules.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
761
    }
762
763
    /**
764
     * Set field validator.
765
     *
766
     * @param callable $validator
767
     *
768
     * @return $this
769
     */
770
    public function validator(callable $validator): self
771
    {
772
        $this->validator = $validator;
0 ignored issues
show
Documentation Bug introduced by
It seems like $validator of type callable is incompatible with the declared type object<Closure> of property $validator.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
773
774
        return $this;
775
    }
776
777
    /**
778
     * Get key for error message.
779
     *
780
     * @return string|array
781
     */
782
    public function getErrorKey()
783
    {
784
        return $this->errorKey ?: $this->column;
785
    }
786
787
    /**
788
     * Set key for error message.
789
     *
790
     * @param string $key
791
     *
792
     * @return $this
793
     */
794
    public function setErrorKey($key): self
795
    {
796
        $this->errorKey = $key;
797
798
        return $this;
799
    }
800
801
    /**
802
     * Set or get value of the field.
803
     *
804
     * @param null $value
805
     *
806
     * @return mixed
807
     */
808
    public function value($value = null)
809
    {
810
        if ($value === null) {
811
            return $this->value ?? $this->getDefault();
812
        }
813
814
        $this->value = $value;
815
816
        return $this;
817
    }
818
819
    /**
820
     * Set or get data.
821
     *
822
     * @param array $data
823
     *
824
     * @return mixed
825
     */
826
    public function data(array $data = null)
827
    {
828
        if ($data === null) {
829
            return $this->data;
830
        }
831
832
        $this->data = $data;
833
834
        return $this;
835
    }
836
837
    /**
838
     * Set default value for field.
839
     *
840
     * @param $default
841
     *
842
     * @return $this
843
     */
844
    public function default($default): self
845
    {
846
        $this->default = $default;
847
848
        return $this;
849
    }
850
851
    /**
852
     * Get default value.
853
     *
854
     * @return mixed
855
     */
856
    public function getDefault()
857
    {
858
        if ($this->default instanceof \Closure) {
859
            return call_user_func($this->default, $this->form);
860
        }
861
862
        return $this->default;
863
    }
864
865
    /**
866
     * Set help block for current field.
867
     *
868
     * @param string $text
869
     * @param string $icon
870
     *
871
     * @return $this
872
     */
873
    public function help($text = '', $icon = 'fa-info-circle'): self
874
    {
875
        $this->help = compact('text', 'icon');
876
877
        return $this;
878
    }
879
880
    /**
881
     * Get column of the field.
882
     *
883
     * @return string|array
884
     */
885
    public function column()
886
    {
887
        return $this->column;
888
    }
889
890
    /**
891
     * Get label of the field.
892
     *
893
     * @return string
894
     */
895
    public function label(): string
896
    {
897
        return $this->label;
898
    }
899
900
    /**
901
     * Get original value of the field.
902
     *
903
     * @return mixed
904
     */
905
    public function original()
906
    {
907
        return $this->original;
908
    }
909
910
    /**
911
     * Get validator for this field.
912
     *
913
     * @param array $input
914
     *
915
     * @return bool|\Illuminate\Contracts\Validation\Validator|mixed
916
     */
917
    public function getValidator(array $input)
918
    {
919
        if ($this->validator) {
920
            return $this->validator->call($this, $input);
921
        }
922
923
        $rules = $attributes = [];
924
925
        if (!$fieldRules = $this->getRules()) {
926
            return false;
927
        }
928
929
        if (is_string($this->column)) {
930
            if (!Arr::has($input, $this->column)) {
931
                return false;
932
            }
933
934
            $input = $this->sanitizeInput($input, $this->column);
935
936
            $rules[$this->column] = $fieldRules;
937
            $attributes[$this->column] = $this->label;
938
        }
939
940
        if (is_array($this->column)) {
941
            foreach ($this->column as $key => $column) {
942
                if (!array_key_exists($column, $input)) {
943
                    continue;
944
                }
945
                $input[$column.$key] = Arr::get($input, $column);
946
                $rules[$column.$key] = $fieldRules;
947
                $attributes[$column.$key] = $this->label."[$column]";
948
            }
949
        }
950
951
        return \validator($input, $rules, $this->getValidationMessages(), $attributes);
952
    }
953
954
    /**
955
     * Sanitize input data.
956
     *
957
     * @param array  $input
958
     * @param string $column
959
     *
960
     * @return array
961
     */
962
    protected function sanitizeInput($input, $column)
963
    {
964
        if ($this instanceof Field\MultipleSelect) {
965
            $value = Arr::get($input, $column);
966
            Arr::set($input, $column, array_filter($value));
967
        }
968
969
        return $input;
970
    }
971
972
    /**
973
     * Add html attributes to elements.
974
     *
975
     * @param array|string $attribute
976
     * @param mixed        $value
977
     *
978
     * @return $this
979
     */
980
    public function attribute($attribute, $value = null): self
981
    {
982
        if (is_array($attribute)) {
983
            $this->attributes = array_merge($this->attributes, $attribute);
984
        } else {
985
            $this->attributes[$attribute] = (string) $value;
986
        }
987
988
        return $this;
989
    }
990
991
    /**
992
     * Remove html attributes from elements.
993
     *
994
     * @param array|string $attribute
995
     *
996
     * @return $this
997
     */
998
    public function removeAttribute($attribute): self
999
    {
1000
        Arr::forget($this->attributes, $attribute);
1001
1002
        return $this;
1003
    }
1004
1005
    /**
1006
     * Set Field style.
1007
     *
1008
     * @param string $attr
1009
     * @param string $value
1010
     *
1011
     * @return $this
1012
     */
1013
    public function style($attr, $value): self
1014
    {
1015
        return $this->attribute('style', "{$attr}: {$value}");
1016
    }
1017
1018
    /**
1019
     * Set Field width.
1020
     *
1021
     * @param string $width
1022
     *
1023
     * @return $this
1024
     */
1025
    public function width($width): self
1026
    {
1027
        return $this->style('width', $width);
1028
    }
1029
1030
    /**
1031
     * Specifies a regular expression against which to validate the value of the input.
1032
     *
1033
     * @param string $regexp
1034
     *
1035
     * @return $this
1036
     */
1037
    public function pattern($regexp): self
1038
    {
1039
        return $this->attribute('pattern', $regexp);
1040
    }
1041
1042
    /**
1043
     * set the input filed required.
1044
     *
1045
     * @param bool $isLabelAsterisked
1046
     *
1047
     * @return $this
1048
     */
1049
    public function required($isLabelAsterisked = true): self
1050
    {
1051
        if ($isLabelAsterisked) {
1052
            $this->setLabelClass(['asterisk']);
1053
        }
1054
1055
        return $this->attribute('required', true);
1056
    }
1057
1058
    /**
1059
     * Set the field automatically get focus.
1060
     *
1061
     * @return $this
1062
     */
1063
    public function autofocus(): self
1064
    {
1065
        return $this->attribute('autofocus', true);
1066
    }
1067
1068
    /**
1069
     * Set the field as readonly mode.
1070
     *
1071
     * @return $this
1072
     */
1073
    public function readonly()
1074
    {
1075
        return $this->attribute('readonly', true);
1076
    }
1077
1078
    /**
1079
     * Set field as disabled.
1080
     *
1081
     * @return $this
1082
     */
1083
    public function disable(): self
1084
    {
1085
        return $this->attribute('disabled', true);
1086
    }
1087
1088
    /**
1089
     * Set field placeholder.
1090
     *
1091
     * @param string $placeholder
1092
     *
1093
     * @return $this
1094
     */
1095
    public function placeholder($placeholder = ''): self
1096
    {
1097
        $this->placeholder = $placeholder;
1098
1099
        return $this;
1100
    }
1101
1102
    /**
1103
     * Get placeholder.
1104
     *
1105
     * @return mixed
1106
     */
1107
    public function getPlaceholder()
1108
    {
1109
        return $this->placeholder ?: trans('admin.input').' '.$this->label;
1110
    }
1111
1112
    /**
1113
     * Add a divider after this field.
1114
     *
1115
     * @return $this
1116
     */
1117
    public function divider()
1118
    {
1119
        $this->form->divider();
1120
1121
        return $this;
1122
    }
1123
1124
    /**
1125
     * Prepare for a field value before update or insert.
1126
     *
1127
     * @param $value
1128
     *
1129
     * @return mixed
1130
     */
1131
    public function prepare($value)
1132
    {
1133
        return $value;
1134
    }
1135
1136
    /**
1137
     * Format the field attributes.
1138
     *
1139
     * @return string
1140
     */
1141 View Code Duplication
    protected function formatAttributes(): string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1142
    {
1143
        $html = [];
1144
1145
        foreach ($this->attributes as $name => $value) {
1146
            $html[] = $name.'="'.e($value).'"';
1147
        }
1148
1149
        return implode(' ', $html);
1150
    }
1151
1152
    /**
1153
     * @return $this
1154
     */
1155
    public function disableHorizontal(): self
1156
    {
1157
        $this->horizontal = false;
1158
1159
        return $this;
1160
    }
1161
1162
    /**
1163
     * @return array
1164
     */
1165
    public function getViewElementClasses(): array
1166
    {
1167
        if ($this->horizontal) {
1168
            return [
1169
                'label'      => "col-sm-{$this->width['label']} {$this->getLabelClass()}",
1170
                'field'      => "col-sm-{$this->width['field']}",
1171
                'form-group' => $this->getGroupClass(true),
1172
            ];
1173
        }
1174
1175
        return ['label' => $this->getLabelClass(), 'field' => '', 'form-group' => ''];
1176
    }
1177
1178
    /**
1179
     * Set form element class.
1180
     *
1181
     * @param string|array $class
1182
     *
1183
     * @return $this
1184
     */
1185
    public function setElementClass($class): self
1186
    {
1187
        $this->elementClass = array_merge($this->elementClass, (array) $class);
1188
1189
        return $this;
1190
    }
1191
1192
    /**
1193
     * Get element class.
1194
     *
1195
     * @return array
1196
     */
1197
    public function getElementClass(): array
1198
    {
1199
        if (!$this->elementClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->elementClass 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...
1200
            $name = $this->elementName ?: $this->formatName($this->column);
0 ignored issues
show
Bug introduced by
It seems like $this->column can also be of type array; however, Encore\Admin\Form\Field::formatName() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1201
1202
            $this->elementClass = (array) str_replace(['[', ']'], '_', $name);
1203
        }
1204
1205
        return $this->elementClass;
1206
    }
1207
1208
    /**
1209
     * Get element class string.
1210
     *
1211
     * @return mixed
1212
     */
1213 View Code Duplication
    protected function getElementClassString()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1214
    {
1215
        $elementClass = $this->getElementClass();
1216
1217
        if (Arr::isAssoc($elementClass)) {
1218
            $classes = [];
1219
1220
            foreach ($elementClass as $index => $class) {
1221
                $classes[$index] = is_array($class) ? implode(' ', $class) : $class;
1222
            }
1223
1224
            return $classes;
1225
        }
1226
1227
        return implode(' ', $elementClass);
1228
    }
1229
1230
    /**
1231
     * Get element class selector.
1232
     *
1233
     * @return string|array
1234
     */
1235 View Code Duplication
    protected function getElementClassSelector()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1236
    {
1237
        $elementClass = $this->getElementClass();
1238
1239
        if (Arr::isAssoc($elementClass)) {
1240
            $classes = [];
1241
1242
            foreach ($elementClass as $index => $class) {
1243
                $classes[$index] = '.'.(is_array($class) ? implode('.', $class) : $class);
1244
            }
1245
1246
            return $classes;
1247
        }
1248
1249
        return '.'.implode('.', $elementClass);
1250
    }
1251
1252
    /**
1253
     * Add the element class.
1254
     *
1255
     * @param $class
1256
     *
1257
     * @return $this
1258
     */
1259
    public function addElementClass($class): self
1260
    {
1261
        if (is_array($class) || is_string($class)) {
1262
            $this->elementClass = array_unique(array_merge($this->elementClass, (array) $class));
1263
        }
1264
1265
        return $this;
1266
    }
1267
1268
    /**
1269
     * Remove element class.
1270
     *
1271
     * @param $class
1272
     *
1273
     * @return $this
1274
     */
1275
    public function removeElementClass($class): self
1276
    {
1277
        $delClass = [];
1278
1279
        if (is_string($class) || is_array($class)) {
1280
            $delClass = (array) $class;
1281
        }
1282
1283
        foreach ($delClass as $del) {
1284
            if (($key = array_search($del, $this->elementClass, true)) !== false) {
1285
                unset($this->elementClass[$key]);
1286
            }
1287
        }
1288
1289
        return $this;
1290
    }
1291
1292
    /**
1293
     * Set form group class.
1294
     *
1295
     * @param string|array $class
1296
     *
1297
     * @return $this
1298
     */
1299
    public function setGroupClass($class): self
1300
    {
1301
        if (is_array($class)) {
1302
            $this->groupClass = array_merge($this->groupClass, $class);
1303
        } else {
1304
            $this->groupClass[] = $class;
1305
        }
1306
1307
        return $this;
1308
    }
1309
1310
    /**
1311
     * Get element class.
1312
     *
1313
     * @param bool $default
1314
     *
1315
     * @return string
1316
     */
1317
    protected function getGroupClass($default = false): string
1318
    {
1319
        return ($default ? 'form-group ' : '').implode(' ', array_filter($this->groupClass));
1320
    }
1321
1322
    /**
1323
     * reset field className.
1324
     *
1325
     * @param string $className
1326
     * @param string $resetClassName
1327
     *
1328
     * @return $this
1329
     */
1330
    public function resetElementClassName(string $className, string $resetClassName): self
1331
    {
1332
        if (($key = array_search($className, $this->getElementClass())) !== false) {
1333
            $this->elementClass[$key] = $resetClassName;
1334
        }
1335
1336
        return $this;
1337
    }
1338
1339
    /**
1340
     * Add variables to field view.
1341
     *
1342
     * @param array $variables
1343
     *
1344
     * @return $this
1345
     */
1346
    protected function addVariables(array $variables = []): self
1347
    {
1348
        $this->variables = array_merge($this->variables, $variables);
1349
1350
        return $this;
1351
    }
1352
1353
    /**
1354
     * @return string
1355
     */
1356
    public function getLabelClass(): string
1357
    {
1358
        return implode(' ', $this->labelClass);
1359
    }
1360
1361
    /**
1362
     * @param array $labelClass
1363
     *
1364
     * @return self
1365
     */
1366
    public function setLabelClass(array $labelClass): self
1367
    {
1368
        $this->labelClass = $labelClass;
1369
1370
        return $this;
1371
    }
1372
1373
    /**
1374
     * Get the view variables of this field.
1375
     *
1376
     * @return array
1377
     */
1378
    public function variables(): array
1379
    {
1380
        return array_merge($this->variables, [
1381
            'id'          => $this->id,
1382
            'name'        => $this->elementName ?: $this->formatName($this->column),
0 ignored issues
show
Bug introduced by
It seems like $this->column can also be of type array; however, Encore\Admin\Form\Field::formatName() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1383
            'help'        => $this->help,
1384
            'class'       => $this->getElementClassString(),
1385
            'value'       => $this->value(),
1386
            'label'       => $this->label,
1387
            'viewClass'   => $this->getViewElementClasses(),
1388
            'column'      => $this->column,
1389
            'errorKey'    => $this->getErrorKey(),
1390
            'attributes'  => $this->formatAttributes(),
1391
            'placeholder' => $this->getPlaceholder(),
1392
        ]);
1393
    }
1394
1395
    /**
1396
     * Get view of this field.
1397
     *
1398
     * @return string
1399
     */
1400
    public function getView(): string
1401
    {
1402
        if (!empty($this->view)) {
1403
            return $this->view;
1404
        }
1405
1406
        $class = explode('\\', static::class);
1407
1408
        return 'admin::form.'.strtolower(end($class));
1409
    }
1410
1411
    /**
1412
     * Set view of current field.
1413
     *
1414
     * @param string $view
1415
     *
1416
     * @return string
1417
     */
1418
    public function setView($view): self
1419
    {
1420
        $this->view = $view;
1421
1422
        return $this;
1423
    }
1424
1425
    /**
1426
     * Get script of current field.
1427
     *
1428
     * @return string
1429
     */
1430
    public function getScript(): string
1431
    {
1432
        return $this->script;
1433
    }
1434
1435
    /**
1436
     * Set script of current field.
1437
     *
1438
     * @param string $script
1439
     *
1440
     * @return $this
1441
     */
1442
    public function setScript($script): self
1443
    {
1444
        $this->script = $script;
1445
1446
        return $this;
1447
    }
1448
1449
    /**
1450
     * To set this field should render or not.
1451
     *
1452
     * @param bool $display
1453
     *
1454
     * @return $this
1455
     */
1456
    public function setDisplay(bool $display): self
1457
    {
1458
        $this->display = $display;
1459
1460
        return $this;
1461
    }
1462
1463
    /**
1464
     * If this field should render.
1465
     *
1466
     * @return bool
1467
     */
1468
    protected function shouldRender(): bool
1469
    {
1470
        if (!$this->display) {
1471
            return false;
1472
        }
1473
1474
        return true;
1475
    }
1476
1477
    /**
1478
     * @param \Closure $callback
1479
     *
1480
     * @return \Encore\Admin\Form\Field
1481
     */
1482
    public function with(Closure $callback): self
1483
    {
1484
        $this->callback = $callback;
1485
1486
        return $this;
1487
    }
1488
1489
    /**
1490
     * Render this filed.
1491
     *
1492
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|string
1493
     */
1494
    public function render()
1495
    {
1496
        if (!$this->shouldRender()) {
1497
            return '';
1498
        }
1499
1500
        $this->addRequiredAttributeFromRules();
1501
1502
        if ($this->callback instanceof Closure) {
1503
            $this->value = $this->callback->call($this->form->model(), $this->value, $this);
1504
        }
1505
1506
        Admin::script($this->script);
1507
1508
        return view($this->getView(), $this->variables());
0 ignored issues
show
Bug Compatibility introduced by
The expression view($this->getView(), $this->variables()); of type Illuminate\View\View|Ill...\Contracts\View\Factory adds the type Illuminate\Contracts\View\Factory to the return on line 1508 which is incompatible with the return type declared by the interface Illuminate\Contracts\Support\Renderable::render of type string.
Loading history...
1509
    }
1510
1511
    protected function fieldRender()
1512
    {
1513
        return self::render();
1514
    }
1515
1516
    /**
1517
     * @return string
1518
     */
1519
    public function __toString()
1520
    {
1521
        return $this->render()->render();
0 ignored issues
show
Bug introduced by
The method render does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1522
    }
1523
}
1524