Completed
Push — master ( 012343...f524a9 )
by jxlwqq
05:18 queued 02:33
created

Form::setRelationFieldSnakeAttributes()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 1
nop 0
dl 0
loc 22
rs 9.568
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\Concerns\HandleCascadeFields;
9
use Encore\Admin\Form\Concerns\HasFields;
10
use Encore\Admin\Form\Concerns\HasHooks;
11
use Encore\Admin\Form\Field;
12
use Encore\Admin\Form\Layout\Layout;
13
use Encore\Admin\Form\Row;
14
use Encore\Admin\Form\Tab;
15
use Encore\Admin\Traits\ShouldSnakeAttributes;
16
use Illuminate\Contracts\Support\Arrayable;
17
use Illuminate\Contracts\Support\Renderable;
18
use Illuminate\Database\Eloquent\Model;
19
use Illuminate\Database\Eloquent\Relations;
20
use Illuminate\Database\Eloquent\SoftDeletes;
21
use Illuminate\Http\Request;
22
use Illuminate\Support\Arr;
23
use Illuminate\Support\Facades\DB;
24
use Illuminate\Support\MessageBag;
25
use Illuminate\Support\Str;
26
use Illuminate\Validation\Validator;
27
use Spatie\EloquentSortable\Sortable;
28
use Symfony\Component\HttpFoundation\Response;
29
30
/**
31
 * Class Form.
32
 */
33
class Form implements Renderable
34
{
35
    use HasHooks;
36
    use HasFields;
37
    use HandleCascadeFields;
38
    use ShouldSnakeAttributes;
39
    /**
40
     * Remove flag in `has many` form.
41
     */
42
    const REMOVE_FLAG_NAME = '_remove_';
43
44
    /**
45
     * Eloquent model of the form.
46
     *
47
     * @var Model
48
     */
49
    protected $model;
50
51
    /**
52
     * @var \Illuminate\Validation\Validator
53
     */
54
    protected $validator;
55
56
    /**
57
     * @var Builder
58
     */
59
    protected $builder;
60
61
    /**
62
     * Data for save to current model from input.
63
     *
64
     * @var array
65
     */
66
    protected $updates = [];
67
68
    /**
69
     * Data for save to model's relations from input.
70
     *
71
     * @var array
72
     */
73
    protected $relations = [];
74
75
    /**
76
     * Input data.
77
     *
78
     * @var array
79
     */
80
    protected $inputs = [];
81
82
    /**
83
     * @var Layout
84
     */
85
    protected $layout;
86
87
    /**
88
     * Ignored saving fields.
89
     *
90
     * @var array
91
     */
92
    protected $ignored = [];
93
94
    /**
95
     * Collected field assets.
96
     *
97
     * @var array
98
     */
99
    protected static $collectedAssets = [];
100
101
    /**
102
     * @var Form\Tab
103
     */
104
    protected $tab = null;
105
106
    /**
107
     * Field rows in form.
108
     *
109
     * @var array
110
     */
111
    public $rows = [];
112
113
    /**
114
     * @var bool
115
     */
116
    protected $isSoftDeletes = false;
117
118
    /**
119
     * Create a new form instance.
120
     *
121
     * @param $model
122
     * @param \Closure $callback
123
     */
124
    public function __construct($model, Closure $callback = null)
125
    {
126
        $this->model = $model;
127
128
        $this->builder = new Builder($this);
129
130
        $this->initLayout();
131
132
        if ($callback instanceof Closure) {
133
            $callback($this);
134
        }
135
136
        $this->isSoftDeletes = in_array(SoftDeletes::class, class_uses_deep($this->model), true);
137
138
        $this->callInitCallbacks();
139
    }
140
141
    /**
142
     * @param Field $field
143
     *
144
     * @return $this
145
     */
146
    public function pushField(Field $field): self
147
    {
148
        $field->setForm($this);
149
150
        $width = $this->builder->getWidth();
151
        $field->setWidth($width['field'], $width['label']);
152
153
        $this->fields()->push($field);
154
        $this->layout->addField($field);
155
156
        return $this;
157
    }
158
159
    /**
160
     * @return Model
161
     */
162
    public function model(): Model
163
    {
164
        return $this->model;
165
    }
166
167
    /**
168
     * @return Builder
169
     */
170
    public function builder(): Builder
171
    {
172
        return $this->builder;
173
    }
174
175
    /**
176
     * @return \Illuminate\Support\Collection
177
     */
178
    public function fields()
179
    {
180
        return $this->builder()->fields();
181
    }
182
183
    /**
184
     * Generate a edit form.
185
     *
186
     * @param $id
187
     *
188
     * @return $this
189
     */
190
    public function edit($id): self
191
    {
192
        $this->builder->setMode(Builder::MODE_EDIT);
193
        $this->builder->setResourceId($id);
194
195
        $this->setRelationFieldSnakeAttributes();
196
197
        $this->setFieldValue($id);
198
199
        return $this;
200
    }
201
202
    /**
203
     * Use tab to split form.
204
     *
205
     * @param string  $title
206
     * @param Closure $content
207
     * @param bool    $active
208
     *
209
     * @return $this
210
     */
211
    public function tab($title, Closure $content, bool $active = false): self
212
    {
213
        $this->setTab()->append($title, $content, $active);
214
215
        return $this;
216
    }
217
218
    /**
219
     * Get Tab instance.
220
     *
221
     * @return Tab
222
     */
223
    public function getTab()
224
    {
225
        return $this->tab;
226
    }
227
228
    /**
229
     * Set Tab instance.
230
     *
231
     * @return Tab
232
     */
233
    public function setTab(): Tab
234
    {
235
        if ($this->tab === null) {
236
            $this->tab = new Tab($this);
237
        }
238
239
        return $this->tab;
240
    }
241
242
    /**
243
     * Destroy data entity and remove files.
244
     *
245
     * @param $id
246
     *
247
     * @return mixed
248
     */
249
    public function destroy($id)
250
    {
251
        try {
252
            if (($ret = $this->callDeleting($id)) instanceof Response) {
253
                return $ret;
254
            }
255
256
            collect(explode(',', $id))->filter()->each(function ($id) {
257
                $builder = $this->model()->newQuery();
258
259
                if ($this->isSoftDeletes) {
260
                    $builder = $builder->withTrashed();
261
                }
262
263
                $model = $builder->with($this->getRelations())->findOrFail($id);
264
265
                if ($this->isSoftDeletes && $model->trashed()) {
266
                    $this->deleteFiles($model, true);
267
                    $model->forceDelete();
268
269
                    return;
270
                }
271
272
                $this->deleteFiles($model);
273
                $model->delete();
274
            });
275
276
            if (($ret = $this->callDeleted()) instanceof Response) {
277
                return $ret;
278
            }
279
280
            $response = [
281
                'status'  => true,
282
                'message' => trans('admin.delete_succeeded'),
283
            ];
284
        } catch (\Exception $exception) {
285
            $response = [
286
                'status'  => false,
287
                'message' => $exception->getMessage() ?: trans('admin.delete_failed'),
288
            ];
289
        }
290
291
        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...
292
    }
293
294
    /**
295
     * Remove files in record.
296
     *
297
     * @param Model $model
298
     * @param bool  $forceDelete
299
     */
300
    protected function deleteFiles(Model $model, $forceDelete = false)
301
    {
302
        // If it's a soft delete, the files in the data will not be deleted.
303
        if (!$forceDelete && $this->isSoftDeletes) {
304
            return;
305
        }
306
307
        $data = $model->toArray();
308
309
        $this->fields()->filter(function ($field) {
310
            return $field instanceof Field\File;
311
        })->each(function (Field\File $file) use ($data) {
312
            $file->setOriginal($data);
313
314
            $file->destroy();
315
        });
316
    }
317
318
    /**
319
     * Store a new record.
320
     *
321
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\Http\JsonResponse
322
     */
323
    public function store()
324
    {
325
        $data = \request()->all();
326
327
        // Handle validation errors.
328
        if ($validationMessages = $this->validationMessages($data)) {
329
            return $this->responseValidationError($validationMessages);
330
        }
331
332
        if (($response = $this->prepare($data)) instanceof Response) {
333
            return $response;
334
        }
335
336 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...
337
            $inserts = $this->prepareInsert($this->updates);
338
339
            foreach ($inserts as $column => $value) {
340
                $this->model->setAttribute($column, $value);
341
            }
342
343
            $this->model->save();
344
345
            $this->updateRelation($this->relations);
346
        });
