Completed
Push — master ( c633e4...51e624 )
by Song
02:23
created

Form::callSaving()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin;
4
5
use Closure;
6
use Encore\Admin\Exception\Handler;
7
use Encore\Admin\Form\Builder;
8
use Encore\Admin\Form\Field;
9
use Encore\Admin\Form\HasHooks;
10
use Encore\Admin\Form\Row;
11
use Encore\Admin\Form\Tab;
12
use Illuminate\Contracts\Support\Renderable;
13
use Illuminate\Database\Eloquent\Model;
14
use Illuminate\Database\Eloquent\Relations;
15
use Illuminate\Database\Eloquent\SoftDeletes;
16
use Illuminate\Http\Request;
17
use Illuminate\Support\Arr;
18
use Illuminate\Support\Facades\DB;
19
use Illuminate\Support\MessageBag;
20
use Illuminate\Support\Str;
21
use Illuminate\Validation\Validator;
22
use Spatie\EloquentSortable\Sortable;
23
use Symfony\Component\HttpFoundation\Response;
24
25
/**
26
 * Class Form.
27
 *
28
 * @method Field\Text           text($column, $label = '')
29
 * @method Field\Checkbox       checkbox($column, $label = '')
30
 * @method Field\Radio          radio($column, $label = '')
31
 * @method Field\Select         select($column, $label = '')
32
 * @method Field\MultipleSelect multipleSelect($column, $label = '')
33
 * @method Field\Textarea       textarea($column, $label = '')
34
 * @method Field\Hidden         hidden($column, $label = '')
35
 * @method Field\Id             id($column, $label = '')
36
 * @method Field\Ip             ip($column, $label = '')
37
 * @method Field\Url            url($column, $label = '')
38
 * @method Field\Color          color($column, $label = '')
39
 * @method Field\Email          email($column, $label = '')
40
 * @method Field\Mobile         mobile($column, $label = '')
41
 * @method Field\Slider         slider($column, $label = '')
42
 * @method Field\Map            map($latitude, $longitude, $label = '')
43
 * @method Field\Editor         editor($column, $label = '')
44
 * @method Field\File           file($column, $label = '')
45
 * @method Field\Image          image($column, $label = '')
46
 * @method Field\Date           date($column, $label = '')
47
 * @method Field\Datetime       datetime($column, $label = '')
48
 * @method Field\Time           time($column, $label = '')
49
 * @method Field\Year           year($column, $label = '')
50
 * @method Field\Month          month($column, $label = '')
51
 * @method Field\DateRange      dateRange($start, $end, $label = '')
52
 * @method Field\DateTimeRange  datetimeRange($start, $end, $label = '')
53
 * @method Field\TimeRange      timeRange($start, $end, $label = '')
54
 * @method Field\Number         number($column, $label = '')
55
 * @method Field\Currency       currency($column, $label = '')
56
 * @method Field\HasMany        hasMany($relationName, $callback)
57
 * @method Field\SwitchField    switch($column, $label = '')
58
 * @method Field\Display        display($column, $label = '')
59
 * @method Field\Rate           rate($column, $label = '')
60
 * @method Field\Divide         divider()
61
 * @method Field\Password       password($column, $label = '')
62
 * @method Field\Decimal        decimal($column, $label = '')
63
 * @method Field\Html           html($html, $label = '')
64
 * @method Field\Tags           tags($column, $label = '')
65
 * @method Field\Icon           icon($column, $label = '')
66
 * @method Field\Embeds         embeds($column, $label = '')
67
 * @method Field\MultipleImage  multipleImage($column, $label = '')
68
 * @method Field\MultipleFile   multipleFile($column, $label = '')
69
 * @method Field\Captcha        captcha($column, $label = '')
70
 * @method Field\Listbox        listbox($column, $label = '')
71
 * @method Field\Table          table($column, $label, $builder)
72
 */
