Completed
Pull Request — master (#5306)
by rust17
03:06
created

Form::setRelationFieldSnakeAttributes()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 1
nop 0
dl 0
loc 23
rs 9.552
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
            {
1026
                return ;
1027
            }
1028
1029
            $column = $field->column();
1030
1031
            $column = is_array($column) ? head($column) : $column;
1032
1033
            list($relation) = explode('.', $column);
1034
1035
            if (!in_array($relation, $relations)) {
1036
                return ;
1037
            }
1038
1039
            $field->setSnakeAttributes($this->model::$snakeAttributes);
1040
        });
1041
    }
1042
1043
    /**
1044
     * Set all fields value in form.
1045
     *
1046
     * @param $id
1047
     *
1048
     * @return void
1049
     */
1050
    protected function setFieldValue($id)
1051
    {
1052
        $relations = $this->getRelations();
1053
1054
        $builder = $this->model();
1055
1056
        if ($this->isSoftDeletes) {
1057
            $builder = $builder->withTrashed();
1058
        }
1059
1060
        $this->model = $builder->with($relations)->findOrFail($id);
1061
1062
        $this->callEditing();
1063
1064
        $data = $this->model->toArray();
1065
1066
        $this->fields()->each(function (Field $field) use ($data) {
1067
            if (!in_array($field->column(), $this->ignored, true)) {
1068
                $field->fill($data);
1069
            }
1070
        });
1071
    }
1072
1073
    /**
1074
     * Add a fieldset to form.
1075
     *
1076
     * @param string  $title
1077
     * @param Closure $setCallback
1078
     *
1079
     * @return Field\Fieldset
1080
     */
1081 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...
1082
    {
1083
        $fieldset = new Field\Fieldset();
1084
1085
        $this->html($fieldset->start($title))->plain();
1086
1087
        $setCallback($this);
1088
1089
        $this->html($fieldset->end())->plain();
1090
1091
        return $fieldset;
1092
    }
1093
1094
    /**
1095
     * Get validation messages.
1096
     *
1097
     * @param array $input
1098
     *
1099
     * @return MessageBag|bool
1100
     */
1101
    public function validationMessages($input)
1102
    {
1103
        $failedValidators = [];
1104
1105
        /** @var Field $field */
1106 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...
1107
            if (!$validator = $field->getValidator($input)) {
1108
                continue;
1109
            }
1110
1111
            if (($validator instanceof Validator) && !$validator->passes()) {
1112
                $failedValidators[] = $validator;
1113
            }
1114
        }
1115
1116
        $message = $this->mergeValidationMessages($failedValidators);
1117
1118
        return $message->any() ? $message : false;
1119
    }
1120
1121
    /**
1122
     * Merge validation messages from input validators.
1123
     *
1124
     * @param \Illuminate\Validation\Validator[] $validators
1125
     *
1126
     * @return MessageBag
1127
     */
1128
    protected function mergeValidationMessages($validators): MessageBag
1129
    {
1130
        $messageBag = new MessageBag();
1131
1132
        foreach ($validators as $validator) {
1133
            $messageBag = $messageBag->merge($validator->messages());
1134
        }
1135
1136
        return $messageBag;
1137
    }
1138
1139
    /**
1140
     * Get all relations of model from callable.
1141
     *
1142
     * @return array
1143
     */
1144
    public function getRelations(): array
1145
    {
1146
        $relations = $columns = [];
1147
1148
        /** @var Field $field */
1149
        foreach ($this->fields() as $field) {
1150
            $columns[] = $field->column();
1151
        }
1152
1153
        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...
1154
            if (Str::contains($column, '.')) {
1155
                list($relation) = explode('.', $column);
1156
1157
                if (method_exists($this->model, $relation) &&
1158
                    !method_exists(Model::class, $relation) &&
1159
                    $this->model->$relation() instanceof Relations\Relation
1160
                ) {
1161
                    $relations[] = $relation;
1162
                }
1163
            } elseif (method_exists($this->model, $column) &&
1164
                !method_exists(Model::class, $column)
1165
            ) {
1166
                $relations[] = $column;
1167
            }
1168
        }
1169
1170
        return array_unique($relations);
1171
    }
1172
1173
    /**
1174
     * Set action for form.
1175
     *
1176
     * @param string $action
1177
     *
1178
     * @return $this
1179
     */
1180
    public function setAction($action): self
1181
    {
1182
        $this->builder()->setAction($action);
1183
1184
        return $this;
1185
    }
