Completed
Pull Request — master (#2905)
by
unknown
03:34
created

Form::getFieldByColumn()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 1
nop 1
dl 0
loc 12
rs 9.8666
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\Row;
10
use Encore\Admin\Form\Tab;
11
use Illuminate\Contracts\Support\Renderable;
12
use Illuminate\Database\Eloquent\Model;
13
use Illuminate\Database\Eloquent\Relations;
14
use Illuminate\Database\Eloquent\SoftDeletes;
15
use Illuminate\Http\Request;
16
use Illuminate\Support\Arr;
17
use Illuminate\Support\Facades\DB;
18
use Illuminate\Support\Facades\Input;
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
 */
72
class Form implements Renderable
73
{
74
    /**
75
     * Eloquent model of the form.
76
     *
77
     * @var Model
78
     */
79
    protected $model;
80
81
    /**
82
     * @var \Illuminate\Validation\Validator
83
     */
84
    protected $validator;
85
86
    /**
87
     * @var Builder
88
     */
89
    protected $builder;
90
91
    /**
92
     * Submitted callback.
93
     *
94
     * @var Closure[]
95
     */
96
    protected $submitted = [];
97
98
    /**
99
     * Saving callback.
100
     *
101
     * @var Closure[]
102
     */
103
    protected $saving = [];
104
105
    /**
106
     * Saved callback.
107
     *
108
     * @var Closure[]
109
     */
110
    protected $saved = [];
111
112
    /**
113
     * Callbacks after getting editing model.
114
     *
115
     * @var Closure[]
116
     */
117
    protected $editing = [];
118
119
    /**
120
     * Data for save to current model from input.
121
     *
122
     * @var array
123
     */
124
    protected $updates = [];
125
126
    /**
127
     * Data for save to model's relations from input.
128
     *
129
     * @var array
130
     */
131
    protected $relations = [];
132
133
    /**
134
     * Input data.
135
     *
136
     * @var array
137
     */
138
    protected $inputs = [];
139
140
    /**
141
     * Available fields.
142
     *
143
     * @var array
144
     */
145
    public static $availableFields = [];
146
147
    /**
148
     * Form field alias.
149
     *
150
     * @var array
151
     */
152
    public static $fieldAlias = [];
153
154
    /**
155
     * Ignored saving fields.
156
     *
157
     * @var array
158
     */
159
    protected $ignored = [];
160
161
    /**
162
     * Collected field assets.
163
     *
164
     * @var array
165
     */
166
    protected static $collectedAssets = [];
167
168
    /**
169
     * @var Form\Tab
170
     */
171
    protected $tab = null;
172
173
    /**
174
     * Remove flag in `has many` form.
175
     */
176
    const REMOVE_FLAG_NAME = '_remove_';
177
178
    /**
179
     * Field rows in form.
180
     *
181
     * @var array
182
     */
183
    public $rows = [];
184
185
    /**
186
     * @var bool
187
     */
188
    protected $isSoftDeletes = false;
189
190
    /**
191
     * Create a new form instance.
192
     *
193
     * @param $model
194
     * @param \Closure $callback
195
     */
196
    public function __construct($model, Closure $callback = null)
197
    {
198
        $this->model = $model;
199
200
        $this->builder = new Builder($this);
201
202
        if ($callback instanceof Closure) {
203
            $callback($this);
204
        }
205
206
        $this->isSoftDeletes = in_array(SoftDeletes::class, class_uses($this->model));
207
    }
208
209
    /**
210
     * @param Field $field
211
     *
212
     * @return $this
213
     */
214
    public function pushField(Field $field)
215
    {
216
        $field->setForm($this);
217
218
        $this->builder->fields()->push($field);
219
220
        return $this;
221
    }
222
223
    /**
224
     * @return Model
225
     */
226
    public function model()
227
    {
228
        return $this->model;
229
    }
230
231
    /**
232
     * @return Builder
233
     */
234
    public function builder()
235
    {
236
        return $this->builder;
237
    }
238
239
    /**
240
     * Generate a edit form.
241
     *
242
     * @param $id
243
     *
244
     * @return $this
245
     */
246
    public function edit($id)
247
    {
248
        $this->builder->setMode(Builder::MODE_EDIT);
249
        $this->builder->setResourceId($id);
250
251
        $this->setFieldValue($id);
252
253
        return $this;
254
    }
255
256
    /**
257
     * Use tab to split form.
258
     *
259
     * @param string  $title
260
     * @param Closure $content
261
     *
262
     * @return $this
263
     */
264
    public function tab($title, Closure $content, $active = false)
265
    {
266
        $this->getTab()->append($title, $content, $active);
267
268
        return $this;
269
    }
270
271
    /**
272
     * Get Tab instance.
273
     *
274
     * @return Tab
275
     */
276
    public function getTab()
277
    {
278
        if (is_null($this->tab)) {
279
            $this->tab = new Tab($this);
280
        }
281
282
        return $this->tab;
283
    }
284
285
    /**
286
     * Destroy data entity and remove files.
287
     *
288
     * @param $id
289
     *
290
     * @return mixed
291
     */
292
    public function destroy($id)
293
    {
294
        collect(explode(',', $id))->filter()->each(function ($id) {
295
            $builder = $this->model()->newQuery();
296
297
            if ($this->isSoftDeletes) {
298
                $builder = $builder->withTrashed();
299
            }
300
301
            $model = $builder->with($this->getRelations())->findOrFail($id);
302
303
            if ($this->isSoftDeletes && $model->trashed()) {
304
                $this->deleteFiles($model, true);
305
                $model->forceDelete();
306
307
                return;
308
            }
309
310
            $this->deleteFiles($model);
311
            $model->delete();
312
        });
313
314
        return true;
315
    }
316
317
    /**
318
     * Remove files in record.
319
     *
320
     * @param Model $model
321
     * @param bool  $forceDelete
322
     */
323
    protected function deleteFiles(Model $model, $forceDelete = false)
324
    {
325
        // If it's a soft delete, the files in the data will not be deleted.
326
        if (!$forceDelete && $this->isSoftDeletes) {
327
            return;
328
        }
329
330
        $data = $model->toArray();
331
332
        $this->builder->fields()->filter(function ($field) {
333
            return $field instanceof Field\File;
334
        })->each(function (Field\File $file) use ($data) {
335
            $file->setOriginal($data);
336
337
            $file->destroy();
338
        });
339
    }
340
341
    /**
342
     * Store a new record.
343
     *
344
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\Http\JsonResponse
345
     */
346
    public function store()
347
    {
348
        $data = Input::all();
349
350
        // Handle validation errors.
351
        if ($validationMessages = $this->validationMessages($data)) {
352
            return back()->withInput()->withErrors($validationMessages);
353
        }
354
355
        //Disable Pjax otherwise it would cause the show() method to be called before redirecting
356
        if (request()->pjax()) {
357
            request()->headers->set('X-PJAX', false);
358
        }
359
360
        if (($response = $this->prepare($data)) instanceof Response) {
361
            return $response;
362
        }
363
364 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...
365
            $inserts = $this->prepareInsert($this->updates);
366
367
            foreach ($inserts as $column => $value) {
368
                $this->model->setAttribute($column, $value);
369
            }
370
371
            $this->model->save();
372
373
            $this->updateRelation($this->relations);
374
        });
375
376
        if (($response = $this->callSaved()) instanceof Response) {
377
            return $response;
378
        }
379
380
        if ($response = $this->ajaxResponse(trans('admin.save_succeeded'))) {
381
            return $response;
382
        }
383
384
        return $this->redirectAfterStore();
385
    }