73
class Form implements Renderable
74
{
75
    use HasHooks;
76
77
    /**
78
     * Remove flag in `has many` form.
79
     */
80
    const REMOVE_FLAG_NAME = '_remove_';
81
82
    /**
83
     * Eloquent model of the form.
84
     *
85
     * @var Model
86
     */
87
    protected $model;
88
89
    /**
90
     * @var \Illuminate\Validation\Validator
91
     */
92
    protected $validator;
93
94
    /**
95
     * @var Builder
96
     */
97
    protected $builder;
98
99
    /**
100
     * Data for save to current model from input.
101
     *
102
     * @var array
103
     */
104
    protected $updates = [];
105
106
    /**
107
     * Data for save to model's relations from input.
108
     *
109
     * @var array
110
     */
111
    protected $relations = [];
112
113
    /**
114
     * Input data.
115
     *
116
     * @var array
117
     */
118
    protected $inputs = [];
119
120
    /**
121
     * Available fields.
122
     *
123
     * @var array
124
     */
125
    public static $availableFields = [];
126
127
    /**
128
     * Form field alias.
129
     *
130
     * @var array
131
     */
132
    public static $fieldAlias = [];
133
134
    /**
135
     * Ignored saving fields.
136
     *
137
     * @var array
138
     */
139
    protected $ignored = [];
140
141
    /**
142
     * Collected field assets.
143
     *
144
     * @var array
145
     */
146
    protected static $collectedAssets = [];
147
148
    /**
149
     * @var Form\Tab
150
     */
151
    protected $tab = null;
152
153
    /**
154
     * Field rows in form.
155
     *
156
     * @var array
157
     */
158
    public $rows = [];
159
160
    /**
161
     * @var bool
162
     */
163
    protected $isSoftDeletes = false;
164
165
    /**
166
     * Initialization closure array.
167
     *
168
     * @var []Closure
169
     */
170
    protected static $initCallbacks;
171
172
    /**
173
     * Create a new form instance.
174
     *
175
     * @param $model
176
     * @param \Closure $callback
177
     */
178
    public function __construct($model, Closure $callback = null)
179
    {
180
        $this->model = $model;
181
182
        $this->builder = new Builder($this);
183
184
        if ($callback instanceof Closure) {
185
            $callback($this);
186
        }
187
188
        $this->isSoftDeletes = in_array(SoftDeletes::class, class_uses_deep($this->model));
189
190
        $this->callInitCallbacks();
191
    }
192
193
    /**
194
     * Initialize with user pre-defined default disables, etc.
195
     *
196
     * @param Closure $callback
197
     */
198
    public static function init(Closure $callback = null)
199
    {
200
        static::$initCallbacks[] = $callback;
201
    }
202
203
    /**
204
     * Call the initialization closure array in sequence.
205
     */
206
    protected function callInitCallbacks()
207
    {
208
        if (empty(static::$initCallbacks)) {
209
            return;
210
        }
211
212
        foreach (static::$initCallbacks as $callback) {
213
            call_user_func($callback, $this);
214
        }
215
    }
216
217
    /**
218
     * @param Field $field
219
     *
220
     * @return $this
221
     */
222
    public function pushField(Field $field)
223
    {
224
        $field->setForm($this);
225
226
        $this->builder->fields()->push($field);
227
228
        return $this;
229
    }
230
231
    /**
232
     * @return Model
233
     */
234
    public function model()
235
    {
236
        return $this->model;
237
    }
238
239
    /**
240
     * @return Builder
241
     */
242
    public function builder()
243
    {
244
        return $this->builder;
245
    }
246
247
    /**
248
     * Generate a edit form.
249
     *
250
     * @param $id
251
     *
252
     * @return $this
253
     */
254
    public function edit($id)
255
    {
256
        $this->builder->setMode(Builder::MODE_EDIT);
257
        $this->builder->setResourceId($id);
258
259
        $this->setFieldValue($id);
260
261
        return $this;
262
    }
263
264
    /**
265
     * Use tab to split form.
266
     *
267
     * @param string  $title
268
     * @param Closure $content
269
     *
270
     * @return $this
271
     */
272
    public function tab($title, Closure $content, $active = false)
273
    {
274
        $this->getTab()->append($title, $content, $active);
275
276
        return $this;
277
    }
278
279
    /**
280
     * Get Tab instance.
281
     *
282
     * @return Tab
283
     */
284
    public function getTab()
285
    {
286
        if (is_null($this->tab)) {
287
            $this->tab = new Tab($this);
288
        }
289
290
        return $this->tab;
291
    }
292
293
    /**
294
     * Destroy data entity and remove files.
295
     *
296
     * @param $id
297
     *
298
     * @return mixed
299
     */
300
    public function destroy($id)
301
    {
302
        try {
303
304
            if (($ret = $this->callDeleting()) instanceof Response) {
305
                return $ret;
306
            }
307
308
            collect(explode(',', $id))->filter()->each(function ($id) {
309
                $builder = $this->model()->newQuery();
310
311
                if ($this->isSoftDeletes) {
312
                    $builder = $builder->withTrashed();
313
                }
314
315
                $model = $builder->with($this->getRelations())->findOrFail($id);
316
317
                if ($this->isSoftDeletes && $model->trashed()) {
318
                    $this->deleteFiles($model, true);
319
                    $model->forceDelete();
320
321
                    return;
322
                }
323
324
                $this->deleteFiles($model);
325
                $model->delete();
326
            });
327
328
            if (($ret = $this->callDeleted()) instanceof Response) {
329
                return $ret;
330
            }
331
332
            $response = [
333
                'status'  => true,
334
                'message' => trans('admin.delete_succeeded'),
335
            ];
336
337
        } catch (\Exception $exception) {
338
            $response = [
339
                'status'  => false,
340
                'message' => $exception->getMessage() ?: trans('admin.delete_failed'),
341
            ];
342
        }
343
344
        return response()->json($response);
0 ignored issues
show
Bug introduced by
The method json does only exist in Illuminate\Contracts\Routing\ResponseFactory, but not in Illuminate\Http\Response.

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...
345
    }
346
347
    /**
348
     * Remove files in record.
349
     *
350
     * @param Model $model
351
     * @param bool  $forceDelete
352
     */
353
    protected function deleteFiles(Model $model, $forceDelete = false)
354
    {
355
        // If it's a soft delete, the files in the data will not be deleted.
356
        if (!$forceDelete && $this->isSoftDeletes) {
357
            return;
358
        }
359
360
        $data = $model->toArray();
361
362
        $this->builder->fields()->filter(function ($field) {
363
            return $field instanceof Field\File;
364
        })->each(function (Field\File $file) use ($data) {
365
            $file->setOriginal($data);
366
367
            $file->destroy();
368
        });
369
    }
370
371
    /**
372
     * Store a new record.
373
     *
374
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\Http\JsonResponse
375
     */
376
    public function store()
377
    {
378
        $data = \request()->all();
379
380
        // Handle validation errors.
381
        if ($validationMessages = $this->validationMessages($data)) {
382
            return back()->withInput()->withErrors($validationMessages);
383
        }
384
385
        if (($response = $this->prepare($data)) instanceof Response) {
386
            return $response;
387
        }
388
389 View Code Duplication
        DB::transaction(function () {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
390
            $inserts = $this->prepareInsert($this->updates);
391
392
            foreach ($inserts as $column => $value) {
393
                $this->model->setAttribute($column, $value);
394
            }
395
396
            $this->model->save();
397
398
            $this->updateRelation($this->relations);
399
        });
400
401
        if (($response = $this->callSaved()) instanceof Response) {
402
            return $response;
403
        }
404
405
        if ($response = $this->ajaxResponse(trans('admin.save_succeeded'))) {
406
            return $response;
407
        }
408
409
        return $this->redirectAfterStore();
410
    }
411
412
    /**
413
     * Get ajax response.
414
     *
415
     * @param string $message
416
     *
417
     * @return bool|\Illuminate\Http\JsonResponse
418
     */
419
    protected function ajaxResponse($message)
420
    {
421
        $request = Request::capture();
422
423
        // ajax but not pjax
424
        if ($request->ajax() && !$request->pjax()) {
425
            return response()->json([
0 ignored issues
show
Bug introduced by
The method json does only exist in Illuminate\Contracts\Routing\ResponseFactory, but not in Illuminate\Http\Response.

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...
426
                'status'  => true,
427
                'message' => $message,
428
            ]);
429
        }
430
431
        return false;
432
    }
433
434
    /**
435
     * Prepare input data for insert or update.
436
     *
437
     * @param array $data
438
     *
439
     * @return mixed
440
     */
441
    protected function prepare($data = [])
442
    {
443
        if (($response = $this->callSubmitted()) instanceof Response) {
444
            return $response;
445
        }
446
447
        $this->inputs = array_merge($this->removeIgnoredFields($data), $this->inputs);
448
449
        if (($response = $this->callSaving()) instanceof Response) {
450
            return $response;
451
        }
452
453
        $this->relations = $this->getRelationInputs($this->inputs);
454
455
        $this->updates = Arr::except($this->inputs, array_keys($this->relations));
456
    }
457
458
    /**
459
     * Remove ignored fields from input.
460
     *
461
     * @param array $input
462
     *
463
     * @return array
464
     */
465
    protected function removeIgnoredFields($input)
466
    {
467
        Arr::forget($input, $this->ignored);
468
469
        return $input;
470
    }
471
472
    /**
473
     * Get inputs for relations.
474
     *
475
     * @param array $inputs
476
     *
477
     * @return array
478
     */
479
    protected function getRelationInputs($inputs = [])
480
    {
481
        $relations = [];
482
483
        foreach ($inputs as $column => $value) {
484
            if (method_exists($this->model, $column)) {
485
                $relation = call_user_func([$this->model, $column]);
486
487
                if ($relation instanceof Relations\Relation) {
488
                    $relations[$column] = $value;
489
                }
490
            }
491
        }
492
493
        return $relations;
494
    }
495
496
    /**
497
     * Handle update.
498
     *
499
     * @param int  $id
500
     * @param null $data
501
     *
502
     * @return bool|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|mixed|null|Response
503
     */
504
    public function update($id, $data = null)
505
    {
506
        $data = ($data) ?: request()->all();
507
508
        $isEditable = $this->isEditable($data);
509
510
        if (($data = $this->handleColumnUpdates($id, $data)) instanceof Response) {
511
            return $data;
512
        }
513
514
        /* @var Model $this->model */
515
        $builder = $this->model();
516
517
        if ($this->isSoftDeletes) {
518
            $builder = $builder->withTrashed();
519
        }
520
521
        $this->model = $builder->with($this->getRelations())->findOrFail($id);
522
523
        $this->setFieldOriginalValue();
524
525
        // Handle validation errors.
526
        if ($validationMessages = $this->validationMessages($data)) {
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->handleColumnUpdates($id, $data) on line 510 can also be of type object<Illuminate\Contra...outing\ResponseFactory>; however, Encore\Admin\Form::validationMessages() 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...
527
            if (!$isEditable) {
528
                return back()->withInput()->withErrors($validationMessages);
529
            }
530
531
            return response()->json(['errors' => Arr::dot($validationMessages->getMessages())], 422);
0 ignored issues
show
Bug introduced by
The method json does only exist in Illuminate\Contracts\Routing\ResponseFactory, but not in Illuminate\Http\Response.

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...
532
        }
533
534
        if (($response = $this->prepare($data)) instanceof Response) {
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->handleColumnUpdates($id, $data) on line 510 can also be of type object<Illuminate\Contra...outing\ResponseFactory>; however, Encore\Admin\Form::prepare() 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...
535
            return $response;
536
        }
537
538 View Code Duplication
        DB::transaction(function () {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
539
            $updates = $this->prepareUpdate($this->updates);
540
541
            foreach ($updates as $column => $value) {
542
                /* @var Model $this->model */
543
                $this->model->setAttribute($column, $value);
544
            }
545
546
            $this->model->save();
547
548
            $this->updateRelation($this->relations);
549
        });
550
551
        if (($result = $this->callSaved()) instanceof Response) {
552
            return $result;
553
        }
554
555
        if ($response = $this->ajaxResponse(trans('admin.update_succeeded'))) {
556
            return $response;
557
        }
558
559
        return $this->redirectAfterUpdate($id);
560
    }
561
562
    /**
563
     * Get RedirectResponse after store.
564
     *
565
     * @return \Illuminate\Http\RedirectResponse
566
     */
567
    protected function redirectAfterStore()
568
    {
569
        $resourcesPath = $this->resource(0);
570
571
        $key = $this->model->getKey();
572
573
        return $this->redirectAfterSaving($resourcesPath, $key);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->redirectAfterSaving($resourcesPath, $key); of type Illuminate\Http\Redirect...nate\Routing\Redirector adds the type Illuminate\Routing\Redirector to the return on line 573 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterStore of type Illuminate\Http\RedirectResponse.
Loading history...
574
    }
575
576
    /**
577
     * Get RedirectResponse after update.
578
     *
579
     * @param mixed $key
580
     *
581
     * @return \Illuminate\Http\RedirectResponse
582
     */
583
    protected function redirectAfterUpdate($key)
584
    {
585
        $resourcesPath = $this->resource(-1);
586
587
        return $this->redirectAfterSaving($resourcesPath, $key);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->redirectAfterSaving($resourcesPath, $key); of type Illuminate\Http\Redirect...nate\Routing\Redirector adds the type Illuminate\Routing\Redirector to the return on line 587 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterUpdate of type Illuminate\Http\RedirectResponse.
Loading history...
588
    }
589
590
    /**
591
     * Get RedirectResponse after data saving.
592
     *
593
     * @param string $resourcesPath
594
     * @param string $key
595
     *
596
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
597
     */
598
    protected function redirectAfterSaving($resourcesPath, $key)
599
    {
600
        if (request('after-save') == 1) {
601
            // continue editing
602
            $url = rtrim($resourcesPath, '/')."/{$key}/edit";
603
        } elseif (request('after-save') == 2) {
604
            // continue creating
605
            $url = rtrim($resourcesPath, '/').'/create';
606
        } elseif (request('after-save') == 3) {
607
            // view resource
608
            $url = rtrim($resourcesPath, '/')."/{$key}";
609
        } else {
610
            $url = request(Builder::PREVIOUS_URL_KEY) ?: $resourcesPath;
611
        }
612
613
        admin_toastr(trans('admin.save_succeeded'));
614
615
        return redirect($url);
616
    }
617
618
    /**
619
     * Check if request is from editable.
620
     *
621
     * @param array $input
622
     *
623
     * @return bool
624
     */
625
    protected function isEditable(array $input = [])
626
    {
627
        return array_key_exists('_editable', $input);
628
    }
629
630
    /**
631
     * Handle updates for single column.
632
     *
633
     * @param int   $id
634
     * @param array $data
635
     *
636
     * @return array|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|Response
637
     */
638
    protected function handleColumnUpdates($id, $data)
639
    {
640
        $data = $this->handleEditable($data);
641
642
        $data = $this->handleFileDelete($data);
643
644
        $data = $this->handleFileSort($data);
645
646
        if ($this->handleOrderable($id, $data)) {
647
            return response([
648
                'status'  => true,
649
                'message' => trans('admin.update_succeeded'),
650
            ]);
651
        }
652
653
        return $data;
654
    }
655
656
    /**
657
     * Handle editable update.
658
     *
659
     * @param array $input
660
     *
661
     * @return array
662
     */
663
    protected function handleEditable(array $input = [])
664
    {
665
        if (array_key_exists('_editable', $input)) {
666
            $name = $input['name'];
667
            $value = $input['value'];
668
669
            Arr::forget($input, ['pk', 'value', 'name']);
670
            Arr::set($input, $name, $value);
671
        }
672
673
        return $input;
674
    }
675
676
    /**
677
     * @param array $input
678
     *
679
     * @return array
680
     */
681
    protected function handleFileDelete(array $input = [])
682
    {
683
        if (array_key_exists(Field::FILE_DELETE_FLAG, $input)) {
684
            $input[Field::FILE_DELETE_FLAG] = $input['key'];
685
            unset($input['key']);
686
        }
687
688
        request()->replace($input);
689
690
        return $input;
691
    }
692
693
    /**
694
     * @param array $input
695
     *
696
     * @return array
697
     */
698
    protected function handleFileSort(array $input = [])
699
    {
700
        if (!array_key_exists(Field::FILE_SORT_FLAG, $input)) {
701
            return $input;
702
        }
703
704
        $sorts = array_filter($input[Field::FILE_SORT_FLAG]);
705
706
        if (empty($sorts)) {
707
            return $input;
708
        }
709
710
        foreach ($sorts as $column => $order) {
711
            $input[$column] = $order;
712
        }
713
714
        request()->replace($input);
715
716
        return $input;
717
    }
718
719
    /**
720
     * Handle orderable update.
721
     *
722
     * @param int   $id
723
     * @param array $input
724
     *
725
     * @return bool
726
     */
727
    protected function handleOrderable($id, array $input = [])
728
    {
729
        if (array_key_exists('_orderable', $input)) {
730
            $model = $this->model->find($id);
731
732
            if ($model instanceof Sortable) {
0 ignored issues
show
Bug introduced by
The class Spatie\EloquentSortable\Sortable does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
733
                $input['_orderable'] == 1 ? $model->moveOrderUp() : $model->moveOrderDown();
734
735
                return true;
736
            }
737
        }
738
739
        return false;
740
    }
741
742
    /**
743
     * Update relation data.
744
     *
745
     * @param array $relationsData
746
     *
747
     * @return void
748
     */
749
    protected function updateRelation($relationsData)
750
    {
751
        foreach ($relationsData as $name => $values) {
752
            if (!method_exists($this->model, $name)) {
753
                continue;
754
            }
755
756
            $relation = $this->model->$name();
757
758
            $oneToOneRelation = $relation instanceof Relations\HasOne
759
                || $relation instanceof Relations\MorphOne
760
                || $relation instanceof Relations\BelongsTo;
761
762
            $prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation);
763
764
            if (empty($prepared)) {
765
                continue;
766
            }
767
768
            switch (true) {
769
                case $relation instanceof Relations\BelongsToMany:
770
                case $relation instanceof Relations\MorphToMany:
771
                    if (isset($prepared[$name])) {
772
                        $relation->sync($prepared[$name]);
773
                    }
774
                    break;
775
                case $relation instanceof Relations\HasOne:
776
777
                    $related = $this->model->$name;
778
779
                    // if related is empty
780
                    if (is_null($related)) {
781
                        $related = $relation->getRelated();
782
                        $qualifiedParentKeyName = $relation->getQualifiedParentKeyName();
783
                        $localKey = Arr::last(explode('.', $qualifiedParentKeyName));
784
                        $related->{$relation->getForeignKeyName()} = $this->model->{$localKey};
785
                    }
786
787
                    foreach ($prepared[$name] as $column => $value) {
788
                        $related->setAttribute($column, $value);
789
                    }
790
791
                    $related->save();
792
                    break;
793
                case $relation instanceof Relations\BelongsTo:
794
                case $relation instanceof Relations\MorphTo:
795
796
                    $parent = $this->model->$name;
797
798
                    // if related is empty
799
                    if (is_null($parent)) {
800
                        $parent = $relation->getRelated();
801
                    }
802
803
                    foreach ($prepared[$name] as $column => $value) {
804
                        $parent->setAttribute($column, $value);
805
                    }
806
807
                    $parent->save();
808
809
                    // When in creating, associate two models
810
                    $foreignKeyMethod = (app()->version() < '5.8.0') ? 'getForeignKey' : 'getForeignKeyName';
811
                    if (!$this->model->{$relation->{$foreignKeyMethod}()}) {
812
                        $this->model->{$relation->{$foreignKeyMethod}()} = $parent->getKey();
813
814
                        $this->model->save();
815
                    }
816
817
                    break;
818
                case $relation instanceof Relations\MorphOne:
819
                    $related = $this->model->$name;
820
                    if (is_null($related)) {
821
                        $related = $relation->make();
822
                    }
823
                    foreach ($prepared[$name] as $column => $value) {
824
                        $related->setAttribute($column, $value);
825
                    }
826
                    $related->save();
827
                    break;
828
                case $relation instanceof Relations\HasMany:
829
                case $relation instanceof Relations\MorphMany:
830
831
                    foreach ($prepared[$name] as $related) {
832
                        /** @var Relations\Relation $relation */
833
                        $relation = $this->model()->$name();
834
835
                        $keyName = $relation->getRelated()->getKeyName();
836
837
                        $instance = $relation->findOrNew(Arr::get($related, $keyName));
838
839
                        if ($related[static::REMOVE_FLAG_NAME] == 1) {
840
                            $instance->delete();
841
842
                            continue;
843
                        }
844
845
                        Arr::forget($related, static::REMOVE_FLAG_NAME);
846
847
                        $instance->fill($related);
848
849
                        $instance->save();
850
                    }
851
852
                    break;
853
            }
854
        }
855
    }