347
348
        if (($response = $this->callSaved()) instanceof Response) {
349
            return $response;
350
        }
351
352
        if ($response = $this->ajaxResponse(trans('admin.save_succeeded'))) {
353
            return $response;
354
        }
355
356
        return $this->redirectAfterStore();
357
    }
358
359
    /**
360
     * @param MessageBag $message
361
     *
362
     * @return $this|\Illuminate\Http\JsonResponse
363
     */
364
    protected function responseValidationError(MessageBag $message)
365
    {
366
        if (\request()->ajax() && !\request()->pjax()) {
367
            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...
368
                'status'     => false,
369
                'validation' => $message,
370
                'message'    => $message->first(),
371
            ]);
372
        }
373
374
        return back()->withInput()->withErrors($message);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return back()->withInput()->withErrors($message); (Illuminate\Http\RedirectResponse) is incompatible with the return type documented by Encore\Admin\Form::responseValidationError of type Encore\Admin\Form|Illuminate\Http\JsonResponse.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
375
    }
376
377
    /**
378
     * Get ajax response.
379
     *
380
     * @param string $message
381
     *
382
     * @return bool|\Illuminate\Http\JsonResponse
383
     */
384
    protected function ajaxResponse($message)
385
    {
386
        $request = \request();
387
388
        // ajax but not pjax
389
        if ($request->ajax() && !$request->pjax()) {
390
            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...
391
                'status'    => true,
392
                'message'   => $message,
393
                'display'   => $this->applayFieldDisplay(),
394
            ]);
395
        }
396
397
        return false;
398
    }
399
400
    /**
401
     * @return array
402
     */
403
    protected function applayFieldDisplay()