386
387
    /**
388
     * Get ajax response.
389
     *
390
     * @param string $message
391
     *
392
     * @return bool|\Illuminate\Http\JsonResponse
393
     */
394
    protected function ajaxResponse($message)
395
    {
396
        $request = Request::capture();
397
398
        // ajax but not pjax
399
        if ($request->ajax() && !$request->pjax()) {
400
            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...
401
                'status'  => true,
402
                'message' => $message,
403
            ]);
404
        }
405
406
        return false;
407
    }
408
409
    /**
410
     * Prepare input data for insert or update.
411
     *
412
     * @param array $data
413
     *
414
     * @return mixed
415
     */
416
    protected function prepare($data = [])
417
    {
418
        if (($response = $this->callSubmitted()) instanceof Response) {
419
            return $response;
420
        }
421
422
        $this->inputs = array_merge($this->removeIgnoredFields($data), $this->inputs);
423
424
        if (($response = $this->callSaving()) instanceof Response) {
425
            return $response;
426
        }
427
428
        $this->relations = $this->getRelationInputs($this->inputs);
429
430
        $this->updates = array_except($this->inputs, array_keys($this->relations));
431
    }
432
433
    /**
434
     * Remove ignored fields from input.
435
     *
436
     * @param array $input
437
     *
438
     * @return array
439
     */
440
    protected function removeIgnoredFields($input)
441
    {
442
        array_forget($input, $this->ignored);
443
444
        return $input;
445
    }
446
447
    /**
448
     * Get inputs for relations.
449
     *
450
     * @param array $inputs
451
     *
452
     * @return array
453
     */
454
    protected function getRelationInputs($inputs = [])
455
    {
456
        $relations = [];
457
458
        foreach ($inputs as $column => $value) {
459
            if (method_exists($this->model, $column)) {
460
                $relation = call_user_func([$this->model, $column]);
461
462
                if ($relation instanceof Relations\Relation) {
463
                    $relations[$column] = $value;
464
                }
465
            }
466
        }
467
468
        return $relations;
469
    }
470
471
    /**
472
     * Call editing callbacks.
473
     *
474
     * @return void
475
     */
476
    protected function callEditing()
477
    {
478
        foreach ($this->editing as $func) {
479
            call_user_func($func, $this);
480
        }
481
    }
482
483
    /**
484
     * Call submitted callback.
485
     *
486
     * @return mixed
487
     */
488
    protected function callSubmitted()
489
    {
490
        foreach ($this->submitted as $func) {
491
            if ($func instanceof Closure && ($ret = call_user_func($func, $this)) instanceof Response) {
492
                return $ret;
493
            }
494
        }
495
    }