856
857
    /**
858
     * Prepare input data for update.
859
     *
860
     * @param array $updates
861
     * @param bool  $oneToOneRelation If column is one-to-one relation.
862
     *
863
     * @return array
864
     */
865
    protected function prepareUpdate(array $updates, $oneToOneRelation = false)
866
    {
867
        $prepared = [];
868
869
        /** @var Field $field */
870
        foreach ($this->builder->fields() as $field) {
871
            $columns = $field->column();
872
873
            // If column not in input array data, then continue.
874
            if (!Arr::has($updates, $columns)) {
875
                continue;
876
            }
877
878
            if ($this->invalidColumn($columns, $oneToOneRelation)) {
879
                continue;
880
            }
881
882
            $value = $this->getDataByColumn($updates, $columns);
883
884
            $value = $field->prepare($value);
885
886 View Code Duplication
            if (is_array($columns)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
887
                foreach ($columns as $name => $column) {
888
                    Arr::set($prepared, $column, $value[$name]);
889
                }
890
            } elseif (is_string($columns)) {
891
                Arr::set($prepared, $columns, $value);
892
            }
893
        }
894
895
        return $prepared;
896
    }
897
898
    /**
899
     * @param string|array $columns
900
     * @param bool         $oneToOneRelation
901
     *
902
     * @return bool
903
     */
