Completed
Push — master ( c833c1...b511be )
by Song
03:16
created

Field::isDependsOn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
462
            $this->dependency = $dependency;
463
        }
464
465
        $this->form = $form;
466
467
        return $this;
468
    }
469
470
    public function getDependency()
471
    {
472
        return $this->dependency;
473
    }
474
475
    public function isDependsOn(Field $field)
476
    {
477
        if (!$this->dependency) {
478
            return false;
479
        }
480
481
        return $this->dependency['field'] == $field->column();
482
    }
483
484
    /**
485
     * Set width for field and label.
486
     *
487
     * @param int $field
488
     * @param int $label
489
     *
490
     * @return $this
491
     */
492
    public function setWidth($field = 8, $label = 2): self
493
    {
494
        $this->width = [
495
            'label' => $label,
496
            'field' => $field,
497
        ];
498
499
        return $this;
500
    }
501
502
    /**
503
     * Set the field options.
504
     *
505
     * @param array $options
506
     *
507
     * @return $this
508
     */
509 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...
510
    {
511
        if ($options instanceof Arrayable) {
512
            $options = $options->toArray();
513
        }
514
515
        $this->options = array_merge($this->options, $options);
516
517
        return $this;
518
    }
519
520
    /**
521
     * Set the field option checked.
522
     *
523
     * @param array $checked
524
     *
525
     * @return $this
526
     */
527 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...
528
    {
529
        if ($checked instanceof Arrayable) {
530
            $checked = $checked->toArray();
531
        }
532
533
        $this->checked = array_merge($this->checked, $checked);
534
535
        return $this;
536
    }
537
538
    /**
539
     * Add `required` attribute to current field if has required rule,
540
     * except file and image fields.
541
     *
542
     * @param array $rules
543
     */
544
    protected function addRequiredAttribute($rules)
545
    {
546
        if (!is_array($rules)) {
547
            return;
548
        }
549
550
        if (!in_array('required', $rules, true)) {
551
            return;
552
        }
553
554
        $this->setLabelClass(['asterisk']);
555
556
        // Only text field has `required` attribute.
557
        if (!$this instanceof Form\Field\Text) {
558
            return;
559
        }
560
561
        //do not use required attribute with tabs
562
        if ($this->form && $this->form->getTab()) {
563
            return;
564
        }
565
566
        $this->required();
567
    }
568
569
    /**
570
     * If has `required` rule, add required attribute to this field.
571
     */
572
    protected function addRequiredAttributeFromRules()
573
    {
574
        if ($this->data === null) {
575
            // Create page
576
            $rules = $this->creationRules ?: $this->rules;
577
        } else {
578
            // Update page
579
            $rules = $this->updateRules ?: $this->rules;
580
        }
581
582
        $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...
583
    }
584
585
    /**
586
     * Format validation rules.
587
     *
588
     * @param array|string $rules
589
     *
590
     * @return array
591
     */
592
    protected function formatRules($rules): array
593
    {
594
        if (is_string($rules)) {
595
            $rules = array_filter(explode('|', $rules));
596
        }
597
598
        return array_filter((array) $rules);
599
    }
600
601
    /**
602
     * @param string|array|Closure $input
603
     * @param string|array         $original
604
     *
605
     * @return array|Closure
606
     */
607
    protected function mergeRules($input, $original)
608
    {
609
        if ($input instanceof Closure) {
610
            $rules = $input;
611
        } else {
612
            if (!empty($original)) {
613
                $original = $this->formatRules($original);
614
            }
615
616
            $rules = array_merge($original, $this->formatRules($input));
617
        }
618
619
        return $rules;
620
    }
621
622
    /**
623
     * Set the validation rules for the field.
624
     *
625
     * @param array|callable|string $rules
626
     * @param array                 $messages
627
     *
628
     * @return $this
629
     */
630 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...
631
    {
632
        $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...
633
634
        $this->setValidationMessages('default', $messages);
635
636
        return $this;
637
    }
638
639
    /**
640
     * Set the update validation rules for the field.
641
     *
642
     * @param array|callable|string $rules
643
     * @param array                 $messages
644
     *
645
     * @return $this
646
     */
647 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...
648
    {
649
        $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...
650
651
        $this->setValidationMessages('update', $messages);
652
653
        return $this;
654
    }