496
497
    /**
498
     * Call saving callback.
499
     *
500
     * @return mixed
501
     */
502
    protected function callSaving()
503
    {
504
        foreach ($this->saving as $func) {
505
            if ($func instanceof Closure && ($ret = call_user_func($func, $this)) instanceof Response) {
506
                return $ret;
507
            }
508
        }
509
    }
510
511
    /**
512
     * Callback after saving a Model.
513
     *
514
     * @return mixed|null
515
     */
516
    protected function callSaved()
517
    {
518
        foreach ($this->saved as $func) {
519
            if ($func instanceof Closure && ($ret = call_user_func($func, $this)) instanceof Response) {
520
                return $ret;
521
            }
522
        }
523
    }
524
525
    /**
526
     * Handle update.
527
     *
528
     * @param int $id
529
     *
530
     * @return \Symfony\Component\HttpFoundation\Response
531
     */
532
    public function update($id, $data = null)
533
    {
534
        $data = ($data) ?: Input::all();
535
536
        $isEditable = $this->isEditable($data);
537
538
        $data = $this->handleEditable($data);
539
540
        $data = $this->handleFileDelete($data);
541
542
        if ($this->handleOrderable($id, $data)) {
543
            return response([
0 ignored issues
show
Bug Compatibility introduced by
The expression response(array('status' ...n.update_succeeded'))); of type Illuminate\Http\Response...Routing\ResponseFactory adds the type Illuminate\Contracts\Routing\ResponseFactory to the return on line 543 which is incompatible with the return type documented by Encore\Admin\Form::update of type Symfony\Component\HttpFoundation\Response.
Loading history...
544
                'status'  => true,
545
                'message' => trans('admin.update_succeeded'),
546
            ]);
547
        }
548
549
        /* @var Model $this->model */
550
        $this->model = $this->model->with($this->getRelations())->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

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...
551
552
        $this->setFieldOriginalValue();
553
554
        // Handle validation errors.
555
        if ($validationMessages = $this->validationMessages($data)) {
556
            if (!$isEditable) {
557
                return back()->withInput()->withErrors($validationMessages);
558
            } else {
559
                return response()->json(['errors' => array_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...
560
            }
561
        }
562
563
        if (($response = $this->prepare($data)) instanceof Response) {
564
            return $response;
565
        }
566
567 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...
568
            $updates = $this->prepareUpdate($this->updates);
569
570
            foreach ($updates as $column => $value) {
571
                /* @var Model $this->model */
572
                $this->model->setAttribute($column, $value);
573
            }
574
575
            $this->model->save();
576
577
            $this->updateRelation($this->relations);
578
        });
579
        //Disable Pjax otherwise it would cause the show() method to be called before redirecting
580
        if (request()->pjax()) {
581
            request()->headers->set('X-PJAX', false);
582
        }
583
        if (($result = $this->callSaved()) instanceof Response) {
584
            return $result;
585
        }
586
587
        if ($response = $this->ajaxResponse(trans('admin.update_succeeded'))) {
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->ajaxResponse(tran...in.update_succeeded')); of type boolean|Illuminate\Http\JsonResponse adds the type boolean to the return on line 588 which is incompatible with the return type documented by Encore\Admin\Form::update of type Symfony\Component\HttpFoundation\Response.
Loading history...
588
            return $response;
589
        }
590
591
        return $this->redirectAfterUpdate($id);
592
    }
593
594
    /**
595
     * Get RedirectResponse after store.
596
     *
597
     * @return \Illuminate\Http\RedirectResponse
598
     */
599
    protected function redirectAfterStore()
600
    {
601
        $resourcesPath = $this->resource(0);
602
603
        $key = $this->model->getKey();
604
605
        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 605 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterStore of type Illuminate\Http\RedirectResponse.
Loading history...
606
    }
607
608
    /**
609
     * Get RedirectResponse after update.
610
     *
611
     * @param mixed $key
612
     *
613
     * @return \Illuminate\Http\RedirectResponse
614
     */
615
    protected function redirectAfterUpdate($key)
616
    {
617
        $resourcesPath = $this->resource(-1);
618
619
        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 619 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterUpdate of type Illuminate\Http\RedirectResponse.
Loading history...
620
    }
621
622
    /**
623
     * Get RedirectResponse after data saving.
624
     *
625
     * @param string $resourcesPath
626
     * @param string $key
627
     *
628
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
629
     */
630
    protected function redirectAfterSaving($resourcesPath, $key)
631
    {
632
        if (request('after-save') == 1) {
633
            // continue editing
634
            $url = rtrim($resourcesPath, '/')."/{$key}/edit";
635
        } elseif (request('after-save') == 2) {
636
            // continue creating
637
            $url = rtrim($resourcesPath, '/').'/create';
638
        } elseif (request('after-save') == 3) {
639
            // view resource
640
            $url = rtrim($resourcesPath, '/')."/{$key}";
641
        } else {
642
            $url = request(Builder::PREVIOUS_URL_KEY) ?: $resourcesPath;
643
        }
644
645
        admin_toastr(trans('admin.save_succeeded'));
646
647
        return redirect($url);
648
    }
649
650
    /**
651
     * Check if request is from editable.
652
     *
653
     * @param array $input
654
     *
655
     * @return bool
656
     */
657
    protected function isEditable(array $input = [])
658
    {
659
        return array_key_exists('_editable', $input);
660
    }
661
662
    /**
663
     * Handle editable update.
664
     *
665
     * @param array $input
666
     *
667
     * @return array
668
     */
669
    protected function handleEditable(array $input = [])
670
    {
671
        if (array_key_exists('_editable', $input)) {
672
            $name = $input['name'];
673
            $value = $input['value'];
674
675
            array_forget($input, ['pk', 'value', 'name']);
676
            array_set($input, $name, $value);
677
        }
678
679
        return $input;
680
    }
681
682
    /**
683
     * @param array $input
684
     *
685
     * @return array
686
     */
687
    protected function handleFileDelete(array $input = [])
688
    {
689
        if (array_key_exists(Field::FILE_DELETE_FLAG, $input)) {
690
            $input[Field::FILE_DELETE_FLAG] = $input['key'];
691
            unset($input['key']);
692
        }
693
694
        Input::replace($input);
695
696
        return $input;
697
    }
698
699
    /**
700
     * Handle orderable update.
701
     *
702
     * @param int   $id
703
     * @param array $input
704
     *
705
     * @return bool
706
     */
707
    protected function handleOrderable($id, array $input = [])
708
    {
709
        if (array_key_exists('_orderable', $input)) {
710
            $model = $this->model->find($id);
711
712
            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...
713
                $input['_orderable'] == 1 ? $model->moveOrderUp() : $model->moveOrderDown();
714
715
                return true;
716
            }
717
        }
718
719
        return false;
720
    }
721
722
    /**
723
     * Update relation data.
724
     *
725
     * @param array $relationsData
726
     *
727
     * @return void
728
     */
729
    protected function updateRelation($relationsData)
730
    {
731
        foreach ($relationsData as $name => $values) {
732
            if (!method_exists($this->model, $name)) {
733
                continue;
734
            }
735
736
            $relation = $this->model->$name();
737
738
            $oneToOneRelation = $relation instanceof Relations\HasOne
739
                || $relation instanceof Relations\MorphOne
740
                || $relation instanceof Relations\BelongsTo;
741
742
            $prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation);
743
744
            if (empty($prepared)) {
745
                continue;
746
            }
747
748
            switch (get_class($relation)) {
749
                case Relations\BelongsToMany::class:
750
                case Relations\MorphToMany::class:
751
                    if (isset($prepared[$name])) {
752
                        $relation->sync($prepared[$name]);
753
                    }
754
                    break;
755
                case Relations\HasOne::class:
756
757
                    $related = $this->model->$name;
758
759
                    // if related is empty
760
                    if (is_null($related)) {
761
                        $related = $relation->getRelated();
762
                        $qualifiedParentKeyName = $relation->getQualifiedParentKeyName();
763
                        $localKey = array_last(explode('.', $qualifiedParentKeyName));
764
                        $related->{$relation->getForeignKeyName()} = $this->model->{$localKey};
765
                    }
766
767
                    foreach ($prepared[$name] as $column => $value) {
768
                        $related->setAttribute($column, $value);
769
                    }
770
771
                    $related->save();
772
                    break;
773
                case Relations\BelongsTo::class:
774
775
                    $parent = $this->model->$name;
776
777
                    // if related is empty
778
                    if (is_null($parent)) {
779
                        $parent = $relation->getRelated();
780
                    }
781
782
                    foreach ($prepared[$name] as $column => $value) {
783
                        $parent->setAttribute($column, $value);
784
                    }
785
786
                    $parent->save();
787
788
                    // When in creating, associate two models
789
                    if (!$this->model->{$relation->getForeignKey()}) {
790
                        $this->model->{$relation->getForeignKey()} = $parent->getKey();
791
792
                        $this->model->save();
793
                    }
794
795
                    break;
796
                case Relations\MorphOne::class:
797
                    $related = $this->model->$name;
798
                    if (is_null($related)) {
799
                        $related = $relation->make();
800
                    }
801
                    foreach ($prepared[$name] as $column => $value) {
802
                        $related->setAttribute($column, $value);
803
                    }
804
                    $related->save();
805
                    break;
806
                case Relations\HasMany::class:
807
                case Relations\MorphMany::class:
808
809
                    foreach ($prepared[$name] as $related) {
810
                        /** @var Relations\Relation $relation */
811
                        $relation = $this->model()->$name();
812
813
                        $keyName = $relation->getRelated()->getKeyName();
814
815
                        $instance = $relation->findOrNew(array_get($related, $keyName));
816
817
                        if ($related[static::REMOVE_FLAG_NAME] == 1) {
818
                            $instance->delete();
819
820
                            continue;
821
                        }
822
823
                        array_forget($related, static::REMOVE_FLAG_NAME);
824
825
                        $instance->fill($related);
826
827
                        $instance->save();
828
                    }
829
830
                    break;
831
            }
832
        }
833
    }
834
835
    /**
836
     * Prepare input data for update.
837
     *
838
     * @param array $updates
839
     * @param bool  $oneToOneRelation If column is one-to-one relation.
840
     *
841
     * @return array
842
     */
843
    protected function prepareUpdate(array $updates, $oneToOneRelation = false)
844
    {
845
        $prepared = [];
846
847
        /** @var Field $field */
848
        foreach ($this->builder->fields() as $field) {
849
            $columns = $field->column();
850
851
            // If column not in input array data, then continue.
852
            if (!array_has($updates, $columns)) {
853
                continue;
854
            }
855
856
            if ($this->invalidColumn($columns, $oneToOneRelation)) {
857
                continue;
858
            }
859
860
            $value = $this->getDataByColumn($updates, $columns);
861
862
            $value = $field->prepare($value);
863
864 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...
865
                foreach ($columns as $name => $column) {
866
                    array_set($prepared, $column, $value[$name]);
867
                }
868
            } elseif (is_string($columns)) {
869
                array_set($prepared, $columns, $value);
870
            }
871
        }
872
873
        return $prepared;
874
    }