904
    protected function invalidColumn($columns, $oneToOneRelation = false)
905
    {
906
        foreach ((array) $columns as $column) {
907
            if ((!$oneToOneRelation && Str::contains($column, '.')) ||
908
                ($oneToOneRelation && !Str::contains($column, '.'))) {
909
                return true;
910
            }
911
        }
912
913
        return false;
914
    }
915
916
    /**
917
     * Prepare input data for insert.
918
     *
919
     * @param $inserts
920
     *
921
     * @return array
922
     */
923
    protected function prepareInsert($inserts)
924
    {
925
        if ($this->isHasOneRelation($inserts)) {
926
            $inserts = Arr::dot($inserts);
927
        }
928
929
        foreach ($inserts as $column => $value) {
930
            if (is_null($field = $this->getFieldByColumn($column))) {
931
                unset($inserts[$column]);
932
                continue;
933
            }
934
935
            $inserts[$column] = $field->prepare($value);
936
        }
937
938
        $prepared = [];
939
940
        foreach ($inserts as $key => $value) {
941
            Arr::set($prepared, $key, $value);
942
        }
943
944
        return $prepared;
945
    }
946
947
    /**
948
     * Is input data is has-one relation.
949
     *
950
     * @param array $inserts
951
     *
952
     * @return bool
953
     */