404
    {
405
        $editable = [];
406
407
        /** @var Field $field */
408
        foreach ($this->fields() as $field) {
409
            if (!\request()->has($field->column())) {
410
                continue;
411
            }
412
413
            $newValue = $this->model->fresh()->getAttribute($field->column());
414
415
            if ($newValue instanceof Arrayable) {
416
                $newValue = $newValue->toArray();
417
            }
418
419
            if ($field instanceof Field\BelongsTo || $field instanceof Field\BelongsToMany) {
420
                $selectable = $field->getSelectable();
421
422
                if (method_exists($selectable, 'display')) {
423
                    $display = $selectable::display();
424
425
                    $editable[$field->column()] = $display->call($this->model, $newValue);
426
                }
427
            }
428
        }
429
430
        return $editable;
431
    }
432
433
    /**
434
     * Prepare input data for insert or update.
435
     *
436
     * @param array $data
437
     *
438
     * @return mixed
439
     */
440
    protected function prepare($data = [])
441
    {
442
        if (($response = $this->callSubmitted()) instanceof Response) {
443
            return $response;
444
        }
445
446
        $this->inputs = array_merge($this->removeIgnoredFields($data), $this->inputs);
447
448
        if (($response = $this->callSaving()) instanceof Response) {
449
            return $response;
450
        }
451
452
        $this->relations = $this->getRelationInputs($this->inputs);
453
454
        $this->updates = Arr::except($this->inputs, array_keys($this->relations));
455
    }
456
457
    /**
458
     * Remove ignored fields from input.
459
     *
460
     * @param array $input
461
     *
462
     * @return array
463
     */
464
    protected function removeIgnoredFields($input): array
465
    {
466
        Arr::forget($input, $this->ignored);
467
468
        return $input;
469
    }
470
471
    /**
472
     * Get inputs for relations.
473
     *
474
     * @param array $inputs
475
     *
476
     * @return array
477
     */
478
    protected function getRelationInputs($inputs = []): array
479
    {
480
        $relations = [];
481
482
        foreach ($inputs as $column => $value) {
483
            if ((method_exists($this->model, $column) ||
484
                method_exists($this->model, $column = Str::camel($column))) &&
485
                !method_exists(Model::class, $column)
486
            ) {
487
                $relation = call_user_func([$this->model, $column]);
488
489
                if ($relation instanceof Relations\Relation) {
490
                    $relations[$column] = $value;
491
                }
492
            }
493
        }
494
495
        return $relations;
496
    }
497
498
    /**
499
     * Handle update.
500
     *
501
     * @param int  $id
502
     * @param null $data
503
     *
504
     * @return bool|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|mixed|null|Response
505
     */
506
    public function update($id, $data = null)
507
    {
508
        $data = ($data) ?: request()->all();
509
510
        $isEditable = $this->isEditable($data);
511
512
        if (($data = $this->handleColumnUpdates($id, $data)) instanceof Response) {
513
            return $data;
514
        }
515
516
        /* @var Model $this ->model */
517
        $builder = $this->model();
518
519
        if ($this->isSoftDeletes) {
520
            $builder = $builder->withTrashed();
521
        }
522
523
        $this->model = $builder->with($this->getRelations())->findOrFail($id);
524
525
        $this->setFieldOriginalValue();
526
527
        // Handle validation errors.
528
        if ($validationMessages = $this->validationMessages($data)) {
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->handleColumnUpdates($id, $data) on line 512 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...
529
            if (!$isEditable) {
530
                return back()->withInput()->withErrors($validationMessages);
531
            }
532
533
            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...
Documentation introduced by
$validationMessages->getMessages() is of type array, but the function expects a object<Illuminate\Support\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
534
        }
535
536
        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 512 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...
537
            return $response;
538
        }
539
540 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...
541
            $updates = $this->prepareUpdate($this->updates);
542
543
            foreach ($updates as $column => $value) {
544
                /* @var Model $this ->model */
545
                $this->model->setAttribute($column, $value);
546
            }
547
548
            $this->model->save();
549
550
            $this->updateRelation($this->relations);
551
        });
552
553
        if (($result = $this->callSaved()) instanceof Response) {
554
            return $result;
555
        }
556
557
        if ($response = $this->ajaxResponse(trans('admin.update_succeeded'))) {
558
            return $response;
559
        }
560
561
        return $this->redirectAfterUpdate($id);
562
    }
563
564
    /**
565
     * Get RedirectResponse after store.
566
     *
567
     * @return \Illuminate\Http\RedirectResponse
568
     */
569
    protected function redirectAfterStore()
570
    {
571
        $resourcesPath = $this->resource(0);
572
573
        $key = $this->model->getKey();
574
575
        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 575 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterStore of type Illuminate\Http\RedirectResponse.
Loading history...
576
    }
577
578
    /**
579
     * Get RedirectResponse after update.
580
     *
581
     * @param mixed $key
582
     *
583
     * @return \Illuminate\Http\RedirectResponse
584
     */
585
    protected function redirectAfterUpdate($key)
586
    {
587
        $resourcesPath = $this->resource(-1);
588
589
        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 589 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterUpdate of type Illuminate\Http\RedirectResponse.
Loading history...
590
    }