655
656
    /**
657
     * Set the creation validation rules for the field.
658
     *
659
     * @param array|callable|string $rules
660
     * @param array                 $messages
661
     *
662
     * @return $this
663
     */
664
    public function creationRules($rules = null, $messages = []): self
665
    {
666
        $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...
667
668
        $this->setValidationMessages('creation', $messages);
669
670
        return $this;
671
    }
672
673
    /**
674
     * Set validation messages for column.
675
     *
676
     * @param string $key
677
     * @param array  $messages
678
     *
679
     * @return $this
680
     */
681
    public function setValidationMessages($key, array $messages): self
682
    {
683
        $this->validationMessages[$key] = $messages;
684
685
        return $this;
686
    }
687
688
    /**
689
     * Get validation messages for the field.
690
     *
691
     * @return array|mixed
692
     */
693
    public function getValidationMessages()
694
    {
695
        // Default validation message.
696
        $messages = $this->validationMessages['default'] ?? [];
697
698
        if (request()->isMethod('POST')) {
699
            $messages = $this->validationMessages['creation'] ?? $messages;
700
        } elseif (request()->isMethod('PUT')) {
701
            $messages = $this->validationMessages['update'] ?? $messages;
702
        }
703
704
        return $messages;
705
    }
706
707
    /**
708
     * Get field validation rules.
709
     *
710
     * @return string
711
     */
712
    protected function getRules()
713
    {
714
        if (request()->isMethod('POST')) {
715
            $rules = $this->creationRules ?: $this->rules;
716
        } elseif (request()->isMethod('PUT')) {
717
            $rules = $this->updateRules ?: $this->rules;
718
        } else {
719
            $rules = $this->rules;
720
        }
721
722
        if ($rules instanceof \Closure) {
723
            $rules = $rules->call($this, $this->form);
724
        }
725
726
        if (is_string($rules)) {
727
            $rules = array_filter(explode('|', $rules));
728
        }
729
730
        if (!$this->form) {
731
            return $rules;
732
        }
733
734
        if (!$id = $this->form->model()->getKey()) {
735
            return $rules;
736
        }
737
738
        if (is_array($rules)) {
739
            foreach ($rules as &$rule) {
740
                if (is_string($rule)) {
741
                    $rule = str_replace('{{id}}', $id, $rule);
742
                }
743
            }
744
        }
745
746
        return $rules;
747
    }
748
749
    /**
750
     * Remove a specific rule by keyword.
751
     *
752
     * @param string $rule
753
     *
754
     * @return void
755
     */
756
    protected function removeRule($rule)
757
    {
758
        if (is_array($this->rules)) {
759
            array_delete($this->rules, $rule);
760
761
            return;
762
        }
763
764
        if (!is_string($this->rules)) {
765
            return;
766
        }
767
768
        $pattern = "/{$rule}[^\|]?(\||$)/";
769
        $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...
770
    }
771
772
    /**
773
     * Set field validator.
774
     *
775
     * @param callable $validator
776
     *
777
     * @return $this
778
     */
779
    public function validator(callable $validator): self
780
    {
781
        $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...
782
783
        return $this;
784
    }
785
786
    /**
787
     * Get key for error message.
788
     *
789
     * @return string|array
790
     */
791
    public function getErrorKey()
792
    {
793
        return $this->errorKey ?: $this->column;
794
    }
795
796
    /**
797
     * Set key for error message.
798
     *
799
     * @param string $key
800
     *
801
     * @return $this
802
     */
803
    public function setErrorKey($key): self
804
    {
805
        $this->errorKey = $key;
806
807
        return $this;
808
    }
809
810
    /**
811
     * Set or get value of the field.
812
     *
813
     * @param null $value
814
     *
815
     * @return mixed
816
     */
817
    public function value($value = null)
818
    {
819
        if ($value === null) {
820
            return $this->value ?? $this->getDefault();
821
        }
822
823
        $this->value = $value;
824
825
        return $this;
826
    }
827
828
    /**
829
     * Set or get data.
830
     *
831
     * @param array $data
832
     *
833
     * @return mixed
834
     */
835
    public function data(array $data = null)