954
    protected function isHasOneRelation($inserts)
955
    {
956
        $first = current($inserts);
957
958
        if (!is_array($first)) {
959
            return false;
960
        }
961
962
        if (is_array(current($first))) {
963
            return false;
964
        }
965
966
        return Arr::isAssoc($first);
967
    }
968
969
    /**
970
     * Ignore fields to save.
971
     *
972
     * @param string|array $fields
973
     *
974
     * @return $this
975
     */
976
    public function ignore($fields)
977
    {
978
        $this->ignored = array_merge($this->ignored, (array) $fields);
979
980
        return $this;
981
    }
982
983
    /**
984
     * @param array        $data
985
     * @param string|array $columns
986
     *
987
     * @return array|mixed
988
     */
989 View Code Duplication
    protected function getDataByColumn($data, $columns)
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...
990
    {
991
        if (is_string($columns)) {
992
            return Arr::get($data, $columns);
993
        }
994
995
        if (is_array($columns)) {
996
            $value = [];
997
            foreach ($columns as $name => $column) {
998
                if (!Arr::has($data, $column)) {
999
                    continue;
1000
                }
1001
                $value[$name] = Arr::get($data, $column);
1002
            }
1003
1004
            return $value;
1005
        }
1006
    }
1007
1008
    /**
1009
     * Find field object by column.
1010
     *
1011
     * @param $column
1012
     *
1013
     * @return mixed
1014
     */