591
592
    /**
593
     * Get RedirectResponse after data saving.
594
     *
595
     * @param string $resourcesPath
596
     * @param string $key
597
     *
598
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
599
     */
600
    protected function redirectAfterSaving($resourcesPath, $key)
601
    {
602
        if (request('after-save') == 1) {
603
            // continue editing
604
            $url = rtrim($resourcesPath, '/')."/{$key}/edit";
605
        } elseif (request('after-save') == 2) {
606
            // continue creating
607
            $url = rtrim($resourcesPath, '/').'/create';
608
        } elseif (request('after-save') == 3) {
609
            // view resource
610
            $url = rtrim($resourcesPath, '/')."/{$key}";
611
        } else {
612
            $url = request(Builder::PREVIOUS_URL_KEY) ?: $resourcesPath;
613
        }
614
615
        admin_toastr(trans('admin.save_succeeded'));
616
617
        return redirect($url);
618
    }
619
620
    /**
621
     * Check if request is from editable.
622
     *
623
     * @param array $input
624
     *
625
     * @return bool
626
     */
627
    protected function isEditable(array $input = []): bool
628
    {
629
        return array_key_exists('_editable', $input) || array_key_exists('_edit_inline', $input);
630
    }
631
632
    /**
633
     * Handle updates for single column.
634
     *
635
     * @param int   $id
636
     * @param array $data
637
     *
638
     * @return array|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|Response
639
     */
640
    protected function handleColumnUpdates($id, $data)
641
    {
642
        $data = $this->handleEditable($data);
643
644
        $data = $this->handleFileDelete($data);
645
646
        $data = $this->handleFileSort($data);
647
648
        if ($this->handleOrderable($id, $data)) {
649
            return response([
650
                'status'  => true,
651
                'message' => trans('admin.update_succeeded'),
652
            ]);
653
        }
654
655
        return $data;
656
    }
657
658
    /**
659
     * Handle editable update.
660
     *
661
     * @param array $input
662
     *
663
     * @return array
664
     */
665
    protected function handleEditable(array $input = []): array
666
    {
667
        if (array_key_exists('_editable', $input)) {
668
            $name = $input['name'];
669
            $value = $input['value'];
670
671
            Arr::forget($input, ['pk', 'value', 'name']);
672
            Arr::set($input, $name, $value);
673
        }
674
675
        return $input;
676
    }
677
678
    /**
679
     * @param array $input
680
     *
681
     * @return array
682
     */
683
    protected function handleFileDelete(array $input = []): array
684
    {
685
        if (array_key_exists(Field::FILE_DELETE_FLAG, $input)) {
686
            $input[Field::FILE_DELETE_FLAG] = $input['key'];
687
            unset($input['key']);
688
        }
689
690
        request()->replace($input);
691
692
        return $input;
693
    }
694
695
    /**
696
     * @param array $input
697
     *
698
     * @return array
699
     */
700
    protected function handleFileSort(array $input = []): array
701
    {
702
        if (!array_key_exists(Field::FILE_SORT_FLAG, $input)) {
703
            return $input;
704
        }
705
706
        $sorts = array_filter($input[Field::FILE_SORT_FLAG]);
707
708
        if (empty($sorts)) {
709
            return $input;
710
        }
711
712
        foreach ($sorts as $column => $order) {
713
            $input[$column] = $order;
714
        }
715
716
        request()->replace($input);
717
718
        return $input;
719
    }
720
721
    /**
722
     * Handle orderable update.
723
     *
724
     * @param int   $id
725
     * @param array $input
726
     *
727
     * @return bool
728
     */
729
    protected function handleOrderable($id, array $input = [])
730
    {
731
        if (array_key_exists('_orderable', $input)) {
732
            $model = $this->model->find($id);
733
734
            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...
735
                $input['_orderable'] == 1 ? $model->moveOrderUp() : $model->moveOrderDown();
736
737
                return true;
738
            }
739
        }
740
741
        return false;
742
    }
743
744
    /**
745
     * Update relation data.
746
     *
747
     * @param array $relationsData
748
     *
749
     * @return void
750
     */
751
    protected function updateRelation($relationsData)
