Completed
Push — master ( 7cba09...f5a087 )
by Song
02:15
created

Grid   F

Complexity

Total Complexity 105

Size/Duplication

Total Lines 1035
Duplicated Lines 4.44 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 0
Metric Value
dl 46
loc 1035
rs 1.46
c 0
b 0
f 0
wmc 105
lcom 1
cbo 20

54 Methods

Rating   Name   Duplication   Size   Complexity  
A setRelation() 0 6 1
A resource() 0 14 3
A handleGetMutatorColumn() 0 8 2
A buildRows() 0 10 2
A rows() 0 8 2
A __construct() 0 18 1
A init() 0 4 1
A callInitCallbacks() 0 10 3
A handleExportRequest() 0 23 5
A getExporter() 0 4 1
A option() 0 10 2
A getKeyName() 0 4 2
A column() 0 12 3
A columns() 0 18 6
A visibleColumns() 14 14 2
A visibleColumnNames() 14 14 2
A addColumn() 9 9 1
A addRelationColumn() 0 20 4
A addJsonColumn() 0 8 2
A prependColumn() 9 9 1
A model() 0 4 1
A paginate() 0 6 1
A paginator() 0 4 1
A disablePagination() 0 6 1
A showPagination() 0 4 1
A perPages() 0 4 1
A disableActions() 0 4 1
A actions() 0 12 4
A appendActionsColumn() 0 9 2
A disableRowSelector() 0 6 1
A prependRowSelectorColumn() 0 9 2
A build() 0 25 2
A exporter() 0 6 1
A getExportUrl() 0 10 2
A getCreateUrl() 0 13 3
A showExportBtn() 0 4 1
A disableExport() 0 4 1
A renderExportButton() 0 4 1
A disableCreation() 0 4 1
A disableCreateButton() 0 4 1
A showCreateBtn() 0 4 1
A renderCreateButton() 0 4 1
A disableColumnSelector() 0 4 1
A showColumnSelector() 0 4 1
A renderColumnSelector() 0 4 1
B handleRelationColumn() 0 33 10
A __call() 0 18 5
A registerColumnDisplayer() 0 26 2
A with() 0 6 1
A variables() 0 6 1
A setView() 0 8 2
A setTitle() 0 6 1
A setResource() 0 6 1
A render() 0 12 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Grid often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Grid, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Encore\Admin;
4
5
use Closure;
6
use Encore\Admin\Exception\Handler;
7
use Encore\Admin\Grid\Column;
8
use Encore\Admin\Grid\Concerns;
9
use Encore\Admin\Grid\Displayers;
10
use Encore\Admin\Grid\Exporter;
11
use Encore\Admin\Grid\Exporters\AbstractExporter;
12
use Encore\Admin\Grid\Model;
13
use Encore\Admin\Grid\Row;
14
use Encore\Admin\Grid\Tools;
15
use Illuminate\Database\Eloquent\Model as Eloquent;
16
use Illuminate\Database\Eloquent\Relations;
17
use Illuminate\Support\Collection;
18
use Illuminate\Support\Facades\Input;
19
use Illuminate\Support\Str;
20
use Jenssegers\Mongodb\Eloquent\Model as MongodbModel;
21
22
class Grid
23
{
24
    use Concerns\HasElementNames,
25
        Concerns\HasHeader,
26
        Concerns\HasFooter,
27
        Concerns\HasFilter,
28
        Concerns\HasTools,
29
        Concerns\HasTotalRow;
30
31
    /**
32
     * The grid data model instance.
33
     *
34
     * @var \Encore\Admin\Grid\Model
35
     */
36
    protected $model;
37
38
    /**
39
     * Collection of all grid columns.
40
     *
41
     * @var \Illuminate\Support\Collection
42
     */
43
    protected $columns;
44
45
    /**
46
     * Collection of all data rows.
47
     *
48
     * @var \Illuminate\Support\Collection
49
     */
50
    protected $rows;
51
52
    /**
53
     * Rows callable fucntion.
54
     *
55
     * @var \Closure
56
     */
57
    protected $rowsCallback;
58
59
    /**
60
     * All column names of the grid.
61
     *
62
     * @var array
63
     */
64
    public $columnNames = [];
65
66
    /**
67
     * Grid builder.
68
     *
69
     * @var \Closure
70
     */
71
    protected $builder;
72
73
    /**
74
     * Mark if the grid is builded.
75
     *
76
     * @var bool
77
     */
78
    protected $builded = false;
79
80
    /**
81
     * All variables in grid view.
82
     *
83
     * @var array
84
     */
85
    protected $variables = [];
86
87
    /**
88
     * Resource path of the grid.
89
     *
90
     * @var
91
     */
92
    protected $resourcePath;
93
94
    /**
95
     * Default primary key name.
96
     *
97
     * @var string
98
     */
99
    protected $keyName = 'id';
100
101
    /**
102
     * Export driver.
103
     *
104
     * @var string
105
     */
106
    protected $exporter;
107
108
    /**
109
     * View for grid to render.
110
     *
111
     * @var string
112
     */
113
    protected $view = 'admin::grid.table';
114
115
    /**
116
     * Per-page options.
117
     *
118
     * @var array
119
     */
120
    public $perPages = [10, 20, 30, 50, 100];
121
122
    /**
123
     * Default items count per-page.
124
     *
125
     * @var int
126
     */
127
    public $perPage = 20;
128
129
    /**
130
     * Callback for grid actions.
131
     *
132
     * @var Closure
133
     */
134
    protected $actionsCallback;
135
136
    /**
137
     * Actions column display class.
138
     *
139
     * @var string
140
     */
141
    protected $actionsClass = Displayers\Actions::class;
142
143
    /**
144
     * Options for grid.
145
     *
146
     * @var array
147
     */
148
    protected $options = [
149
        'show_pagination'        => true,
150
        'show_tools'             => true,
151
        'show_filter'            => true,
152
        'show_exporter'          => true,
153
        'show_actions'           => true,
154
        'show_row_selector'      => true,
155
        'show_create_btn'        => true,
156
        'show_column_selector'   => true,
157
    ];
158
159
    /**
160
     * @var string
161
     */
162
    public $tableID;
163
164
    /**
165
     * Initialization closure array.
166
     *
167
     * @var []Closure
168
     */
169
    protected static $initCallbacks = [];
170
171
    /**
172
     * Create a new grid instance.
173
     *
174
     * @param Eloquent $model
175
     * @param Closure  $builder
176
     */
177
    public function __construct(Eloquent $model, Closure $builder = null)
178
    {
179
        $this->keyName = $model->getKeyName();
180
        $this->model = new Model($model);
181
        $this->columns = new Collection();
182
        $this->rows = new Collection();
183
        $this->builder = $builder;
184
        $this->tableID = uniqid('grid-table');
185
186
        $this->model()->setGrid($this);
187
188
        $this->setupTools();
189
        $this->setupFilter();
190
191
        $this->handleExportRequest();
192
193
        $this->callInitCallbacks();
194
    }
195
196
    /**
197
     * Initialize with user pre-defined default disables and exporter, etc.
198
     *
199
     * @param Closure $callback
200
     */
201
    public static function init(Closure $callback = null)
202
    {
203
        static::$initCallbacks[] = $callback;
204
    }
205
206
    /**
207
     * Call the initialization closure array in sequence.
208
     */
209
    protected function callInitCallbacks()
210
    {
211
        if (empty(static::$initCallbacks)) {
212
            return;
213
        }
214
215
        foreach (static::$initCallbacks as $callback) {
216
            call_user_func($callback, $this);
217
        }
218
    }
219
220
    /**
221
     * Handle export request.
222
     *
223
     * @param bool $forceExport
224
     */
225
    protected function handleExportRequest($forceExport = false)
226
    {
227
        if (!$scope = request(Exporter::$queryName)) {
228
            return;
229
        }
230
231
        // clear output buffer.
232
        if (ob_get_length()) {
233
            ob_end_clean();
234
        }
235
236
        $this->model()->usePaginate(false);
237
238
        if ($this->builder) {
239
            call_user_func($this->builder, $this);
240
241
            $this->getExporter($scope)->export();
242
        }
243
244
        if ($forceExport) {
245
            $this->getExporter($scope)->export();
246
        }
247
    }
248
249
    /**
250
     * @param string $scope
251
     *
252
     * @return AbstractExporter
253
     */
254
    protected function getExporter($scope)
255
    {
256
        return (new Exporter($this))->resolve($this->exporter)->withScope($scope);
257
    }
258
259
    /**
260
     * Get or set option for grid.
261
     *
262
     * @param string $key
263
     * @param mixed  $value
264
     *
265
     * @return $this|mixed
266
     */
267
    public function option($key, $value = null)
268
    {
269
        if (is_null($value)) {
270
            return $this->options[$key];
271
        }
272
273
        $this->options[$key] = $value;
274
275
        return $this;
276
    }
277
278
    /**
279
     * Get primary key name of model.
280
     *
281
     * @return string
282
     */
283
    public function getKeyName()
284
    {
285
        return $this->keyName ?: 'id';
286
    }
287
288
    /**
289
     * Add a column to Grid.
290
     *
291
     * @param string $name
292
     * @param string $label
293
     *
294
     * @return Column
295
     */
296
    public function column($name, $label = '')
297
    {
298
        if (Str::contains($name, '.')) {
299
            return $this->addRelationColumn($name, $label);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->addRelationColumn($name, $label); of type Encore\Admin\Grid|Encore\Admin\Grid\Column adds the type Encore\Admin\Grid to the return on line 299 which is incompatible with the return type documented by Encore\Admin\Grid::column of type Encore\Admin\Grid\Column.
Loading history...
300
        }
301
302
        if (Str::contains($name, '->')) {
303
            return $this->addJsonColumn($name, $label);
304
        }
305
306
        return $this->__call($name, [$label]);
307
    }
308
309
    /**
310
     * Batch add column to grid.
311
     *
312
     * @example
313
     * 1.$grid->columns(['name' => 'Name', 'email' => 'Email' ...]);
314
     * 2.$grid->columns('name', 'email' ...)
315
     *
316
     * @param array $columns
317
     *
318
     * @return Collection|null
319
     */
320
    public function columns($columns = [])
321
    {
322
        if (func_num_args() == 0) {
323
            return $this->columns;
324
        }
325
326
        if (func_num_args() == 1 && is_array($columns)) {
327
            foreach ($columns as $column => $label) {
328
                $this->column($column, $label);
329
            }
330
331
            return;
332
        }
333
334
        foreach (func_get_args() as $column) {
335
            $this->column($column);
336
        }
337
    }
338
339
    /**
340
     * Get all visible column instances.
341
     *
342
     * @return Collection|static
343
     */
344 View Code Duplication
    public function visibleColumns()
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...
345
    {
346
        $visible = array_filter(explode(',', request(Tools\ColumnSelector::SELECT_COLUMN_NAME)));
347
348
        if (empty($visible)) {
349
            return $this->columns;
350
        }
351
352
        array_push($visible, '__row_selector__', '__actions__');
353
354
        return $this->columns->filter(function (Column $column) use ($visible) {
355
            return in_array($column->getName(), $visible);
356
        });
357
    }
358
359
    /**
360
     * Get all visible column names.
361
     *
362
     * @return array|static
363
     */
364 View Code Duplication
    public function visibleColumnNames()
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...
365
    {
366
        $visible = array_filter(explode(',', request(Tools\ColumnSelector::SELECT_COLUMN_NAME)));
367
368
        if (empty($visible)) {
369
            return $this->columnNames;
370
        }
371
372
        array_push($visible, '__row_selector__', '__actions__');
373
374
        return collect($this->columnNames)->filter(function ($column) use ($visible) {
375
            return in_array($column, $visible);
376
        });
377
    }
378
379
    /**
380
     * Add column to grid.
381
     *
382
     * @param string $column
383
     * @param string $label
384
     *
385
     * @return Column
386
     */
387 View Code Duplication
    protected function addColumn($column = '', $label = '')
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...
388
    {
389
        $column = new Column($column, $label);
390
        $column->setGrid($this);
391
392
        return tap($column, function ($value) {
393
            $this->columns->push($value);
394
        });
395
    }
396
397
    /**
398
     * Add a relation column to grid.
399
     *
400
     * @param string $name
401
     * @param string $label
402
     *
403
     * @return $this|bool|Column
404
     */
405
    protected function addRelationColumn($name, $label = '')
406
    {
407
        list($relation, $column) = explode('.', $name);
408
409
        $model = $this->model()->eloquent();
410
411
        if (!method_exists($model, $relation) || !$model->{$relation}() instanceof Relations\Relation) {
412
            $class = get_class($model);
413
414
            admin_error("Call to undefined relationship [{$relation}] on model [{$class}].");
415
416
            return $this;
417
        }
418
419
        $name = Str::snake($relation).'.'.$column;
420
421
        $this->model()->with($relation);
422
423
        return $this->addColumn($name, $label ?: ucfirst($column))->setRelation($relation, $column);
424
    }
425
426
    /**
427
     * Add a json type column to grid.
428
     *
429
     * @param string $name
430
     * @param string $label
431
     *
432
     * @return Column
433
     */
434
    protected function addJsonColumn($name, $label = '')
435
    {
436
        $column = substr($name, strrpos($name, '->') + 2);
437
438
        $name = str_replace('->', '.', $name);
439
440
        return $this->addColumn($name, $label ?: ucfirst($column));
441
    }
442
443
    /**
444
     * Prepend column to grid.
445
     *
446
     * @param string $column
447
     * @param string $label
448
     *
449
     * @return Column
450
     */
451 View Code Duplication
    protected function prependColumn($column = '', $label = '')
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...
452
    {
453
        $column = new Column($column, $label);
454
        $column->setGrid($this);
455
456
        return tap($column, function ($value) {
457
            $this->columns->prepend($value);
458
        });
459
    }
460
461
    /**
462
     * Get Grid model.
463
     *
464
     * @return Model
465
     */
466
    public function model()
467
    {
468
        return $this->model;
469
    }
470
471
    /**
472
     * Paginate the grid.
473
     *
474
     * @param int $perPage
475
     *
476
     * @return void
477
     */
478
    public function paginate($perPage = 20)
479
    {
480
        $this->perPage = $perPage;
481
482
        $this->model()->paginate($perPage);
0 ignored issues
show
Bug introduced by
The method paginate() does not exist on Encore\Admin\Grid\Model. Did you maybe mean usePaginate()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
483
    }
484
485
    /**
486
     * Get the grid paginator.
487
     *
488
     * @return mixed
489
     */
490
    public function paginator()
491
    {
492
        return new Tools\Paginator($this);
493
    }
494
495
    /**
496
     * Disable grid pagination.
497
     *
498
     * @return $this
499
     */
500
    public function disablePagination(bool $disable = true)
501
    {
502
        $this->model->usePaginate(!$disable);
503
504
        return $this->option('show_pagination', !$disable);
505
    }
506
507
    /**
508
     * If this grid use pagination.
509
     *
510
     * @return bool
511
     */
512
    public function showPagination()
513
    {
514
        return $this->option('show_pagination');
515
    }
516
517
    /**
518
     * Set per-page options.
519
     *
520
     * @param array $perPages
521
     */
522
    public function perPages(array $perPages)
523
    {
524
        $this->perPages = $perPages;
525
    }
526
527
    /**
528
     * Disable all actions.
529
     *
530
     * @return $this
531
     */
532
    public function disableActions(bool $disable = true)
533
    {
534
        return $this->option('show_actions', !$disable);
535
    }
536
537
    /**
538
     * Set grid action callback.
539
     *
540
     * @param Closure|string $actions
541
     *
542
     * @return $this
543
     */
544
    public function actions($actions)
545
    {
546
        if ($actions instanceof Closure) {
547
            $this->actionsCallback = $actions;
548
        }
549
550
        if (is_string($actions) && is_subclass_of($actions, Displayers\Actions::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Encore\Admin\Grid\Displayers\Actions::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
551
            $this->actionsClass = $actions;
552
        }
553
554
        return $this;
555
    }
556
557
    /**
558
     * Add `actions` column for grid.
559
     *
560
     * @return void
561
     */
562
    protected function appendActionsColumn()
563
    {
564
        if (!$this->option('show_actions')) {
565
            return;
566
        }
567
568
        $this->addColumn('__actions__', trans('admin.action'))
569
            ->displayUsing($this->actionsClass, [$this->actionsCallback]);
570
    }
571
572
    /**
573
     * Disable row selector.
574
     *
575
     * @return Grid|mixed
576
     */
577
    public function disableRowSelector(bool $disable = true)
578
    {
579
        $this->tools->disableBatchActions($disable);
580
581
        return $this->option('show_row_selector', !$disable);
582
    }
583
584
    /**
585
     * Prepend checkbox column for grid.
586
     *
587
     * @return void
588
     */
589
    protected function prependRowSelectorColumn()
590
    {
591
        if (!$this->option('show_row_selector')) {
592
            return;
593
        }
594
595
        $this->prependColumn(Column::SELECT_COLUMN_NAME, ' ')
596
            ->displayUsing(Displayers\RowSelector::class);
597
    }
598
599
    /**
600
     * Build the grid.
601
     *
602
     * @return void
603
     */
604
    public function build()
605
    {
606
        if ($this->builded) {
607
            return;
608
        }
609
610
        $collection = $this->processFilter(false);
611
612
        $data = $collection->toArray();
613
614
        $this->prependRowSelectorColumn();
615
        $this->appendActionsColumn();
616
617
        Column::setOriginalGridModels($collection);
618
619
        $this->columns->map(function (Column $column) use (&$data) {
620
            $data = $column->fill($data);
621
622
            $this->columnNames[] = $column->getName();
623
        });
624
625
        $this->buildRows($data);
626
627
        $this->builded = true;
628
    }
629
630
    /**
631
     * Build the grid rows.
632
     *
633
     * @param array $data
634
     *
635
     * @return void
636
     */
637
    protected function buildRows(array $data)
638
    {
639
        $this->rows = collect($data)->map(function ($model, $number) {
640
            return new Row($number, $model);
641
        });
642
643
        if ($this->rowsCallback) {
644
            $this->rows->map($this->rowsCallback);
645
        }
646
    }
647
648
    /**
649
     * Set grid row callback function.
650
     *
651
     * @param Closure $callable
652
     *
653
     * @return Collection|null
654
     */
655
    public function rows(Closure $callable = null)
656
    {
657
        if (is_null($callable)) {
658
            return $this->rows;
659
        }
660
661
        $this->rowsCallback = $callable;
662
    }
663
664
    /**
665
     * Set exporter driver for Grid to export.
666
     *
667
     * @param $exporter
668
     *
669
     * @return $this
670
     */
671
    public function exporter($exporter)
672
    {
673
        $this->exporter = $exporter;
674
675
        return $this;
676
    }
677
678
    /**
679
     * Get the export url.
680
     *
681
     * @param int  $scope
682
     * @param null $args
683
     *
684
     * @return string
685
     */
686
    public function getExportUrl($scope = 1, $args = null)
687
    {
688
        $input = array_merge(Input::all(), Exporter::formatExportQuery($scope, $args));
689
690
        if ($constraints = $this->model()->getConstraints()) {
691
            $input = array_merge($input, $constraints);
692
        }
693
694
        return $this->resource().'?'.http_build_query($input);
695
    }
696
697
    /**
698
     * Get create url.
699
     *
700
     * @return string
701
     */
702
    public function getCreateUrl()
703
    {
704
        $queryString = '';
705
706
        if ($constraints = $this->model()->getConstraints()) {
707
            $queryString = http_build_query($constraints);
708
        }
709
710
        return sprintf('%s/create%s',
711
            $this->resource(),
712
            $queryString ? ('?'.$queryString) : ''
713
        );
714
    }
715
716
    /**
717
     * If grid show export btn.
718
     *
719
     * @return bool
720
     */
721
    public function showExportBtn()
722
    {
723
        return $this->option('show_exporter');
724
    }
725
726
    /**
727
     * Disable export.
728
     *
729
     * @return $this
730
     */
731
    public function disableExport(bool $disable = true)
732
    {
733
        return $this->option('show_exporter', !$disable);
734
    }
735
736
    /**
737
     * Render export button.
738
     *
739
     * @return string
740
     */
741
    public function renderExportButton()
742
    {
743
        return (new Tools\ExportButton($this))->render();
744
    }
745
746
    /**
747
     * Alias for method `disableCreateButton`.
748
     *
749
     * @return $this
750
     *
751
     * @deprecated
752
     */
753
    public function disableCreation()
754
    {
755
        return $this->disableCreateButton();
756
    }
757
758
    /**
759
     * Remove create button on grid.
760
     *
761
     * @return $this
762
     */
763
    public function disableCreateButton(bool $disable = true)
764
    {
765
        return $this->option('show_create_btn', !$disable);
766
    }
767
768
    /**
769
     * If allow creation.
770
     *
771
     * @return bool
772
     */
773
    public function showCreateBtn()
774
    {
775
        return $this->option('show_create_btn');
776
    }
777
778
    /**
779
     * Render create button for grid.
780
     *
781
     * @return string
782
     */
783
    public function renderCreateButton()
784
    {
785
        return (new Tools\CreateButton($this))->render();
786
    }
787
788
    /**
789
     * Remove column selector on grid.
790
     *
791
     * @param bool $disable
792
     *
793
     * @return Grid|mixed
794
     */
795
    public function disableColumnSelector(bool $disable = true)
796
    {
797
        return $this->option('show_column_selector', !$disable);
798
    }
799
800
    /**
801
     * @return bool
802
     */
803
    public function showColumnSelector()
804
    {
805
        return $this->option('show_column_selector');
806
    }
807
808
    /**
809
     * @return string
810
     */
811
    public function renderColumnSelector()
812
    {
813
        return (new Grid\Tools\ColumnSelector($this))->render();
814
    }
815
816
    /**
817
     * Get current resource uri.
818
     *
819
     * @param string $path
820
     *
821
     * @return string
822
     */
823
    public function resource($path = null)
824
    {
825
        if (!empty($path)) {
826
            $this->resourcePath = $path;
827
828
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Encore\Admin\Grid) is incompatible with the return type documented by Encore\Admin\Grid::resource of type string.

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...
829
        }
830
831
        if (!empty($this->resourcePath)) {
832
            return $this->resourcePath;
833
        }
834
835
        return app('request')->getPathInfo();
836
    }
837
838
    /**
839
     * Handle get mutator column for grid.
840
     *
841
     * @param string $method
842
     * @param string $label
843
     *
844
     * @return bool|Column
845
     */
846
    protected function handleGetMutatorColumn($method, $label)
847
    {
848
        if ($this->model()->eloquent()->hasGetMutator($method)) {
849
            return $this->addColumn($method, $label);
850
        }
851
852
        return false;
853
    }
854
855
    /**
856
     * Handle relation column for grid.
857
     *
858
     * @param string $method
859
     * @param string $label
860
     *
861
     * @return bool|Column
862
     */
863
    protected function handleRelationColumn($method, $label)
864
    {
865
        $model = $this->model()->eloquent();
866
867
        if (!method_exists($model, $method)) {
868
            return false;
869
        }
870
871
        if (!($relation = $model->$method()) instanceof Relations\Relation) {
872
            return false;
873
        }
874
875
        if ($relation instanceof Relations\HasOne ||
876
            $relation instanceof Relations\BelongsTo ||
877
            $relation instanceof Relations\MorphOne
878
        ) {
879
            $this->model()->with($method);
880
881
            return $this->addColumn($method, $label)->setRelation(Str::snake($method));
882
        }
883
884
        if ($relation instanceof Relations\HasMany
885
            || $relation instanceof Relations\BelongsToMany
886
            || $relation instanceof Relations\MorphToMany
887
            || $relation instanceof Relations\HasManyThrough
888
        ) {
889
            $this->model()->with($method);
890
891
            return $this->addColumn(Str::snake($method), $label);
892
        }
893
894
        return false;
895
    }
896
897
    /**
898
     * Dynamically add columns to the grid view.
899
     *
900
     * @param $method
901
     * @param $arguments
902
     *
903
     * @return Column
904
     */
905
    public function __call($method, $arguments)
906
    {
907
        $label = isset($arguments[0]) ? $arguments[0] : ucfirst($method);
908
909
        if ($this->model()->eloquent() instanceof MongodbModel) {
0 ignored issues
show
Bug introduced by
The class Jenssegers\Mongodb\Eloquent\Model 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...
910
            return $this->addColumn($method, $label);
911
        }
912
913
        if ($column = $this->handleGetMutatorColumn($method, $label)) {
914
            return $column;
915
        }
916
917
        if ($column = $this->handleRelationColumn($method, $label)) {
918
            return $column;
919
        }
920
921
        return $this->addColumn($method, $label);
922
    }
923
924
    /**
925
     * Register column displayers.
926
     *
927
     * @return void.
0 ignored issues
show
Documentation introduced by
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
928
     */
929
    public static function registerColumnDisplayer()
930
    {
931
        $map = [
932
            'editable'    => Displayers\Editable::class,
933
            'switch'      => Displayers\SwitchDisplay::class,
934
            'switchGroup' => Displayers\SwitchGroup::class,
935
            'select'      => Displayers\Select::class,
936
            'image'       => Displayers\Image::class,
937
            'label'       => Displayers\Label::class,
938
            'button'      => Displayers\Button::class,
939
            'link'        => Displayers\Link::class,
940
            'badge'       => Displayers\Badge::class,
941
            'progressBar' => Displayers\ProgressBar::class,
942
            'radio'       => Displayers\Radio::class,
943
            'checkbox'    => Displayers\Checkbox::class,
944
            'orderable'   => Displayers\Orderable::class,
945
            'table'       => Displayers\Table::class,
946
            'expand'      => Displayers\Expand::class,
947
            'modal'       => Displayers\Modal::class,
948
            'gravatar'    => Displayers\Gravatar::class,
949
        ];
950
951
        foreach ($map as $abstract => $class) {
952
            Column::extend($abstract, $class);
953
        }
954
    }
955
956
    /**
957
     * Add variables to grid view.
958
     *
959
     * @param array $variables
960
     *
961
     * @return $this
962
     */
963
    public function with($variables = [])
964
    {
965
        $this->variables = $variables;
966
967
        return $this;
968
    }
969
970
    /**
971
     * Get all variables will used in grid view.
972
     *
973
     * @return array
974
     */
975
    protected function variables()
976
    {
977
        $this->variables['grid'] = $this;
978
979
        return $this->variables;
980
    }
981
982
    /**
983
     * Set a view to render.
984
     *
985
     * @param string $view
986
     * @param array  $variables
987
     */
988
    public function setView($view, $variables = [])
989
    {
990
        if (!empty($variables)) {
991
            $this->with($variables);
992
        }
993
994
        $this->view = $view;
995
    }
996
997
    /**
998
     * Set grid title.
999
     *
1000
     * @param string $title
1001
     *
1002
     * @return $this
1003
     */
1004
    public function setTitle($title)
1005
    {
1006
        $this->variables['title'] = $title;
1007
1008
        return $this;
1009
    }
1010
1011
    /**
1012
     * Set relation for grid.
1013
     *
1014
     * @param Relations\Relation $relation
1015
     *
1016
     * @return $this
1017
     */
1018
    public function setRelation(Relations\Relation $relation)
1019
    {
1020
        $this->model()->setRelation($relation);
1021
1022
        return $this;
1023
    }
1024
1025
    /**
1026
     * Set resource path for grid.
1027
     *
1028
     * @param string $path
1029
     *
1030
     * @return $this
1031
     */
1032
    public function setResource($path)
1033
    {
1034
        $this->resourcePath = $path;
1035
1036
        return $this;
1037
    }
1038
1039
    /**
1040
     * Get the string contents of the grid view.
1041
     *
1042
     * @return string
1043
     */
1044
    public function render()
1045
    {
1046
        $this->handleExportRequest(true);
1047
1048
        try {
1049
            $this->build();
1050
        } catch (\Exception $e) {
1051
            return Handler::renderException($e);
1052
        }
1053
1054
        return view($this->view, $this->variables())->render();
0 ignored issues
show
Bug introduced by
The method render does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1055
    }
1056
}
1057