1015
    protected function getFieldByColumn($column)
1016
    {
1017
        return $this->builder->fields()->first(
1018
            function (Field $field) use ($column) {
1019
                if (is_array($field->column())) {
1020
                    return in_array($column, $field->column());
1021
                }
1022
1023
                return $field->column() == $column;
1024
            }
1025
        );
1026
    }
1027
1028
    /**
1029
     * Set original data for each field.
1030
     *
1031
     * @return void
1032
     */
1033
    protected function setFieldOriginalValue()
1034
    {
1035
//        static::doNotSnakeAttributes($this->model);
1036
1037
        $values = $this->model->toArray();
1038
1039
        $this->builder->fields()->each(function (Field $field) use ($values) {
1040
            $field->setOriginal($values);
1041
        });
1042
    }
1043
1044
    /**
1045
     * Set all fields value in form.
1046
     *
1047
     * @param $id
1048
     *
1049
     * @return void
1050
     */
1051
    protected function setFieldValue($id)
1052
    {
1053
        $relations = $this->getRelations();
1054
1055
        $builder = $this->model();
1056
1057
        if ($this->isSoftDeletes) {
1058
            $builder = $builder->withTrashed();
1059
        }
1060
1061
        $this->model = $builder->with($relations)->findOrFail($id);
1062
1063
        $this->callEditing();
1064
1065
//        static::doNotSnakeAttributes($this->model);
1066
1067
        $data = $this->model->toArray();
1068
1069
        $this->builder->fields()->each(function (Field $field) use ($data) {
1070
            if (!in_array($field->column(), $this->ignored)) {
1071
                $field->fill($data);
1072
            }
1073
        });
1074
    }
1075
1076
    /**
1077
     * Don't snake case attributes.
1078
     *
1079
     * @param Model $model
1080
     *
1081
     * @return void
1082
     */
1083
    protected static function doNotSnakeAttributes(Model $model)
1084
    {
1085
        $class = get_class($model);
1086
1087
        $class::$snakeAttributes = false;
1088
    }
1089
1090
    /**
1091
     * Get validation messages.
1092
     *
1093
     * @param array $input
1094
     *
1095
     * @return MessageBag|bool
1096
     */
1097
    public function validationMessages($input)
1098
    {
1099
        $failedValidators = [];
1100
1101
        /** @var Field $field */
1102
        foreach ($this->builder->fields() as $field) {
1103
            if (!$validator = $field->getValidator($input)) {
1104
                continue;
1105
            }
1106
1107
            if (($validator instanceof Validator) && !$validator->passes()) {
1108
                $failedValidators[] = $validator;
1109
            }
1110
        }
1111
1112
        $message = $this->mergeValidationMessages($failedValidators);
1113
1114
        return $message->any() ? $message : false;
1115
    }
1116
1117
    /**
1118
     * Merge validation messages from input validators.
1119
     *
1120
     * @param \Illuminate\Validation\Validator[] $validators
1121
     *
1122
     * @return MessageBag
1123
     */
1124
    protected function mergeValidationMessages($validators)
1125
    {
1126
        $messageBag = new MessageBag();
1127
1128
        foreach ($validators as $validator) {
1129
            $messageBag = $messageBag->merge($validator->messages());
1130
        }
1131
1132
        return $messageBag;
1133
    }
1134
1135
    /**
1136
     * Get all relations of model from callable.
1137
     *
1138
     * @return array
1139
     */
1140
    public function getRelations()
1141
    {
1142
        $relations = $columns = [];
1143
1144
        /** @var Field $field */
1145
        foreach ($this->builder->fields() as $field) {
1146
            $columns[] = $field->column();
1147
        }
1148
1149
        foreach (Arr::flatten($columns) as $column) {
1150
            if (Str::contains($column, '.')) {
1151
                list($relation) = explode('.', $column);
1152
1153
                if (method_exists($this->model, $relation) &&
1154
                    $this->model->$relation() instanceof Relations\Relation
1155
                ) {
1156
                    $relations[] = $relation;
1157
                }
1158
            } elseif (method_exists($this->model, $column) &&
1159
                !method_exists(Model::class, $column)
1160
            ) {
1161
                $relations[] = $column;
1162
            }
1163
        }
1164
1165
        return array_unique($relations);
1166
    }
1167
1168
    /**
1169
     * Set action for form.
1170
     *
1171
     * @param string $action
1172
     *
1173
     * @return $this
1174
     */
1175
    public function setAction($action)
1176
    {
1177
        $this->builder()->setAction($action);
1178
1179
        return $this;
1180
    }
1181
1182
    /**
1183
     * Set field and label width in current form.
1184
     *
1185
     * @param int $fieldWidth
1186
     * @param int $labelWidth
1187
     *
1188
     * @return $this
1189
     */
1190
    public function setWidth($fieldWidth = 8, $labelWidth = 2)