752
    {
753
        foreach ($relationsData as $name => $values) {
754
            if (!method_exists($this->model, $name)) {
755
                continue;
756
            }
757
758
            $relation = $this->model->$name();
759
760
            $oneToOneRelation = $relation instanceof Relations\HasOne
761
                || $relation instanceof Relations\MorphOne
762
                || $relation instanceof Relations\BelongsTo;
763
764
            $prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation);
765
766
            if (empty($prepared)) {
767
                continue;
768
            }
769
770
            switch (true) {
771
                case $relation instanceof Relations\BelongsToMany:
772
                case $relation instanceof Relations\MorphToMany:
773
                    if (isset($prepared[$name])) {
774
                        $relation->sync($prepared[$name]);
775
                    }
776
                    break;
777
                case $relation instanceof Relations\HasOne:
778
                case $relation instanceof Relations\MorphOne:
779
                    $related = $this->model->getRelationValue($name) ?: $relation->getRelated();
780
781
                    foreach ($prepared[$name] as $column => $value) {
782
                        $related->setAttribute($column, $value);
783
                    }
784
785
                    // save child
786
                    $relation->save($related);
787
                    break;
788
                case $relation instanceof Relations\BelongsTo:
789
                case $relation instanceof Relations\MorphTo:
790
                    $related = $this->model->getRelationValue($name) ?: $relation->getRelated();
791
792
                    foreach ($prepared[$name] as $column => $value) {
793
                        $related->setAttribute($column, $value);
794
                    }
795
796
                    // save parent
797
                    $related->save();
798
799
                    // save child (self)
800
                    $relation->associate($related)->save();
801
                    break;
802
                case $relation instanceof Relations\HasMany:
803
                case $relation instanceof Relations\MorphMany:
804
                    foreach ($prepared[$name] as $related) {
805
                        /** @var Relations\HasOneOrMany $relation */
806
                        $relation = $this->model->$name();
807
808
                        $keyName = $relation->getRelated()->getKeyName();
809
810
                        /** @var Model $child */
811
                        $child = $relation->findOrNew(Arr::get($related, $keyName));
812
813
                        if (Arr::get($related, static::REMOVE_FLAG_NAME) == 1) {
814
                            $child->delete();
815
                            continue;
816
                        }
817
818
                        Arr::forget($related, static::REMOVE_FLAG_NAME);
819
820
                        $child->fill($related);
821
822
                        $child->save();
823
                    }
824
                    break;
825
            }
826
        }
827
    }
828
829
    /**
830
     * Prepare input data for update.
831
     *
832
     * @param array $updates
833
     * @param bool  $oneToOneRelation If column is one-to-one relation.
834
     *
835
     * @return array
836
     */
837
    protected function prepareUpdate(array $updates, $oneToOneRelation = false): array
838
    {
839
        $prepared = [];
840
841
        /** @var Field $field */
842
        foreach ($this->fields() as $field) {
843
            $columns = $field->column();
844
845
            // If column not in input array data, then continue.
846
            if (!Arr::has($updates, $columns)) {
847
                continue;
848
            }
849
850
            if ($this->isInvalidColumn($columns, $oneToOneRelation || $field->isJsonType)) {
851
                continue;
852
            }
853
854
            $value = $this->getDataByColumn($updates, $columns);
855
856
            $value = $field->prepare($value);
857
858 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...
859
                foreach ($columns as $name => $column) {
860
                    Arr::set($prepared, $column, $value[$name]);
861
                }
862
            } elseif (is_string($columns)) {
863
                Arr::set($prepared, $columns, $value);
864
            }
865
        }
866
867
        return $prepared;
868
    }
869
870
    /**
871
     * @param string|array $columns
872
     * @param bool         $containsDot
873
     *
874
     * @return bool
875
     */
876
    protected function isInvalidColumn($columns, $containsDot = false): bool
877
    {
878
        foreach ((array) $columns as $column) {
879
            if ((!$containsDot && Str::contains($column, '.')) ||
880
                ($containsDot && !Str::contains($column, '.'))) {
881
                return true;
882
            }
883
        }
884
885
        return false;
886
    }
887
888
    /**
889
     * Prepare input data for insert.
890
     *
891
     * @param $inserts
892
     *
893
     * @return array
894
     */
895
    protected function prepareInsert($inserts): array
896
    {
897
        if ($this->isHasOneRelation($inserts)) {
898
            $inserts = Arr::dot($inserts);
899
        }
900
901
        foreach ($inserts as $column => $value) {
902
            if (($field = $this->getFieldByColumn($column)) === null) {
903
                unset($inserts[$column]);
904
                continue;
905
            }
906
907
            $inserts[$column] = $field->prepare($value);
908
        }
909
910
        $prepared = [];
911
912
        foreach ($inserts as $key => $value) {
913
            Arr::set($prepared, $key, $value);
914
        }
915
916
        return $prepared;
917
    }
918
919
    /**
920
     * Is input data is has-one relation.
921
     *
922
     * @param array $inserts
923
     *
924
     * @return bool
925
     */
926
    protected function isHasOneRelation($inserts): bool
927
    {
928
        $first = current($inserts);
929
930
        if (!is_array($first)) {
931
            return false;
932
        }
933
934
        if (is_array(current($first))) {
935
            return false;
936
        }
937
938
        return Arr::isAssoc($first);
939
    }
940
941
    /**
942
     * Ignore fields to save.
943
     *
944
     * @param string|array $fields
945
     *
946
     * @return $this
947
     */
948
    public function ignore($fields): self
949
    {
950
        $this->ignored = array_merge($this->ignored, (array) $fields);
951
952
        return $this;
953
    }
954
955
    /**
956
     * @param array        $data
957
     * @param string|array $columns
958
     *
959
     * @return array|mixed
960
     */
961 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...
962
    {
963
        if (is_string($columns)) {
964
            return Arr::get($data, $columns);
965
        }
966
967
        if (is_array($columns)) {
968
            $value = [];
969
            foreach ($columns as $name => $column) {
970
                if (!Arr::has($data, $column)) {
971
                    continue;
972
                }
973
                $value[$name] = Arr::get($data, $column);
974
            }
975
976
            return $value;
977
        }
978
    }