836
    {
837
        if ($data === null) {
838
            return $this->data;
839
        }
840
841
        $this->data = $data;
842
843
        return $this;
844
    }
845
846
    /**
847
     * Set default value for field.
848
     *
849
     * @param $default
850
     *
851
     * @return $this
852
     */
853
    public function default($default): self
854
    {
855
        $this->default = $default;
856
857
        return $this;
858
    }
859
860
    /**
861
     * Get default value.
862
     *
863
     * @return mixed
864
     */
865
    public function getDefault()
866
    {
867
        if ($this->default instanceof \Closure) {
868
            return call_user_func($this->default, $this->form);
869
        }
870
871
        return $this->default;
872
    }
873
874
    /**
875
     * Set help block for current field.
876
     *
877
     * @param string $text
878
     * @param string $icon
879
     *
880
     * @return $this
881
     */
882
    public function help($text = '', $icon = 'fa-info-circle'): self
883
    {
884
        $this->help = compact('text', 'icon');
885
886
        return $this;
887
    }
888
889
    /**
890
     * Get column of the field.
891
     *
892
     * @return string|array
893
     */
894
    public function column()
895
    {
896
        return $this->column;
897
    }
898
899
    /**
900
     * Get label of the field.
901
     *
902
     * @return string
903
     */
904
    public function label(): string
905
    {
906
        return $this->label;
907
    }
908
909
    /**
910
     * Get original value of the field.
911
     *
912
     * @return mixed
913
     */
914
    public function original()
915
    {
916
        return $this->original;
917
    }
918
919
    /**
920
     * Get validator for this field.
921
     *
922
     * @param array $input
923
     *
924
     * @return bool|\Illuminate\Contracts\Validation\Validator|mixed
925
     */
926
    public function getValidator(array $input)
927
    {
928
        if ($this->validator) {
929
            return $this->validator->call($this, $input);
930
        }
931
932
        $rules = $attributes = [];
933
934
        if (!$fieldRules = $this->getRules()) {
935
            return false;
936
        }
937
938
        if (is_string($this->column)) {
939
            if (!Arr::has($input, $this->column)) {
940
                return false;
941
            }
942
943
            $input = $this->sanitizeInput($input, $this->column);
944
945
            $rules[$this->column] = $fieldRules;
946
            $attributes[$this->column] = $this->label;
947
        }
948
949
        if (is_array($this->column)) {
950
            foreach ($this->column as $key => $column) {
951
                if (!array_key_exists($column, $input)) {
952
                    continue;
953
                }
954
                $input[$column.$key] = Arr::get($input, $column);
955
                $rules[$column.$key] = $fieldRules;
956
                $attributes[$column.$key] = $this->label."[$column]";
957
            }
958
        }
959
960
        return \validator($input, $rules, $this->getValidationMessages(), $attributes);
961
    }
962
963
    /**
964
     * Sanitize input data.
965
     *
966
     * @param array  $input
967
     * @param string $column
968
     *
969
     * @return array
970
     */
971
    protected function sanitizeInput($input, $column)
972
    {
973
        if ($this instanceof Field\MultipleSelect) {
974
            $value = Arr::get($input, $column);
975
            Arr::set($input, $column, array_filter($value));
976
        }
977
978
        return $input;
979
    }
980
981
    /**
982
     * Add html attributes to elements.
983
     *
984
     * @param array|string $attribute
985
     * @param mixed        $value
986
     *
987
     * @return $this
988
     */
989
    public function attribute($attribute, $value = null): self
990
    {
991
        if (is_array($attribute)) {
992
            $this->attributes = array_merge($this->attributes, $attribute);
993
        } else {
994
            $this->attributes[$attribute] = (string) $value;
995
        }
996
997
        return $this;
998
    }
999
1000
    /**
1001
     * Remove html attributes from elements.
1002
     *
1003
     * @param array|string $attribute
1004
     *
1005
     * @return $this
1006
     */
1007
    public function removeAttribute($attribute): self
1008
    {
1009
        Arr::forget($this->attributes, $attribute);
1010
1011
        return $this;
1012
    }
1013
1014
    /**
1015
     * Set Field style.
1016
     *
1017
     * @param string $attr
1018
     * @param string $value
1019
     *
1020
     * @return $this
1021
     */