1191
    {
1192
        $this->builder()->fields()->each(function ($field) use ($fieldWidth, $labelWidth) {
1193
            /* @var Field $field  */
1194
            $field->setWidth($fieldWidth, $labelWidth);
1195
        });
1196
1197
        $this->builder()->setWidth($fieldWidth, $labelWidth);
1198
1199
        return $this;
1200
    }
1201
1202
    /**
1203
     * Set view for form.
1204
     *
1205
     * @param string $view
1206
     *
1207
     * @return $this
1208
     */
1209
    public function setView($view)
1210
    {
1211
        $this->builder()->setView($view);
1212
1213
        return $this;
1214
    }
1215
1216
    /**
1217
     * Set title for form.
1218
     *
1219
     * @param string $title
1220
     *
1221
     * @return $this
1222
     */
1223
    public function setTitle($title = '')
1224
    {
1225
        $this->builder()->setTitle($title);
1226
1227
        return $this;
1228
    }
1229
1230
    /**
1231
     * Add a row in form.
1232
     *
1233
     * @param Closure $callback
1234
     *
1235
     * @return $this
1236
     */
1237
    public function row(Closure $callback)
1238
    {
1239
        $this->rows[] = new Row($callback, $this);
1240
1241
        return $this;
1242
    }
1243
1244
    /**
1245
     * Tools setting for form.
1246
     *
1247
     * @param Closure $callback
1248
     */
1249
    public function tools(Closure $callback)
1250
    {
1251
        $callback->call($this, $this->builder->getTools());
1252
    }
1253
1254
    /**
1255
     * Disable form submit.
1256
     *
1257
     * @param bool $disable
1258
     *
1259
     * @return $this
1260
     *
1261
     * @deprecated
1262
     */
1263
    public function disableSubmit(bool $disable = true)
1264
    {
1265
        $this->builder()->getFooter()->disableSubmit($disable);
1266
1267
        return $this;
1268
    }
1269
1270
    /**
1271
     * Disable form reset.
1272
     *
1273
     * @param bool $disable
1274
     *
1275
     * @return $this
1276
     *
1277
     * @deprecated
1278
     */
1279
    public function disableReset(bool $disable = true)
1280
    {
1281
        $this->builder()->getFooter()->disableReset($disable);
1282
1283
        return $this;
1284
    }
1285
1286
    /**
1287
     * Disable View Checkbox on footer.
1288
     *
1289
     * @param bool $disable
1290
     *
1291
     * @return $this
1292
     */
1293
    public function disableViewCheck(bool $disable = true)
1294
    {
1295
        $this->builder()->getFooter()->disableViewCheck($disable);
1296
1297
        return $this;
1298
    }
1299
1300
    /**
1301
     * Disable Editing Checkbox on footer.
1302
     *
1303
     * @param bool $disable
1304
     *
1305
     * @return $this
1306
     */
1307
    public function disableEditingCheck(bool $disable = true)
1308
    {
1309
        $this->builder()->getFooter()->disableEditingCheck($disable);
1310
1311
        return $this;
1312
    }
1313
1314
    /**
1315
     * Disable Creating Checkbox on footer.
1316
     *
1317
     * @param bool $disable
1318
     *
1319
     * @return $this
1320
     */
1321
    public function disableCreatingCheck(bool $disable = true)
1322
    {
1323
        $this->builder()->getFooter()->disableCreatingCheck($disable);
1324
1325
        return $this;
1326
    }
1327
1328
    /**
1329
     * Footer setting for form.
1330
     *
1331
     * @param Closure $callback
1332
     */
1333
    public function footer(Closure $callback)
1334
    {
1335
        call_user_func($callback, $this->builder()->getFooter());
1336
    }
1337
1338
    /**
1339
     * Get current resource route url.
1340
     *
1341
     * @param int $slice
1342
     *
1343
     * @return string
1344
     */
1345
    public function resource($slice = -2)
1346
    {
1347
        $segments = explode('/', trim(app('request')->getUri(), '/'));
1348
1349
        if ($slice != 0) {
1350
            $segments = array_slice($segments, 0, $slice);
1351
        }
1352
1353
        return implode('/', $segments);
1354
    }
1355
1356
    /**
1357
     * Render the form contents.
1358
     *
1359
     * @return string
1360
     */
1361
    public function render()
1362
    {
1363
        try {
1364
            return $this->builder->render();
1365
        } catch (\Exception $e) {
1366
            return Handler::renderException($e);
1367
        }
1368
    }
1369
1370
    /**
1371
     * Get or set input data.
1372
     *
1373
     * @param string $key
1374
     * @param null   $value
1375
     *
1376
     * @return array|mixed
1377
     */
1378
    public function input($key, $value = null)
1379
    {
1380
        if (is_null($value)) {
1381
            return Arr::get($this->inputs, $key);
1382
        }
1383
1384
        return Arr::set($this->inputs, $key, $value);
1385
    }
1386
1387
    /**
1388
     * Register builtin fields.
1389
     *
1390
     * @return void
1391
     */
1392
    public static function registerBuiltinFields()