979
980
    /**
981
     * Find field object by column.
982
     *
983
     * @param $column
984
     *
985
     * @return mixed
986
     */
987
    protected function getFieldByColumn($column)
988
    {
989
        return $this->fields()->first(
990
            function (Field $field) use ($column) {
991
                if (is_array($field->column())) {
992
                    return in_array($column, $field->column());
993
                }
994
995
                return $field->column() == $column;
996
            }
997
        );
998
    }
999
1000
    /**
1001
     * Set original data for each field.
1002
     *
1003
     * @return void
1004
     */
1005
    protected function setFieldOriginalValue()
1006
    {
1007
        $values = $this->model->toArray();
1008
1009
        $this->fields()->each(function (Field $field) use ($values) {
1010
            $field->setOriginal($values);
1011
        });
1012
    }
1013
1014
    /**
1015
     * Determine relational column needs to be snaked.
1016
     *
1017
     * @return void
1018
     */
1019
    protected function setRelationFieldSnakeAttributes()
1020
    {
1021
        $relations = $this->getRelations();
1022
1023
        $this->fields()->each(function (Field $field) use ($relations) {
1024
            if ($field->getSnakeAttributes()) {
1025
                return;
1026
            }
1027
1028
            $column = $field->column();
1029
1030
            $column = is_array($column) ? head($column) : $column;
1031
1032
            list($relation) = explode('.', $column);
1033
1034
            if (!in_array($relation, $relations)) {
1035
                return;
1036
            }
1037
1038
            $field->setSnakeAttributes($this->model::$snakeAttributes);
1039
        });
1040
    }
1041
1042
    /**
1043
     * Set all fields value in form.
1044
     *
1045
     * @param $id
1046
     *
1047
     * @return void
1048
     */
1049
    protected function setFieldValue($id)
1050
    {
1051
        $relations = $this->getRelations();
1052
1053
        $builder = $this->model();
1054
1055
        if ($this->isSoftDeletes) {
1056
            $builder = $builder->withTrashed();
1057
        }
1058
1059
        $this->model = $builder->with($relations)->findOrFail($id);
1060
1061
        $this->callEditing();
1062
1063
        $data = $this->model->toArray();
1064
1065
        $this->fields()->each(function (Field $field) use ($data) {
1066
            if (!in_array($field->column(), $this->ignored, true)) {
1067
                $field->fill($data);
1068
            }
1069
        });
1070
    }
1071
1072
    /**
1073
     * Add a fieldset to form.
1074
     *
1075
     * @param string  $title
1076
     * @param Closure $setCallback
1077
     *
1078
     * @return Field\Fieldset
1079
     */
1080 View Code Duplication
    public function fieldset(string $title, Closure $setCallback)
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...
1081
    {
1082
        $fieldset = new Field\Fieldset();
1083
1084
        $this->html($fieldset->start($title))->plain();
1085
1086
        $setCallback($this);
1087
1088
        $this->html($fieldset->end())->plain();
1089
1090
        return $fieldset;
1091
    }
1092
1093
    /**
1094
     * Get validation messages.
1095
     *
1096
     * @param array $input
1097
     *
1098
     * @return MessageBag|bool
1099
     */
1100
    public function validationMessages($input)
1101
    {
1102
        $failedValidators = [];
1103
1104
        /** @var Field $field */
1105 View Code Duplication
        foreach ($this->fields() as $field) {
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...
1106
            if (!$validator = $field->getValidator($input)) {
1107
                continue;
1108
            }
1109
1110
            if (($validator instanceof Validator) && !$validator->passes()) {
1111
                $failedValidators[] = $validator;
1112
            }
1113
        }
1114
1115
        $message = $this->mergeValidationMessages($failedValidators);
1116
1117
        return $message->any() ? $message : false;
1118
    }
1119
1120
    /**
1121
     * Merge validation messages from input validators.
1122
     *
1123
     * @param \Illuminate\Validation\Validator[] $validators
1124
     *
1125
     * @return MessageBag
1126
     */
1127
    protected function mergeValidationMessages($validators): MessageBag
1128
    {
1129
        $messageBag = new MessageBag();
1130
1131
        foreach ($validators as $validator) {
1132
            $messageBag = $messageBag->merge($validator->messages());
1133
        }
1134
1135
        return $messageBag;
1136
    }
1137
1138
    /**
1139
     * Get all relations of model from callable.
1140
     *
1141
     * @return array
1142
     */
1143
    public function getRelations(): array
1144
    {
1145
        $relations = $columns = [];
1146
1147
        /** @var Field $field */
1148
        foreach ($this->fields() as $field) {
1149
            $columns[] = $field->column();
1150
        }
1151
1152
        foreach (Arr::flatten($columns) as $column) {
0 ignored issues
show
Documentation introduced by
$columns is of type array, but the function expects a object<Illuminate\Support\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1153
            if (Str::contains($column, '.')) {
1154
                list($relation) = explode('.', $column);
1155
1156
                if (method_exists($this->model, $relation) &&
1157
                    !method_exists(Model::class, $relation) &&
1158
                    $this->model->$relation() instanceof Relations\Relation
1159
                ) {
1160
                    $relations[] = $relation;
1161
                }
1162
            } elseif (method_exists($this->model, $column) &&
1163
                !method_exists(Model::class, $column)
1164
            ) {
1165
                $relations[] = $column;
1166
            }
1167
        }
1168
1169
        return array_unique($relations);
1170
    }