875
876
    /**
877
     * @param string|array $columns
878
     * @param bool         $oneToOneRelation
879
     *
880
     * @return bool
881
     */
882
    protected function invalidColumn($columns, $oneToOneRelation = false)
883
    {
884
        foreach ((array) $columns as $column) {
885
            if ((!$oneToOneRelation && Str::contains($column, '.')) || ($oneToOneRelation && !Str::contains($column, '.'))) {
886
                return true;
887
            }
888
        }
889
890
        return false;
891
    }
892
893
    /**
894
     * Prepare input data for insert.
895
     *
896
     * @param $inserts
897
     *
898
     * @return array
899
     */
900
    protected function prepareInsert($inserts)
901
    {
902
        if ($this->isHasOneRelation($inserts)) {
903
            $inserts = array_dot($inserts);
904
        }
905
906
        foreach ($inserts as $column => $value) {
907
            if (is_null($field = $this->getFieldByColumn($column))) {
908
                unset($inserts[$column]);
909
                continue;
910
            }
911
912
            $inserts[$column] = $field->prepare($value);
913
        }
914
915
        $prepared = [];
916
917
        foreach ($inserts as $key => $value) {
918
            array_set($prepared, $key, $value);
919
        }
920
921
        return $prepared;
922
    }
923
924
    /**
925
     * Is input data is has-one relation.
926
     *
927
     * @param array $inserts
928
     *
929
     * @return bool
930
     */