1022
    public function style($attr, $value): self
1023
    {
1024
        return $this->attribute('style', "{$attr}: {$value}");
1025
    }
1026
1027
    /**
1028
     * Set Field width.
1029
     *
1030
     * @param string $width
1031
     *
1032
     * @return $this
1033
     */
1034
    public function width($width): self
1035
    {
1036
        return $this->style('width', $width);
1037
    }
1038
1039
    /**
1040
     * Specifies a regular expression against which to validate the value of the input.
1041
     *
1042
     * @param string $regexp
1043
     *
1044
     * @return $this
1045
     */
1046
    public function pattern($regexp): self
1047
    {
1048
        return $this->attribute('pattern', $regexp);
1049
    }
1050
1051
    /**
1052
     * set the input filed required.
1053
     *
1054
     * @param bool $isLabelAsterisked
1055
     *
1056
     * @return $this
1057
     */
1058
    public function required($isLabelAsterisked = true): self
1059
    {
1060
        if ($isLabelAsterisked) {
1061
            $this->setLabelClass(['asterisk']);
1062
        }
1063
1064
        return $this->attribute('required', true);
1065
    }
1066
1067
    /**
1068
     * Set the field automatically get focus.
1069
     *
1070
     * @return $this
1071
     */
1072
    public function autofocus(): self
1073
    {
1074
        return $this->attribute('autofocus', true);
1075
    }
1076
1077
    /**
1078
     * Set the field as readonly mode.
1079
     *
1080
     * @return $this
1081
     */
1082
    public function readonly()
1083
    {
1084
        return $this->attribute('readonly', true);
1085
    }
1086
1087
    /**
1088
     * Set field as disabled.
1089
     *
1090
     * @return $this
1091
     */
1092
    public function disable(): self
1093
    {
1094
        return $this->attribute('disabled', true);
1095
    }
1096
1097
    /**
1098
     * Set field placeholder.
1099
     *
1100
     * @param string $placeholder
1101
     *
1102
     * @return $this
1103
     */
1104
    public function placeholder($placeholder = ''): self
1105
    {
1106
        $this->placeholder = $placeholder;
1107
1108
        return $this;
1109
    }
1110
1111
    /**
1112
     * Get placeholder.
1113
     *
1114
     * @return mixed
1115
     */
1116
    public function getPlaceholder()
1117
    {
1118
        return $this->placeholder ?: trans('admin.input').' '.$this->label;
1119
    }
1120
1121
    /**
1122
     * Add a divider after this field.
1123
     *
1124
     * @return $this
1125
     */
1126
    public function divider()
1127
    {
1128
        $this->form->divider();
1129
1130
        return $this;
1131
    }
1132
1133
    /**
1134
     * Prepare for a field value before update or insert.
1135
     *
1136
     * @param $value
1137
     *
1138
     * @return mixed
1139
     */
1140
    public function prepare($value)
1141
    {
1142
        return $value;
1143
    }
1144
1145
    /**
1146
     * Format the field attributes.
1147
     *
1148
     * @return string
1149
     */
1150 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...
1151
    {
1152
        $html = [];
1153
1154
        foreach ($this->attributes as $name => $value) {
1155
            $html[] = $name.'="'.e($value).'"';
1156
        }
1157
1158
        return implode(' ', $html);
1159
    }
1160
1161
    /**
1162
     * @return $this
1163
     */
1164
    public function disableHorizontal(): self
1165
    {
1166
        $this->horizontal = false;
1167
1168
        return $this;
1169
    }
1170
1171
    /**
1172
     * @return array
1173
     */
1174
    public function getViewElementClasses(): array
1175
    {
1176
        if ($this->horizontal) {
1177
            return [
1178
                'label'      => "col-sm-{$this->width['label']} {$this->getLabelClass()}",
1179
                'field'      => "col-sm-{$this->width['field']}",
1180
                'form-group' => $this->getGroupClass(true),
1181
            ];
1182
        }
1183
1184
        return ['label' => $this->getLabelClass(), 'field' => '', 'form-group' => ''];
1185
    }
1186
1187
    /**
1188
     * Set form element class.
1189
     *
1190
     * @param string|array $class
1191
     *
1192
     * @return $this
1193
     */