1186
1187
    /**
1188
     * Set field and label width in current form.
1189
     *
1190
     * @param int $fieldWidth
1191
     * @param int $labelWidth
1192
     *
1193
     * @return $this
1194
     */
1195
    public function setWidth($fieldWidth = 8, $labelWidth = 2): self
1196
    {
1197
        $this->fields()->each(function ($field) use ($fieldWidth, $labelWidth) {
1198
            /* @var Field $field  */
1199
            $field->setWidth($fieldWidth, $labelWidth);
1200
        });
1201
1202
        $this->builder()->setWidth($fieldWidth, $labelWidth);
1203
1204
        return $this;
1205
    }
1206
1207
    /**
1208
     * Set view for form.
1209
     *
1210
     * @param string $view
1211
     *
1212
     * @return $this
1213
     */
1214
    public function setView($view): self
1215
    {
1216
        $this->builder()->setView($view);
1217
1218
        return $this;
1219
    }
1220
1221
    /**
1222
     * Set title for form.
1223
     *
1224
     * @param string $title
1225
     *
1226
     * @return $this
1227
     */
1228
    public function setTitle($title = ''): self
1229
    {
1230
        $this->builder()->setTitle($title);
1231
1232
        return $this;
1233
    }
1234
1235
    /**
1236
     * Set a submit confirm.
1237
     *
1238
     * @param string $message
1239
     * @param string $on
1240
     *
1241
     * @return $this
1242
     */
1243
    public function confirm(string $message, $on = null)
1244
    {
1245
        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...
1246
            throw new \InvalidArgumentException("The second paramater `\$on` must be one of ['create', 'edit']");
1247
        }
1248
1249
        if ($on == 'create' && !$this->isCreating()) {
1250
            return;
1251
        }
1252
1253
        if ($on == 'edit' && !$this->isEditing()) {
1254
            return;
1255
        }
1256
1257
        $this->builder()->confirm($message);
1258
1259
        return $this;
1260
    }
1261
1262
    /**
1263
     * Add a row in form.
1264
     *
1265
     * @param Closure $callback
1266
     *
1267
     * @return $this
1268
     */
1269
    public function row(Closure $callback): self
1270
    {
1271
        $this->rows[] = new Row($callback, $this);
1272
1273
        return $this;
1274
    }
1275
1276
    /**
1277
     * Tools setting for form.
1278
     *
1279
     * @param Closure $callback
1280
     */
1281
    public function tools(Closure $callback)
1282
    {
1283
        $callback->call($this, $this->builder->getTools());
1284
    }
1285
1286
    /**
1287
     * @param Closure|null $callback
1288
     *
1289
     * @return Form\Tools
1290
     */
1291 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...
1292
    {
1293
        if (func_num_args() === 0) {
1294
            return $this->builder->getTools();
1295
        }
1296
1297
        $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...
1298
    }
1299
1300
    /**
1301
     * Indicates if current form page is creating.
1302
     *
1303
     * @return bool
1304
     */
1305
    public function isCreating(): bool
1306
    {
1307
        return Str::endsWith(\request()->route()->getName(), ['.create', '.store']);
1308
    }
1309
1310
    /**
1311
     * Indicates if current form page is editing.
1312
     *
1313
     * @return bool
1314
     */
1315
    public function isEditing(): bool
1316
    {
1317
        return Str::endsWith(\request()->route()->getName(), ['.edit', '.update']);
1318
    }
1319
1320
    /**
1321
     * Disable form submit.
1322
     *
1323
     * @param bool $disable
1324
     *
1325
     * @return $this
1326
     *
1327
     * @deprecated
1328
     */
1329
    public function disableSubmit(bool $disable = true): self
1330
    {
1331
        $this->builder()->getFooter()->disableSubmit($disable);
1332
1333
        return $this;
1334
    }
1335
1336
    /**
1337
     * Disable form reset.
1338
     *
1339
     * @param bool $disable
1340
     *
1341
     * @return $this
1342
     *
1343
     * @deprecated
1344
     */
1345
    public function disableReset(bool $disable = true): self
1346
    {
1347
        $this->builder()->getFooter()->disableReset($disable);
1348
1349
        return $this;
1350
    }
1351
1352
    /**
1353
     * Disable View Checkbox on footer.
1354
     *
1355
     * @param bool $disable
1356
     *
1357
     * @return $this
1358
     */
1359
    public function disableViewCheck(bool $disable = true): self
1360
    {
1361
        $this->builder()->getFooter()->disableViewCheck($disable);
1362
1363
        return $this;
1364
    }
1365
1366
    /**
1367
     * Disable Editing Checkbox on footer.
1368
     *
1369
     * @param bool $disable
1370
     *
1371
     * @return $this
1372
     */