1393
    {
1394
        $map = [
1395
            'button'         => Field\Button::class,
1396
            'checkbox'       => Field\Checkbox::class,
1397
            'color'          => Field\Color::class,
1398
            'currency'       => Field\Currency::class,
1399
            'date'           => Field\Date::class,
1400
            'dateRange'      => Field\DateRange::class,
1401
            'datetime'       => Field\Datetime::class,
1402
            'dateTimeRange'  => Field\DatetimeRange::class,
1403
            'datetimeRange'  => Field\DatetimeRange::class,
1404
            'decimal'        => Field\Decimal::class,
1405
            'display'        => Field\Display::class,
1406
            'divider'        => Field\Divide::class,
1407
            'divide'         => Field\Divide::class,
1408
            'embeds'         => Field\Embeds::class,
1409
            'editor'         => Field\Editor::class,
1410
            'email'          => Field\Email::class,
1411
            'file'           => Field\File::class,
1412
            'hasMany'        => Field\HasMany::class,
1413
            'hidden'         => Field\Hidden::class,
1414
            'id'             => Field\Id::class,
1415
            'image'          => Field\Image::class,
1416
            'ip'             => Field\Ip::class,
1417
            'map'            => Field\Map::class,
1418
            'mobile'         => Field\Mobile::class,
1419
            'month'          => Field\Month::class,
1420
            'multipleSelect' => Field\MultipleSelect::class,
1421
            'number'         => Field\Number::class,
1422
            'password'       => Field\Password::class,
1423
            'radio'          => Field\Radio::class,
1424
            'rate'           => Field\Rate::class,
1425
            'select'         => Field\Select::class,
1426
            'slider'         => Field\Slider::class,
1427
            'switch'         => Field\SwitchField::class,
1428
            'text'           => Field\Text::class,
1429
            'textarea'       => Field\Textarea::class,
1430
            'time'           => Field\Time::class,
1431
            'timeRange'      => Field\TimeRange::class,
1432
            'url'            => Field\Url::class,
1433
            'year'           => Field\Year::class,
1434
            'html'           => Field\Html::class,
1435
            'tags'           => Field\Tags::class,
1436
            'icon'           => Field\Icon::class,
1437
            'multipleFile'   => Field\MultipleFile::class,
1438
            'multipleImage'  => Field\MultipleImage::class,
1439
            'captcha'        => Field\Captcha::class,
1440
            'listbox'        => Field\Listbox::class,
1441
            'table'          => Field\Table::class,
1442
        ];
1443
1444
        foreach ($map as $abstract => $class) {
1445
            static::extend($abstract, $class);
1446
        }
1447
    }
1448
1449
    /**
1450
     * Register custom field.
1451
     *
1452
     * @param string $abstract
1453
     * @param string $class
1454
     *
1455
     * @return void
1456
     */
1457
    public static function extend($abstract, $class)
1458
    {
1459
        static::$availableFields[$abstract] = $class;
1460
    }
1461
1462
    /**
1463
     * Set form field alias.
1464
     *
1465
     * @param string $field
1466
     * @param string $alias
1467
     *
1468
     * @return void
1469
     */
1470
    public static function alias($field, $alias)
1471
    {
1472
        static::$fieldAlias[$alias] = $field;
1473
    }
1474
1475
    /**
1476
     * Remove registered field.
1477
     *
1478
     * @param array|string $abstract
1479
     */
1480
    public static function forget($abstract)
1481
    {
1482
        Arr::forget(static::$availableFields, $abstract);
1483
    }
1484
1485
    /**
1486
     * Find field class.
1487
     *
1488
     * @param string $method
1489
     *
1490
     * @return bool|mixed
1491
     */
1492
    public static function findFieldClass($method)
1493
    {
1494
        // If alias exists.
1495
        if (isset(static::$fieldAlias[$method])) {
1496
            $method = static::$fieldAlias[$method];
1497
        }
1498
1499
        $class = Arr::get(static::$availableFields, $method);
1500
1501
        if (class_exists($class)) {
1502
            return $class;
1503
        }
1504
1505
        return false;
1506
    }
1507
1508
    /**
1509
     * Collect assets required by registered field.
1510
     *
1511
     * @return array
1512
     */
1513
    public static function collectFieldAssets()
1514
    {
1515
        if (!empty(static::$collectedAssets)) {
1516
            return static::$collectedAssets;
1517
        }
1518
1519
        $css = collect();
1520
        $js = collect();
1521
1522
        foreach (static::$availableFields as $field) {
1523
            if (!method_exists($field, 'getAssets')) {
1524
                continue;
1525
            }
1526
1527
            $assets = call_user_func([$field, 'getAssets']);
1528
1529
            $css->push(Arr::get($assets, 'css'));
1530
            $js->push(Arr::get($assets, 'js'));
1531
        }
1532
1533
        return static::$collectedAssets = [
1534
            'css' => $css->flatten()->unique()->filter()->toArray(),
1535
            'js'  => $js->flatten()->unique()->filter()->toArray(),
1536
        ];
1537
    }
1538
1539
    /**
1540
     * Getter.
1541
     *
1542
     * @param string $name
1543
     *
1544
     * @return array|mixed
1545
     */
1546
    public function __get($name)
1547
    {
1548
        return $this->input($name);
1549
    }
1550
1551
    /**
1552
     * Setter.
1553
     *
1554
     * @param string $name
1555
     * @param mixed  $value
1556
     * @return array
1557
     */
1558
    public function __set($name, $value)
1559
    {
1560
        return Arr::set($this->inputs, $name, $value);
1561
    }
1562
1563
    /**
1564
     * Generate a Field object and add to form builder if Field exists.
1565
     *
1566
     * @param string $method
1567
     * @param array  $arguments
1568
     *
1569
     * @return Field
1570
     */
1571
    public function __call($method, $arguments)
1572
    {
1573
        if ($className = static::findFieldClass($method)) {
1574
            $column = Arr::get($arguments, 0, ''); //[0];
1575
1576
            $element = new $className($column, array_slice($arguments, 1));
1577
1578
            $this->pushField($element);
1579
1580
            return $element;
1581
        }
1582
1583
        admin_error('Error', "Field type [$method] does not exist.");
1584
1585
        return new Field\Nullable();
1586
    }
1587
}
1588