1171
1172
    /**
1173
     * Set action for form.
1174
     *
1175
     * @param string $action
1176
     *
1177
     * @return $this
1178
     */
1179
    public function setAction($action): self
1180
    {
1181
        $this->builder()->setAction($action);
1182
1183
        return $this;
1184
    }
1185
1186
    /**
1187
     * Set field and label width in current form.
1188
     *
1189
     * @param int $fieldWidth
1190
     * @param int $labelWidth
1191
     *
1192
     * @return $this
1193
     */
1194
    public function setWidth($fieldWidth = 8, $labelWidth = 2): self
1195
    {
1196
        $this->fields()->each(function ($field) use ($fieldWidth, $labelWidth) {
1197
            /* @var Field $field  */
1198
            $field->setWidth($fieldWidth, $labelWidth);
1199
        });
1200
1201
        $this->builder()->setWidth($fieldWidth, $labelWidth);
1202
1203
        return $this;
1204
    }
1205
1206
    /**
1207
     * Set view for form.
1208
     *
1209
     * @param string $view
1210
     *
1211
     * @return $this
1212
     */
1213
    public function setView($view): self
1214
    {
1215
        $this->builder()->setView($view);
1216
1217
        return $this;
1218
    }
1219
1220
    /**
1221
     * Set title for form.
1222
     *
1223
     * @param string $title
1224
     *
1225
     * @return $this
1226
     */
1227
    public function setTitle($title = ''): self
1228
    {
1229
        $this->builder()->setTitle($title);
1230
1231
        return $this;
1232
    }
1233
1234
    /**
1235
     * Set a submit confirm.
1236
     *
1237
     * @param string $message
1238
     * @param string $on
1239
     *
1240
     * @return $this
1241
     */
1242
    public function confirm(string $message, $on = null)