1194
    public function setElementClass($class): self
1195
    {
1196
        $this->elementClass = array_merge($this->elementClass, (array) $class);
1197
1198
        return $this;
1199
    }
1200
1201
    /**
1202
     * Get element class.
1203
     *
1204
     * @return array
1205
     */
1206
    public function getElementClass(): array
1207
    {
1208
        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...
1209
            $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...
1210
1211
            $this->elementClass = (array) str_replace(['[', ']'], '_', $name);
1212
        }
1213
1214
        return $this->elementClass;
1215
    }
1216
1217
    /**
1218
     * Get element class string.
1219
     *
1220
     * @return mixed
1221
     */
1222 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...
1223
    {
1224
        $elementClass = $this->getElementClass();
1225
1226
        if (Arr::isAssoc($elementClass)) {
1227
            $classes = [];
1228
1229
            foreach ($elementClass as $index => $class) {
1230
                $classes[$index] = is_array($class) ? implode(' ', $class) : $class;
1231
            }
1232
1233
            return $classes;
1234
        }
1235
1236
        return implode(' ', $elementClass);
1237
    }
1238
1239
    /**
1240
     * Get element class selector.
1241
     *
1242
     * @return string|array
1243
     */
1244 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...
1245
    {
1246
        $elementClass = $this->getElementClass();
1247
1248
        if (Arr::isAssoc($elementClass)) {
1249
            $classes = [];
1250
1251
            foreach ($elementClass as $index => $class) {
1252
                $classes[$index] = '.'.(is_array($class) ? implode('.', $class) : $class);
1253
            }
1254
1255
            return $classes;
1256
        }
1257
1258
        return '.'.implode('.', $elementClass);
1259
    }
1260
1261
    /**
1262
     * Add the element class.
1263
     *
1264
     * @param $class
1265
     *
1266
     * @return $this
1267
     */
1268
    public function addElementClass($class): self
1269
    {
1270
        if (is_array($class) || is_string($class)) {
1271
            $this->elementClass = array_unique(array_merge($this->elementClass, (array) $class));
1272
        }
1273
1274
        return $this;
1275
    }
1276
1277
    /**
1278
     * Remove element class.
1279
     *
1280
     * @param $class
1281
     *
1282
     * @return $this
1283
     */
1284
    public function removeElementClass($class): self
1285
    {
1286
        $delClass = [];
1287
1288
        if (is_string($class) || is_array($class)) {
1289
            $delClass = (array) $class;
1290
        }
1291
1292
        foreach ($delClass as $del) {
1293
            if (($key = array_search($del, $this->elementClass, true)) !== false) {
1294
                unset($this->elementClass[$key]);
1295
            }
1296
        }
1297
1298
        return $this;
1299
    }
1300
1301
    /**
1302
     * Set form group class.
1303
     *
1304
     * @param string|array $class
1305
     *
1306
     * @return $this
1307
     */
1308
    public function setGroupClass($class): self
1309
    {
1310
        if (is_array($class)) {
1311
            $this->groupClass = array_merge($this->groupClass, $class);
1312
        } else {
1313
            $this->groupClass[] = $class;
1314
        }
1315
1316
        return $this;
1317
    }
1318
1319
    /**
1320
     * Get element class.
1321
     *
1322
     * @param bool $default
1323
     *
1324
     * @return string
1325
     */
1326
    protected function getGroupClass($default = false): string
1327
    {
1328
        return ($default ? 'form-group ' : '').implode(' ', array_filter($this->groupClass));
1329
    }
1330
1331
    /**
1332
     * reset field className.
1333
     *
1334
     * @param string $className
1335
     * @param string $resetClassName
1336
     *
1337
     * @return $this
1338
     */
1339
    public function resetElementClassName(string $className, string $resetClassName): self
1340
    {
1341
        if (($key = array_search($className, $this->getElementClass())) !== false) {
1342
            $this->elementClass[$key] = $resetClassName;
1343
        }
1344
1345
        return $this;
1346
    }
1347
1348
    /**
1349
     * Add variables to field view.
1350
     *
1351
     * @param array $variables
1352
     *
1353
     * @return $this
1354
     */
1355
    protected function addVariables(array $variables = []): self