1373
    public function disableEditingCheck(bool $disable = true): self
1374
    {
1375
        $this->builder()->getFooter()->disableEditingCheck($disable);
1376
1377
        return $this;
1378
    }
1379
1380
    /**
1381
     * Disable Creating Checkbox on footer.
1382
     *
1383
     * @param bool $disable
1384
     *
1385
     * @return $this
1386
     */
1387
    public function disableCreatingCheck(bool $disable = true): self
1388
    {
1389
        $this->builder()->getFooter()->disableCreatingCheck($disable);
1390
1391
        return $this;
1392
    }
1393
1394
    /**
1395
     * Footer setting for form.
1396
     *
1397
     * @param Closure $callback
1398
     *
1399
     * @return \Encore\Admin\Form\Footer
1400
     */
1401 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...
1402
    {
1403
        if (func_num_args() === 0) {
1404
            return $this->builder()->getFooter();
1405
        }
1406
1407
        $callback($this->builder()->getFooter());
1408
    }
1409
1410
    /**
1411
     * Get current resource route url.
1412
     *
1413
     * @param int $slice
1414
     *
1415
     * @return string
1416
     */
1417
    public function resource($slice = -2): string
1418
    {
1419
        $segments = explode('/', trim(\request()->getUri(), '/'));
1420
1421
        if ($slice !== 0) {
1422
            $segments = array_slice($segments, 0, $slice);
1423
        }
1424
1425
        return implode('/', $segments);
1426
    }
1427
1428
    /**
1429
     * Render the form contents.
1430
     *
1431
     * @return string
1432
     */
1433
    public function render()
1434
    {
1435
        try {
1436
            return $this->builder->render();
1437
        } catch (\Exception $e) {
1438
            return Handler::renderException($e);
1439
        }
1440
    }
1441
1442
    /**
1443
     * Get or set input data.
1444
     *
1445
     * @param string $key
1446
     * @param null   $value
1447
     *
1448
     * @return array|mixed
1449
     */
1450
    public function input($key, $value = null)
1451
    {
1452
        if ($value === null) {
1453
            return Arr::get($this->inputs, $key);
1454
        }
1455
1456
        return Arr::set($this->inputs, $key, $value);
1457
    }
1458
1459
    /**
1460
     * Add a new layout column.
1461
     *
1462
     * @param int      $width
1463
     * @param \Closure $closure
1464
     *
1465
     * @return $this
1466
     */
1467 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...
1468
    {
1469
        $width = $width < 1 ? round(12 * $width) : $width;
1470
1471
        $this->layout->column($width, $closure);
1472
1473
        return $this;
1474
    }
1475
1476
    /**
1477
     * Initialize filter layout.
1478
     */
1479
    protected function initLayout()
1480
    {
1481
        $this->layout = new Layout($this);
1482
    }
1483
1484
    /**
1485
     * Getter.
1486
     *
1487
     * @param string $name
1488
     *
1489
     * @return array|mixed
1490
     */
1491
    public function __get($name)
1492
    {
1493
        return $this->input($name);
1494
    }
1495
1496
    /**
1497
     * Setter.
1498
     *
1499
     * @param string $name
1500
     * @param mixed  $value
1501
     *
1502
     * @return array
1503
     */
1504
    public function __set($name, $value)
1505
    {
1506
        return Arr::set($this->inputs, $name, $value);
1507
    }
1508
1509
    /**
1510
     * __isset.
1511
     *
1512
     * @param string $name
1513
     *
1514
     * @return bool
1515
     */
1516
    public function __isset($name)
1517
    {
1518
        return isset($this->inputs[$name]);
1519
    }
1520
1521
    /**
1522
     * Generate a Field object and add to form builder if Field exists.
1523
     *
1524
     * @param string $method
1525
     * @param array  $arguments
1526
     *
1527
     * @return Field
1528
     */
1529
    public function __call($method, $arguments)
1530
    {
1531
        if ($className = static::findFieldClass($method)) {
1532
            $column = Arr::get($arguments, 0, ''); //[0];
1533
1534
            $element = new $className($column, array_slice($arguments, 1));
1535
1536
            $this->pushField($element);
1537
1538
            return $element;
1539
        }
1540
1541
        admin_error('Error', "Field type [$method] does not exist.");
1542
1543
        return new Field\Nullable();
1544
    }
1545
1546
    /**
1547
     * @return Layout
1548
     */
1549
    public function getLayout(): Layout
1550
    {
1551
        return $this->layout;
1552
    }
1553
}
1554