1243
    {
1244
        if ($on && !in_array($on, ['create', 'edit'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $on of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1245
            throw new \InvalidArgumentException("The second paramater `\$on` must be one of ['create', 'edit']");
1246
        }
1247
1248
        if ($on == 'create' && !$this->isCreating()) {
1249
            return;
1250
        }
1251
1252
        if ($on == 'edit' && !$this->isEditing()) {
1253
            return;
1254
        }
1255
1256
        $this->builder()->confirm($message);
1257
1258
        return $this;
1259
    }
1260
1261
    /**
1262
     * Add a row in form.
1263
     *
1264
     * @param Closure $callback
1265
     *
1266
     * @return $this
1267
     */
1268
    public function row(Closure $callback): self
1269
    {
1270
        $this->rows[] = new Row($callback, $this);
1271
1272
        return $this;
1273
    }
1274
1275
    /**
1276
     * Tools setting for form.
1277
     *
1278
     * @param Closure $callback
1279
     */
1280
    public function tools(Closure $callback)
1281
    {
1282
        $callback->call($this, $this->builder->getTools());
1283
    }
1284
1285
    /**
1286
     * @param Closure|null $callback
1287
     *
1288
     * @return Form\Tools
1289
     */
1290 View Code Duplication
    public function header(Closure $callback = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1291
    {
1292
        if (func_num_args() === 0) {
1293
            return $this->builder->getTools();
1294
        }
1295
1296
        $callback->call($this, $this->builder->getTools());
0 ignored issues
show
Bug introduced by
It seems like $callback is not always an object, but can also be of type null. Maybe add an additional type check?

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

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
1297
    }
1298
1299
    /**
1300
     * Indicates if current form page is creating.
1301
     *
1302
     * @return bool
1303
     */
1304
    public function isCreating(): bool
1305
    {
1306
        return Str::endsWith(\request()->route()->getName(), ['.create', '.store']);
1307
    }
1308
1309
    /**
1310
     * Indicates if current form page is editing.
1311
     *
1312
     * @return bool
1313
     */
1314
    public function isEditing(): bool
1315
    {
1316
        return Str::endsWith(\request()->route()->getName(), ['.edit', '.update']);
1317
    }
1318
1319
    /**
1320
     * Disable form submit.
1321
     *
1322
     * @param bool $disable
1323
     *
1324
     * @return $this
1325
     *
1326
     * @deprecated
1327
     */
1328
    public function disableSubmit(bool $disable = true): self
1329
    {
1330
        $this->builder()->getFooter()->disableSubmit($disable);
1331
1332
        return $this;
1333
    }
1334
1335
    /**
1336
     * Disable form reset.
1337
     *
1338
     * @param bool $disable
1339
     *
1340
     * @return $this
1341
     *
1342
     * @deprecated
1343
     */
1344
    public function disableReset(bool $disable = true): self
1345
    {
1346
        $this->builder()->getFooter()->disableReset($disable);
1347
1348
        return $this;
1349
    }
1350
1351
    /**
1352
     * Disable View Checkbox on footer.
1353
     *
1354
     * @param bool $disable
1355
     *
1356
     * @return $this
1357
     */
1358
    public function disableViewCheck(bool $disable = true): self
1359
    {
1360
        $this->builder()->getFooter()->disableViewCheck($disable);
1361
1362
        return $this;
1363
    }
1364
1365
    /**
1366
     * Disable Editing Checkbox on footer.
1367
     *
1368
     * @param bool $disable
1369
     *
1370
     * @return $this
1371
     */
1372
    public function disableEditingCheck(bool $disable = true): self
1373
    {
1374
        $this->builder()->getFooter()->disableEditingCheck($disable);
1375
1376
        return $this;
1377
    }
1378
1379
    /**
1380
     * Disable Creating Checkbox on footer.
1381
     *
1382
     * @param bool $disable
1383
     *
1384
     * @return $this
1385
     */
1386
    public function disableCreatingCheck(bool $disable = true): self
1387
    {
1388
        $this->builder()->getFooter()->disableCreatingCheck($disable);
1389
1390
        return $this;
1391
    }
1392
1393
    /**
1394
     * Footer setting for form.
1395
     *
1396
     * @param Closure $callback
1397
     *
1398
     * @return \Encore\Admin\Form\Footer
1399
     */
1400 View Code Duplication
    public function footer(Closure $callback = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1401
    {
1402
        if (func_num_args() === 0) {
1403
            return $this->builder()->getFooter();
1404
        }
1405
1406
        $callback($this->builder()->getFooter());
1407
    }
1408
1409
    /**
1410
     * Get current resource route url.
1411
     *
1412
     * @param int $slice
1413
     *
1414
     * @return string
1415
     */
1416
    public function resource($slice = -2): string
1417
    {
1418
        $segments = explode('/', trim(\request()->getUri(), '/'));
1419
1420
        if ($slice !== 0) {
1421
            $segments = array_slice($segments, 0, $slice);
1422
        }
1423
1424
        return implode('/', $segments);
1425
    }
1426
1427
    /**
1428
     * Render the form contents.
1429
     *
1430
     * @return string
1431
     */
1432
    public function render()
1433
    {
1434
        try {
1435
            return $this->builder->render();
1436
        } catch (\Exception $e) {
1437
            return Handler::renderException($e);
1438
        }
1439
    }
1440
1441
    /**
1442
     * Get or set input data.
1443
     *
1444
     * @param string $key
1445
     * @param null   $value
1446
     *
1447
     * @return array|mixed
1448
     */
1449
    public function input($key, $value = null)
1450
    {
1451
        if ($value === null) {
1452
            return Arr::get($this->inputs, $key);
1453
        }
1454
1455
        return Arr::set($this->inputs, $key, $value);
1456
    }
1457
1458
    /**
1459
     * Add a new layout column.
1460
     *
1461
     * @param int      $width
1462
     * @param \Closure $closure
1463
     *
1464
     * @return $this
1465
     */
1466 View Code Duplication
    public function column($width, \Closure $closure): self
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1467
    {
1468
        $width = $width < 1 ? round(12 * $width) : $width;
1469
1470
        $this->layout->column($width, $closure);
1471
1472
        return $this;
1473
    }
1474
1475
    /**
1476
     * Initialize filter layout.
1477
     */
1478
    protected function initLayout()
1479
    {
1480
        $this->layout = new Layout($this);
1481
    }
1482
1483
    /**
1484
     * Getter.
1485
     *
1486
     * @param string $name
1487
     *
1488
     * @return array|mixed
1489
     */
1490
    public function __get($name)
1491
    {
1492
        return $this->input($name);
1493
    }
1494
1495
    /**
1496
     * Setter.
1497
     *
1498
     * @param string $name
1499
     * @param mixed  $value
1500
     *
1501
     * @return array
1502
     */
1503
    public function __set($name, $value)
1504
    {
1505
        return Arr::set($this->inputs, $name, $value);
1506
    }
1507
1508
    /**
1509
     * __isset.
1510
     *
1511
     * @param string $name
1512
     *
1513
     * @return bool
1514
     */
1515
    public function __isset($name)
1516
    {
1517
        return isset($this->inputs[$name]);
1518
    }
1519
1520
    /**
1521
     * Generate a Field object and add to form builder if Field exists.
1522
     *
1523
     * @param string $method
1524
     * @param array  $arguments
1525
     *
1526
     * @return Field
1527
     */
1528
    public function __call($method, $arguments)
1529
    {
1530
        if ($className = static::findFieldClass($method)) {
1531
            $column = Arr::get($arguments, 0, ''); //[0];
1532
1533
            $element = new $className($column, array_slice($arguments, 1));
1534
1535
            $this->pushField($element);
1536
1537
            return $element;
1538
        }
1539
1540
        admin_error('Error', "Field type [$method] does not exist.");
1541
1542
        return new Field\Nullable();
1543
    }
1544
1545
    /**
1546
     * @return Layout
1547
     */
1548
    public function getLayout(): Layout
1549
    {
1550
        return $this->layout;
1551
    }
1552
}
1553