931
    protected function isHasOneRelation($inserts)
932
    {
933
        $first = current($inserts);
934
935
        if (!is_array($first)) {
936
            return false;
937
        }
938
939
        if (is_array(current($first))) {
940
            return false;
941
        }
942
943
        return Arr::isAssoc($first);
944
    }
945
946
    /**
947
     * Set after getting editing model callback.
948
     *
949
     * @param Closure $callback
950
     *
951
     * @return void
952
     */
953
    public function editing(Closure $callback)
954
    {
955
        $this->editing[] = $callback;
956
    }
957
958
    /**
959
     * Set submitted callback.
960
     *
961
     * @param Closure $callback
962
     *
963
     * @return void
964
     */
965
    public function submitted(Closure $callback)
966
    {
967
        $this->submitted[] = $callback;
968
    }
969
970
    /**
971
     * Set saving callback.
972
     *
973
     * @param Closure $callback
974
     *
975
     * @return void
976
     */
977
    public function saving(Closure $callback)
978
    {
979
        $this->saving[] = $callback;
980
    }
981
982
    /**
983
     * Set saved callback.
984
     *
985
     * @param Closure $callback
986
     *
987
     * @return void
988
     */
989
    public function saved(Closure $callback)
990
    {
991
        $this->saved[] = $callback;
992
    }
993
994
    /**
995
     * Ignore fields to save.
996
     *
997
     * @param string|array $fields
998
     *
999
     * @return $this
1000
     */
1001
    public function ignore($fields)
1002
    {
1003
        $this->ignored = array_merge($this->ignored, (array) $fields);
1004
1005
        return $this;
1006
    }
1007
1008
    /**
1009
     * @param array        $data
1010
     * @param string|array $columns
1011
     *
1012
     * @return array|mixed
1013
     */
1014 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...
1015
    {
1016
        if (is_string($columns)) {
1017
            return array_get($data, $columns);
1018
        }
1019
1020
        if (is_array($columns)) {
1021
            $value = [];
1022
            foreach ($columns as $name => $column) {
1023
                if (!array_has($data, $column)) {
1024
                    continue;
1025
                }
1026
                $value[$name] = array_get($data, $column);
1027
            }
1028
1029
            return $value;
1030
        }
1031
    }
1032
1033
    /**
1034
     * Find field object by column.
1035
     *
1036
     * @param $column
1037
     *
1038
     * @return mixed
1039
     */
1040
    protected function getFieldByColumn($column)
1041
    {
1042
        return $this->builder->fields()->first(
1043
            function (Field $field) use ($column) {
1044
                if (is_array($field->column())) {
1045
                    return in_array($column, $field->column());
1046
                }
1047
1048
                return $field->column() == $column;
1049
            }
1050
        );
1051
    }