1356
    {
1357
        $this->variables = array_merge($this->variables, $variables);
1358
1359
        return $this;
1360
    }
1361
1362
    /**
1363
     * @return string
1364
     */
1365
    public function getLabelClass(): string
1366
    {
1367
        return implode(' ', $this->labelClass);
1368
    }
1369
1370
    /**
1371
     * @param array $labelClass
1372
     *
1373
     * @return self
1374
     */
1375
    public function setLabelClass(array $labelClass): self
1376
    {
1377
        $this->labelClass = $labelClass;
1378
1379
        return $this;
1380
    }
1381
1382
    /**
1383
     * Get the view variables of this field.
1384
     *
1385
     * @return array
1386
     */
1387
    public function variables(): array
1388
    {
1389
        return array_merge($this->variables, [
1390
            'id'          => $this->id,
1391
            '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...
1392
            'help'        => $this->help,
1393
            'class'       => $this->getElementClassString(),
1394
            'value'       => $this->value(),
1395
            'label'       => $this->label,
1396
            'viewClass'   => $this->getViewElementClasses(),
1397
            'column'      => $this->column,
1398
            'errorKey'    => $this->getErrorKey(),
1399
            'attributes'  => $this->formatAttributes(),
1400
            'placeholder' => $this->getPlaceholder(),
1401
        ]);
1402
    }
1403
1404
    /**
1405
     * Get view of this field.
1406
     *
1407
     * @return string
1408
     */
1409
    public function getView(): string
1410
    {
1411
        if (!empty($this->view)) {
1412
            return $this->view;
1413
        }
1414
1415
        $class = explode('\\', static::class);
1416
1417
        return 'admin::form.'.strtolower(end($class));
1418
    }
1419
1420
    /**
1421
     * Set view of current field.
1422
     *
1423
     * @param string $view
1424
     *
1425
     * @return string
1426
     */
1427
    public function setView($view): self
1428
    {
1429
        $this->view = $view;
1430
1431
        return $this;
1432
    }
1433
1434
    /**
1435
     * Get script of current field.
1436
     *
1437
     * @return string
1438
     */
1439
    public function getScript(): string
1440
    {
1441
        return $this->script;
1442
    }
1443
1444
    /**
1445
     * Set script of current field.
1446
     *
1447
     * @param string $script
1448
     *
1449
     * @return $this
1450
     */
1451
    public function setScript($script): self
1452
    {
1453
        $this->script = $script;
1454
1455
        return $this;
1456
    }
1457
1458
    /**
1459
     * To set this field should render or not.
1460
     *
1461
     * @param bool $display
1462
     *
1463
     * @return $this
1464
     */
1465
    public function setDisplay(bool $display): self
1466
    {
1467
        $this->display = $display;
1468
1469
        return $this;
1470
    }
1471
1472
    /**
1473
     * If this field should render.
1474
     *
1475
     * @return bool
1476
     */
1477
    protected function shouldRender(): bool
1478
    {
1479
        if (!$this->display) {
1480
            return false;
1481
        }
1482
1483
        return true;
1484
    }
1485
1486
    /**
1487
     * @param \Closure $callback
1488
     *
1489
     * @return \Encore\Admin\Form\Field
1490
     */
1491
    public function with(Closure $callback): self
1492
    {
1493
        $this->callback = $callback;
1494
1495
        return $this;
1496
    }
1497
1498
    /**
1499
     * Render this filed.
1500
     *
1501
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|string
1502
     */
1503
    public function render()
1504
    {
1505
        if (!$this->shouldRender()) {
1506
            return '';
1507
        }
1508
1509
        $this->addRequiredAttributeFromRules();
1510
1511
        if ($this->callback instanceof Closure) {
1512
            $this->value = $this->callback->call($this->form->model(), $this->value, $this);
1513
        }
1514
1515
        Admin::script($this->script);
1516
1517
        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 1517 which is incompatible with the return type declared by the interface Illuminate\Contracts\Support\Renderable::render of type string.
Loading history...
1518
    }
1519
1520
    protected function fieldRender()
1521
    {
1522
        return self::render();
1523
    }
1524
1525
    /**
1526
     * @return string
1527
     */
1528
    public function __toString()
1529
    {
1530
        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...
1531
    }
1532
}
1533