Completed
Push — master ( 0996c2...608dcb )
by Song
02:32
created

Field::getPlaceholder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
534
535
        if (is_string($rules)) {
536
            $this->rules = array_filter(explode('|', $rules));
537
        }
538
539
        $this->setValidationMessages('default', $messages);
540
541
        return $this;
542
    }
543
544
    /**
545
     * Set the update validation rules for the field.
546
     *
547
     * @param array|callable|string $rules
548
     * @param array $messages
549
     *
550
     * @return $this
551
     */
552 View Code Duplication
    public function updateRules($rules = null, $messages = [])
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...
553
    {
554
        $this->updateRules = $rules;
0 ignored issues
show
Documentation Bug introduced by
It seems like $rules can also be of type callable. However, the property $updateRules is declared as type array|object<Closure>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
555
556
        if (is_string($rules)) {
557
            $this->updateRules = array_filter(explode('|', $rules));
558
        }
559
560
        $this->setValidationMessages('update', $messages);
561
562
        return $this;
563
    }
564
565
    /**
566
     * Set the creation validation rules for the field.
567
     *
568
     * @param array|callable|string $rules
569
     * @param array $messages
570
     * @return $this
571
     */
572 View Code Duplication
    public function creationRules($rules = null, $messages = [])
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...
573
    {
574
        $this->creationRules = $rules;
0 ignored issues
show
Documentation Bug introduced by
It seems like $rules can also be of type callable. However, the property $creationRules is declared as type array|object<Closure>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
575
576
        if (is_string($rules)) {
577
            $this->creationRules = array_filter(explode('|', $rules));
578
        }
579
580
        $this->setValidationMessages('creation', $messages);
581
582
        return $this;
583
    }
584
585
    /**
586
     * Set validation messages for column.
587
     *
588
     * @param string $key
589
     * @param array $messages
590
     *
591
     * @return $this
592
     */
593
    public function setValidationMessages($key, array $messages)
594
    {
595
        $this->validationMessages[$key] = $messages;
596
        
597
        return $this;
598
    }
599
600
    /**
601
     * Get validation messages for the field.
602
     *
603
     * @return array|mixed
604
     */
605
    public function getValidationMessages()
606
    {
607
        // Default validation message.
608
        $messages = $this->validationMessages['default'] ?? [];
609
610
        if (request()->isMethod('POST')) {
611
            $messages = $this->validationMessages['creation'] ?? $messages;
612
        } elseif (request()->isMethod('PUT')) {
613
            $messages = $this->validationMessages['update'] ?? $messages;
614
        }
615
616
        return $messages;
617
    }
618
619
    /**
620
     * Get field validation rules.
621
     *
622
     * @return string
623
     */
624
    protected function getRules()
625
    {
626
        if (request()->isMethod('POST')) {
627
            $rules = $this->creationRules ?: $this->rules;
628
        } elseif (request()->isMethod('PUT')) {
629
            $rules = $this->updateRules ?: $this->rules;
630
        } else {
631
            $rules = $this->rules;
632
        }
633
634
        if ($rules instanceof \Closure) {
635
            $rules = $rules->call($this, $this->form);
636
        }
637
638
        if (is_string($rules)) {
639
            $rules = array_filter(explode('|', $rules));
640
        }
641
642
        if (!$id = $this->form->model()->getKey()) {
643
            return $rules;
644
        }
645
646
        foreach ($rules as &$rule) {
0 ignored issues
show
Bug introduced by
The expression $rules of type object|integer|double|null|array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
647
            if (is_string($rule)) {
648
                $rule = str_replace('{{id}}', $id, $rule);
649
            }
650
        }
651
652
        return $rules;
653
    }
654
655
    /**
656
     * Remove a specific rule by keyword.
657
     *
658
     * @param string $rule
659
     *
660
     * @return void
661
     */
662
    protected function removeRule($rule)
663
    {
664
        if (!is_string($this->rules)) {
665
            return;
666
        }
667
668
        $pattern = "/{$rule}[^\|]?(\||$)/";
669
        $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...
670
    }
671
672
    /**
673
     * Set field validator.
674
     *
675
     * @param callable $validator
676
     *
677
     * @return $this
678
     */