1052
1053
    /**
1054
     * Set original data for each field.
1055
     *
1056
     * @return void
1057
     */
1058
    protected function setFieldOriginalValue()
1059
    {
1060
//        static::doNotSnakeAttributes($this->model);
1061
1062
        $values = $this->model->toArray();
1063
1064
        $this->builder->fields()->each(function (Field $field) use ($values) {
1065
            $field->setOriginal($values);
1066
        });
1067
    }
1068
1069
    /**
1070
     * Set all fields value in form.
1071
     *
1072
     * @param $id
1073
     *
1074
     * @return void
1075
     */
1076
    protected function setFieldValue($id)
1077
    {
1078
        $relations = $this->getRelations();
1079
1080
        $builder = $this->model();
1081
1082
        if ($this->isSoftDeletes) {
1083
            $builder->withTrashed();
1084
        }
1085
1086
        $this->model = $builder->with($relations)->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

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...
1087
1088
        $this->callEditing();
1089
1090
//        static::doNotSnakeAttributes($this->model);
1091
1092
        $data = $this->model->toArray();
1093
1094
        $this->builder->fields()->each(function (Field $field) use ($data) {
1095
            if (!in_array($field->column(), $this->ignored)) {
1096
                $field->fill($data);
1097
            }
1098
        });
1099
    }
1100
1101
    /**
1102
     * Don't snake case attributes.
1103
     *
1104
     * @param Model $model
1105
     *
1106
     * @return void
1107
     */
1108
    protected static function doNotSnakeAttributes(Model $model)
1109
    {
1110
        $class = get_class($model);
1111
1112
        $class::$snakeAttributes = false;
1113
    }
1114
1115
    /**
1116
     * Get validation messages.
1117
     *
1118
     * @param array $input
1119
     *
1120
     * @return MessageBag|bool
1121
     */
1122
    public function validationMessages($input)
1123
    {
1124
        $failedValidators = [];
1125
1126
        /** @var Field $field */
1127
        foreach ($this->builder->fields() as $field) {
1128
            if (!$validator = $field->getValidator($input)) {
1129
                continue;
1130
            }
1131
1132
            if (($validator instanceof Validator) && !$validator->passes()) {
1133
                $failedValidators[] = $validator;
1134
            }
1135
        }
1136
1137
        $message = $this->mergeValidationMessages($failedValidators);
1138
1139
        return $message->any() ? $message : false;
1140
    }
1141
1142
    /**
1143
     * Merge validation messages from input validators.
1144
     *
1145
     * @param \Illuminate\Validation\Validator[] $validators
1146
     *
1147
     * @return MessageBag
1148
     */
1149
    protected function mergeValidationMessages($validators)
1150
    {
1151
        $messageBag = new MessageBag();
1152
1153
        foreach ($validators as $validator) {
1154
            $messageBag = $messageBag->merge($validator->messages());
1155
        }
1156
1157
        return $messageBag;
1158
    }
1159
1160
    /**
1161
     * Get all relations of model from callable.
1162
     *
1163
     * @return array
1164
     */
1165
    public function getRelations()
1166
    {
1167
        $relations = $columns = [];
1168
1169
        /** @var Field $field */
1170
        foreach ($this->builder->fields() as $field) {
1171
            $columns[] = $field->column();
1172
        }
1173
1174
        foreach (array_flatten($columns) as $column) {
1175
            if (str_contains($column, '.')) {
1176
                list($relation) = explode('.', $column);
1177
1178
                if (method_exists($this->model, $relation) &&
1179
                    $this->model->$relation() instanceof Relations\Relation) {
1180
                    $relations[] = $relation;
1181
                }
1182
            } elseif (method_exists($this->model, $column) &&
1183
                !method_exists(Model::class, $column)) {
1184
                $relations[] = $column;
1185
            }
1186
        }
1187
1188
        return array_unique($relations);
1189
    }
1190
1191
    /**
1192
     * Set action for form.
1193
     *
1194
     * @param string $action
1195
     *
1196
     * @return $this
1197
     */
1198
    public function setAction($action)
1199
    {
1200
        $this->builder()->setAction($action);
1201
1202
        return $this;
1203
    }
1204
1205
    /**
1206
     * Set field and label width in current form.
1207
     *
1208
     * @param int $fieldWidth
1209
     * @param int $labelWidth
1210
     *
1211
     * @return $this
1212
     */
1213
    public function setWidth($fieldWidth = 8, $labelWidth = 2)
1214
    {
1215
        $this->builder()->fields()->each(function ($field) use ($fieldWidth, $labelWidth) {
1216
            /* @var Field $field  */
1217
            $field->setWidth($fieldWidth, $labelWidth);
1218
        });
1219
1220
        $this->builder()->setWidth($fieldWidth, $labelWidth);
1221
1222
        return $this;
1223
    }
1224
1225
    /**
1226
     * Set view for form.
1227
     *
1228
     * @param string $view
1229
     *
1230
     * @return $this
1231
     */
1232
    public function setView($view)
1233
    {
1234
        $this->builder()->setView($view);
1235
1236
        return $this;
1237
    }
