Completed
Push — master ( 608dcb...7d9d84 )
by Song
04:10
created

Field::mergeRules()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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