679
    public function validator(callable $validator)
680
    {
681
        $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...
682
683
        return $this;
684
    }
685
686
    /**
687
     * Get key for error message.
688
     *
689
     * @return string
690
     */
691
    public function getErrorKey()
692
    {
693
        return $this->errorKey ?: $this->column;
694
    }
695
696
    /**
697
     * Set key for error message.
698
     *
699
     * @param string $key
700
     *
701
     * @return $this
702
     */
703
    public function setErrorKey($key)
704
    {
705
        $this->errorKey = $key;
706
707
        return $this;
708
    }
709
710
    /**
711
     * Set or get value of the field.
712
     *
713
     * @param null $value
714
     *
715
     * @return mixed
716
     */
717 View Code Duplication
    public function value($value = null)
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...
718
    {
719
        if (is_null($value)) {
720
            return is_null($this->value) ? $this->getDefault() : $this->value;
721
        }
722
723
        $this->value = $value;
724
725
        return $this;
726
    }
727
728
    /**
729
     * Set or get data.
730
     *
731
     * @param array $data
732
     *
733
     * @return $this
734
     */
735
    public function data(array $data = null)
736
    {
737
        if (is_null($data)) {
738
            return $this->data;
739
        }
740
741
        $this->data = $data;
742
743
        return $this;
744
    }
745
746
    /**
747
     * Set default value for field.
748
     *
749
     * @param $default
750
     *
751
     * @return $this
752
     */
753
    public function default($default)
754
    {
755
        $this->default = $default;
756
757
        return $this;
758
    }
759
760
    /**
761
     * Get default value.
762
     *
763
     * @return mixed
764
     */
765
    public function getDefault()
766
    {
767
        if ($this->default instanceof \Closure) {
768
            return call_user_func($this->default, $this->form);
769
        }
770
771
        return $this->default;
772
    }
773
774
    /**
775
     * Set help block for current field.
776
     *
777
     * @param string $text
778
     * @param string $icon
779
     *
780
     * @return $this
781
     */
782
    public function help($text = '', $icon = 'fa-info-circle')
783
    {
784
        $this->help = compact('text', 'icon');
785
786
        return $this;
787
    }
788
789
    /**
790
     * Get column of the field.
791
     *
792
     * @return string|array
793
     */
794
    public function column()
795
    {
796
        return $this->column;
797
    }
798
799
    /**
800
     * Get label of the field.
801
     *
802
     * @return string
803
     */
804
    public function label()
805
    {
806
        return $this->label;
807
    }
808
809
    /**
810
     * Get original value of the field.
811
     *
812
     * @return mixed
813
     */
814
    public function original()
815
    {
816
        return $this->original;
817
    }
818
819
    /**
820
     * Get validator for this field.
821
     *
822
     * @param array $input
823
     *
824
     * @return bool|\Illuminate\Contracts\Validation\Validator|mixed
825
     */
826
    public function getValidator(array $input)
827
    {
828
        if ($this->validator) {
829
            return $this->validator->call($this, $input);
830
        }
831
832
        $rules = $attributes = [];
833
834
        if (!$fieldRules = $this->getRules()) {
835
            return false;
836
        }
837
838
        if (is_string($this->column)) {
839
            if (!Arr::has($input, $this->column)) {
840
                return false;
841
            }
842
843
            $input = $this->sanitizeInput($input, $this->column);
844
845
            $rules[$this->column] = $fieldRules;
846
            $attributes[$this->column] = $this->label;
847
        }
848
849
        if (is_array($this->column)) {
850
            foreach ($this->column as $key => $column) {
851
                if (!array_key_exists($column, $input)) {
852
                    continue;
853
                }
854
                $input[$column.$key] = Arr::get($input, $column);
855
                $rules[$column.$key] = $fieldRules;
856
                $attributes[$column.$key] = $this->label."[$column]";
857
            }
858
        }
859
860
        return \validator($input, $rules, $this->getValidationMessages(), $attributes);
861
    }
862
863
    /**
864
     * Sanitize input data.
865
     *
866
     * @param array  $input
867
     * @param string $column
868
     *
869
     * @return array
870
     */