1238
1239
    /**
1240
     * Set title for form.
1241
     *
1242
     * @param string $title
1243
     *
1244
     * @return $this
1245
     */
1246
    public function setTitle($title = '')
1247
    {
1248
        $this->builder()->setTitle($title);
1249
1250
        return $this;
1251
    }
1252
1253
    /**
1254
     * Add a row in form.
1255
     *
1256
     * @param Closure $callback
1257
     *
1258
     * @return $this
1259
     */
1260
    public function row(Closure $callback)
1261
    {
1262
        $this->rows[] = new Row($callback, $this);
1263
1264
        return $this;
1265
    }
1266
1267
    /**
1268
     * Tools setting for form.
1269
     *
1270
     * @param Closure $callback
1271
     */
1272
    public function tools(Closure $callback)
1273
    {
1274
        $callback->call($this, $this->builder->getTools());
1275
    }
1276
1277
    /**
1278
     * Disable form submit.
1279
     *
1280
     * @return $this
1281
     *
1282
     * @deprecated
1283
     */
1284
    public function disableSubmit()
1285
    {
1286
        $this->builder()->getFooter()->disableSubmit();
1287
1288
        return $this;
1289
    }
1290
1291
    /**
1292
     * Disable form reset.
1293
     *
1294
     * @return $this
1295
     *
1296
     * @deprecated
1297
     */
1298
    public function disableReset()
1299
    {
1300
        $this->builder()->getFooter()->disableReset();
1301
1302
        return $this;
1303
    }
1304
1305
    /**
1306
     * Disable View Checkbox on footer.
1307
     *
1308
     * @return $this
1309
     */
1310
    public function disableViewCheck()
1311
    {
1312
        $this->builder()->getFooter()->disableViewCheck();
1313
1314
        return $this;
1315
    }
1316
1317
    /**
1318
     * Disable Editing Checkbox on footer.
1319
     *
1320
     * @return $this
1321
     */
1322
    public function disableEditingCheck()
1323
    {
1324
        $this->builder()->getFooter()->disableEditingCheck();
1325
1326
        return $this;
1327
    }
1328
1329
    /**
1330
     * Disable Creating Checkbox on footer.
1331
     *
1332
     * @return $this
1333
     */
1334
    public function disableCreatingCheck()
1335
    {
1336
        $this->builder()->getFooter()->disableCreatingCheck();
1337
1338
        return $this;
1339
    }
1340
1341
    /**
1342
     * Footer setting for form.
1343
     *
1344
     * @param Closure $callback
1345
     */
1346
    public function footer(Closure $callback)
1347
    {
1348
        call_user_func($callback, $this->builder()->getFooter());
1349
    }
1350
1351
    /**
1352
     * Get current resource route url.
1353
     *
1354
     * @param int $slice
1355
     *
1356
     * @return string
1357
     */
1358
    public function resource($slice = -2)
1359
    {
1360
        $segments = explode('/', trim(app('request')->getUri(), '/'));
1361
1362
        if ($slice != 0) {
1363
            $segments = array_slice($segments, 0, $slice);
1364
        }
1365
1366
        return implode('/', $segments);
1367
    }
1368
1369
    /**
1370
     * Render the form contents.
1371
     *
1372
     * @return string
1373
     */
1374
    public function render()
1375
    {
1376
        try {
1377
            return $this->builder->render();
1378
        } catch (\Exception $e) {
1379
            return Handler::renderException($e);
1380
        }
1381
    }
1382
1383
    /**
1384
     * Get or set input data.
1385
     *
1386
     * @param string $key
1387
     * @param null   $value
1388
     *
1389
     * @return array|mixed
1390
     */
1391
    public function input($key, $value = null)
1392
    {
1393
        if (is_null($value)) {
1394
            return array_get($this->inputs, $key);
1395
        }
1396
1397
        return array_set($this->inputs, $key, $value);
1398
    }
1399
1400
    /**
1401
     * Register builtin fields.
1402
     *
1403
     * @return void
1404
     */
1405
    public static function registerBuiltinFields()