871
    protected function sanitizeInput($input, $column)
872
    {
873
        if ($this instanceof Field\MultipleSelect) {
874
            $value = Arr::get($input, $column);
875
            Arr::set($input, $column, array_filter($value));
876
        }
877
878
        return $input;
879
    }
880
881
    /**
882
     * Add html attributes to elements.
883
     *
884
     * @param array|string $attribute
885
     * @param mixed        $value
886
     *
887
     * @return $this
888
     */
889
    public function attribute($attribute, $value = null)
890
    {
891
        if (is_array($attribute)) {
892
            $this->attributes = array_merge($this->attributes, $attribute);
893
        } else {
894
            $this->attributes[$attribute] = (string) $value;
895
        }
896
897
        return $this;
898
    }
899
900
    /**
901
     * Specifies a regular expression against which to validate the value of the input.
902
     *
903
     * @param string $regexp
904
     *
905
     * @return Field
906
     */
907
    public function pattern($regexp)
908
    {
909
        return $this->attribute('pattern', $regexp);
910
    }
911
912
    /**
913
     * set the input filed required.
914
     *
915
     * @param bool $isLabelAsterisked
916
     *
917
     * @return Field
918
     */
919
    public function required($isLabelAsterisked = true)
920
    {
921
        if ($isLabelAsterisked) {
922
            $this->setLabelClass(['asterisk']);
923
        }
924
925
        return $this->attribute('required', true);
926
    }
927
928
    /**
929
     * Set the field automatically get focus.
930
     *
931
     * @return Field
932
     */
933
    public function autofocus()
934
    {
935
        return $this->attribute('autofocus', true);
936
    }
937
938
    /**
939
     * Set the field as readonly mode.
940
     *
941
     * @return Field
942
     */
943
    public function readOnly()
944
    {
945
        return $this->attribute('readonly', true);
946
    }
947
948
    /**
949
     * Set field as disabled.
950
     *
951
     * @return Field
952
     */
953
    public function disable()
954
    {
955
        return $this->attribute('disabled', true);
956
    }
957
958
    /**
959
     * Set field placeholder.
960
     *
961
     * @param string $placeholder
962
     *
963
     * @return Field
964
     */
965
    public function placeholder($placeholder = '')
966
    {
967
        $this->placeholder = $placeholder;
968
969
        return $this;
970
    }
971
972
    /**
973
     * Get placeholder.
974
     *
975
     * @return string
976
     */
977
    public function getPlaceholder()
978
    {
979
        return $this->placeholder ?: trans('admin.input').' '.$this->label;
980
    }
981
982
    /**
983
     * Prepare for a field value before update or insert.
984
     *
985
     * @param $value
986
     *
987
     * @return mixed
988
     */
989
    public function prepare($value)
990
    {
991
        return $value;
992
    }
993
994
    /**
995
     * Format the field attributes.
996
     *
997
     * @return string
998
     */
999
    protected function formatAttributes()
1000
    {
1001
        $html = [];
1002
1003
        foreach ($this->attributes as $name => $value) {
1004
            $html[] = $name.'="'.e($value).'"';
1005
        }
1006
1007
        return implode(' ', $html);
1008
    }
1009
1010
    /**
1011
     * @return $this
1012
     */
1013
    public function disableHorizontal()
1014
    {
1015
        $this->horizontal = false;
1016
1017
        return $this;
1018
    }
1019
1020
    /**
1021
     * @return array
1022
     */
1023
    public function getViewElementClasses()
1024
    {
1025
        if ($this->horizontal) {
1026
            return [
1027
                'label'      => "col-sm-{$this->width['label']} {$this->getLabelClass()}",
1028
                'field'      => "col-sm-{$this->width['field']}",
1029
                'form-group' => 'form-group ',
1030
            ];
1031
        }
1032
1033
        return ['label' => "{$this->getLabelClass()}", 'field' => '', 'form-group' => ''];
1034
    }
1035
1036
    /**
1037
     * Set form element class.
1038
     *
1039
     * @param string|array $class
1040
     *
1041
     * @return $this
1042
     */
1043
    public function setElementClass($class)
1044
    {
1045
        $this->elementClass = array_merge($this->elementClass, (array) $class);
1046
1047
        return $this;
1048
    }
1049
1050
    /**
1051
     * Get element class.
1052
     *
1053
     * @return array
1054
     */
1055
    protected function getElementClass()
1056
    {
1057
        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...
1058
            $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...
1059
1060
            $this->elementClass = (array) str_replace(['[', ']'], '_', $name);
1061
        }
1062
1063
        return $this->elementClass;
1064
    }
1065
1066
    /**
1067
     * Get element class string.
1068
     *
1069
     * @return mixed
1070
     */
1071 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...
1072
    {
1073
        $elementClass = $this->getElementClass();
1074
1075
        if (Arr::isAssoc($elementClass)) {
1076
            $classes = [];
1077
1078
            foreach ($elementClass as $index => $class) {
1079
                $classes[$index] = is_array($class) ? implode(' ', $class) : $class;
1080
            }
1081
1082
            return $classes;
1083
        }
1084
1085
        return implode(' ', $elementClass);
1086
    }
1087
1088
    /**
1089
     * Get element class selector.
1090
     *
1091
     * @return string|array
1092
     */
1093 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...
1094
    {
1095
        $elementClass = $this->getElementClass();
1096
1097
        if (Arr::isAssoc($elementClass)) {
1098
            $classes = [];
1099
1100
            foreach ($elementClass as $index => $class) {
1101
                $classes[$index] = '.'.(is_array($class) ? implode('.', $class) : $class);
1102
            }
1103
1104
            return $classes;
1105
        }
1106
1107
        return '.'.implode('.', $elementClass);
1108
    }
1109
1110
    /**
1111
     * Add the element class.
1112
     *
1113
     * @param $class
1114
     *
1115
     * @return $this
1116
     */
1117
    public function addElementClass($class)
1118
    {
1119
        if (is_array($class) || is_string($class)) {
1120
            $this->elementClass = array_merge($this->elementClass, (array) $class);
1121
1122
            $this->elementClass = array_unique($this->elementClass);
1123
        }
1124
1125
        return $this;
1126
    }
1127
1128
    /**
1129
     * Remove element class.
1130
     *
1131
     * @param $class
1132
     *
1133
     * @return $this
1134
     */
1135
    public function removeElementClass($class)
1136
    {
1137
        $delClass = [];
1138
1139
        if (is_string($class) || is_array($class)) {
1140
            $delClass = (array) $class;
1141
        }
1142
1143
        foreach ($delClass as $del) {
1144
            if (($key = array_search($del, $this->elementClass)) !== false) {
1145
                unset($this->elementClass[$key]);
1146
            }
1147
        }
1148
1149
        return $this;
1150
    }
1151
1152
    /**
1153
     * Set form group class.
1154
     *
1155
     * @param string|array $class
1156
     *
1157
     * @return $this
1158
     */
1159
    public function setGroupClass($class)
1160
    : self
1161
    {
1162
        array_push($this->groupClass, $class);
1163
1164
        return $this;
1165
    }
1166
1167
    /**
1168
     * Get element class.
1169
     *
1170
     * @param bool $default
1171
     *
1172
     * @return string
1173
     */
1174
    protected function getGroupClass($default = false)
1175
    : string
1176
    {
1177
        return ($default ? 'form-group ' : '').implode(' ', array_filter($this->groupClass));
1178
    }
1179
1180
    /**
1181
     * reset field className.
1182
     *
1183
     * @param string $className
1184
     * @param string $resetClassName
1185
     *
1186
     * @return $this
1187
     */
1188
    public function resetElementClassName(string $className, string $resetClassName)
1189
    {
1190
        if (($key = array_search($className, $this->getElementClass())) !== false) {
1191
            $this->elementClass[$key] = $resetClassName;
1192
        }
1193
1194
        return $this;
1195
    }
1196
1197
    /**
1198
     * Add variables to field view.
1199
     *
1200
     * @param array $variables
1201
     *
1202
     * @return $this
1203
     */
1204
    protected function addVariables(array $variables = [])