1406
    {
1407
        $map = [
1408
            'button'         => Field\Button::class,
1409
            'checkbox'       => Field\Checkbox::class,
1410
            'color'          => Field\Color::class,
1411
            'currency'       => Field\Currency::class,
1412
            'date'           => Field\Date::class,
1413
            'dateRange'      => Field\DateRange::class,
1414
            'datetime'       => Field\Datetime::class,
1415
            'dateTimeRange'  => Field\DatetimeRange::class,
1416
            'datetimeRange'  => Field\DatetimeRange::class,
1417
            'decimal'        => Field\Decimal::class,
1418
            'display'        => Field\Display::class,
1419
            'divider'        => Field\Divide::class,
1420
            'divide'         => Field\Divide::class,
1421
            'embeds'         => Field\Embeds::class,
1422
            'editor'         => Field\Editor::class,
1423
            'email'          => Field\Email::class,
1424
            'file'           => Field\File::class,
1425
            'hasMany'        => Field\HasMany::class,
1426
            'hidden'         => Field\Hidden::class,
1427
            'id'             => Field\Id::class,
1428
            'image'          => Field\Image::class,
1429
            'ip'             => Field\Ip::class,
1430
            'map'            => Field\Map::class,
1431
            'mobile'         => Field\Mobile::class,
1432
            'month'          => Field\Month::class,
1433
            'multipleSelect' => Field\MultipleSelect::class,
1434
            'number'         => Field\Number::class,
1435
            'password'       => Field\Password::class,
1436
            'radio'          => Field\Radio::class,
1437
            'rate'           => Field\Rate::class,
1438
            'select'         => Field\Select::class,
1439
            'slider'         => Field\Slider::class,
1440
            'switch'         => Field\SwitchField::class,
1441
            'text'           => Field\Text::class,
1442
            'textarea'       => Field\Textarea::class,
1443
            'time'           => Field\Time::class,
1444
            'timeRange'      => Field\TimeRange::class,
1445
            'url'            => Field\Url::class,
1446
            'year'           => Field\Year::class,
1447
            'html'           => Field\Html::class,
1448
            'tags'           => Field\Tags::class,
1449
            'icon'           => Field\Icon::class,
1450
            'multipleFile'   => Field\MultipleFile::class,
1451
            'multipleImage'  => Field\MultipleImage::class,
1452
            'captcha'        => Field\Captcha::class,
1453
            'listbox'        => Field\Listbox::class,
1454
        ];
1455
1456
        foreach ($map as $abstract => $class) {
1457
            static::extend($abstract, $class);
1458
        }
1459
    }
1460
1461
    /**
1462
     * Register custom field.
1463
     *
1464
     * @param string $abstract
1465
     * @param string $class
1466
     *
1467
     * @return void
1468
     */
1469
    public static function extend($abstract, $class)
1470
    {
1471
        static::$availableFields[$abstract] = $class;
1472
    }
1473
1474
    /**
1475
     * Set form field alias.
1476
     *
1477
     * @param string $field
1478
     * @param string $alias
1479
     *
1480
     * @return void
1481
     */
1482
    public static function alias($field, $alias)
1483
    {
1484
        static::$fieldAlias[$alias] = $field;
1485
    }
1486
1487
    /**
1488
     * Remove registered field.
1489
     *
1490
     * @param array|string $abstract
1491
     */
1492
    public static function forget($abstract)
1493
    {
1494
        array_forget(static::$availableFields, $abstract);
1495
    }
1496
1497
    /**
1498
     * Find field class.
1499
     *
1500
     * @param string $method
1501
     *
1502
     * @return bool|mixed
1503
     */
1504
    public static function findFieldClass($method)
1505
    {
1506
        // If alias exists.
1507
        if (isset(static::$fieldAlias[$method])) {
1508
            $method = static::$fieldAlias[$method];
1509
        }
1510
1511
        $class = array_get(static::$availableFields, $method);
1512
1513
        if (class_exists($class)) {
1514
            return $class;
1515
        }
1516
1517
        return false;
1518
    }
1519
1520
    /**
1521
     * Collect assets required by registered field.
1522
     *
1523
     * @return array
1524
     */
1525
    public static function collectFieldAssets()
1526
    {
1527
        if (!empty(static::$collectedAssets)) {
1528
            return static::$collectedAssets;
1529
        }
1530
1531
        $css = collect();
1532
        $js = collect();
1533
1534
        foreach (static::$availableFields as $field) {
1535
            if (!method_exists($field, 'getAssets')) {
1536
                continue;
1537
            }
1538
1539
            $assets = call_user_func([$field, 'getAssets']);
1540
1541
            $css->push(array_get($assets, 'css'));
1542
            $js->push(array_get($assets, 'js'));
1543
        }
1544
1545
        return static::$collectedAssets = [
1546
            'css' => $css->flatten()->unique()->filter()->toArray(),
1547
            'js'  => $js->flatten()->unique()->filter()->toArray(),
1548
        ];
1549
    }
1550
1551
    /**
1552
     * Getter.
1553
     *
1554
     * @param string $name
1555
     *
1556
     * @return array|mixed
1557
     */
1558
    public function __get($name)
1559
    {
1560
        return $this->input($name);
1561
    }
1562
1563
    /**
1564
     * Setter.
1565
     *
1566
     * @param string $name
1567
     * @param $value
1568
     */
1569
    public function __set($name, $value)
1570
    {
1571
        return array_set($this->inputs, $name, $value);
1572
    }
1573
1574
    /**
1575
     * Generate a Field object and add to form builder if Field exists.
1576
     *
1577
     * @param string $method
1578
     * @param array  $arguments
1579
     *
1580
     * @return Field
1581
     */
1582
    public function __call($method, $arguments)
1583
    {
1584
        if ($className = static::findFieldClass($method)) {
1585
            $column = array_get($arguments, 0, ''); //[0];
1586
1587
            $element = new $className($column, array_slice($arguments, 1));
1588
1589
            $this->pushField($element);
1590
1591
            return $element;
1592
        }
1593
1594
        admin_error('Error', "Field type [$method] does not exist.");
1595
1596
        return new Field\Nullable();
1597
    }
1598
}
1599