1205
    {
1206
        $this->variables = array_merge($this->variables, $variables);
1207
1208
        return $this;
1209
    }
1210
1211
    /**
1212
     * @return string
1213
     */
1214
    public function getLabelClass()
1215
    : string
1216
    {
1217
        return implode(' ', $this->labelClass);
1218
    }
1219
1220
    /**
1221
     * @param array $labelClass
1222
     *
1223
     * @return self
1224
     */
1225
    public function setLabelClass(array $labelClass)
1226
    : self
1227
    {
1228
        $this->labelClass = $labelClass;
1229
1230
        return $this;
1231
    }
1232
1233
    /**
1234
     * Get the view variables of this field.
1235
     *
1236
     * @return array
1237
     */
1238
    public function variables()
1239
    {
1240
        return array_merge($this->variables, [
1241
            'id'          => $this->id,
1242
            '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...
1243
            'help'        => $this->help,
1244
            'class'       => $this->getElementClassString(),
1245
            'value'       => $this->value(),
1246
            'label'       => $this->label,
1247
            'viewClass'   => $this->getViewElementClasses(),
1248
            'column'      => $this->column,
1249
            'errorKey'    => $this->getErrorKey(),
1250
            'attributes'  => $this->formatAttributes(),
1251
            'placeholder' => $this->getPlaceholder(),
1252
        ]);
1253
    }
1254
1255
    /**
1256
     * Get view of this field.
1257
     *
1258
     * @return string
1259
     */
1260
    public function getView()
1261
    {
1262
        if (!empty($this->view)) {
1263
            return $this->view;
1264
        }
1265
1266
        $class = explode('\\', get_called_class());
1267
1268
        return 'admin::form.'.strtolower(end($class));
1269
    }
1270
1271
    /**
1272
     * Set view of current field.
1273
     *
1274
     * @param string $view
1275
     *
1276
     * @return string
1277
     */
1278
    public function setView($view)
1279
    {
1280
        $this->view = $view;
1281
1282
        return $this;
1283
    }
1284
1285
    /**
1286
     * Get script of current field.
1287
     *
1288
     * @return string
1289
     */
1290
    public function getScript()
1291
    {
1292
        return $this->script;
1293
    }
1294
1295
    /**
1296
     * Set script of current field.
1297
     *
1298
     * @param string $script
1299
     *
1300
     * @return $this
1301
     */
1302
    public function setScript($script)
1303
    {
1304
        $this->script = $script;
1305
1306
        return $this;
1307
    }
1308
1309
    /**
1310
     * To set this field should render or not.
1311
     *
1312
     * @param bool $display
1313
     *
1314
     * @return $this
1315
     */
1316
    public function setDisplay(bool $display)
1317
    {
1318
        $this->display = $display;
1319
1320
        return $this;
1321
    }
1322
1323
    /**
1324
     * If this field should render.
1325
     *
1326
     * @return bool
1327
     */
1328
    protected function shouldRender()
1329
    {
1330
        if (!$this->display) {
1331
            return false;
1332
        }
1333
1334
        return true;
1335
    }
1336
1337
    /**
1338
     * @param \Closure $callback
1339
     *
1340
     * @return \Encore\Admin\Form\Field
1341
     */
1342
    public function with(Closure $callback)
1343
    {
1344
        $this->callback = $callback;
1345
1346
        return $this;
1347
    }
1348
1349
    /**
1350
     * Render this filed.
1351
     *
1352
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|string
1353
     */
1354
    public function render()
1355
    {
1356
        if (!$this->shouldRender()) {
1357
            return '';
1358
        }
1359
1360
        if ($this->callback instanceof Closure) {
1361
            $this->value = $this->callback->call($this->form->model(), $this->value, $this);
1362
        }
1363
1364
        $this->addRequiredAttributeFromRules();
1365
1366
        Admin::script($this->script);
1367
1368
        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 1368 which is incompatible with the return type declared by the interface Illuminate\Contracts\Support\Renderable::render of type string.
Loading history...
1369
    }
1370
1371
    /**
1372
     * @return string
1373
     */
1374
    public function __toString()
1375
    {
1376
        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...
1377
    }
1378
}
1379