Completed
Push — master ( 6fbb78...0fea44 )
by Song
02:24
created

Grid::callInitCallbacks()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 0
dl 0
loc 10
rs 9.9332
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\Grid\Column;
8
use Encore\Admin\Grid\Displayers;
9
use Encore\Admin\Grid\Exporter;
10
use Encore\Admin\Grid\Exporters\AbstractExporter;
11
use Encore\Admin\Grid\Filter;
12
use Encore\Admin\Grid\HasElementNames;
13
use Encore\Admin\Grid\Model;
14
use Encore\Admin\Grid\Row;
15
use Encore\Admin\Grid\Tools;
16
use Illuminate\Database\Eloquent\Model as Eloquent;
17
use Illuminate\Database\Eloquent\Relations;
18
use Illuminate\Support\Collection;
19
use Illuminate\Support\Facades\Input;
20
use Jenssegers\Mongodb\Eloquent\Model as MongodbModel;
21
22
class Grid
23
{
24
    use HasElementNames;
25
26
    /**
27
     * The grid data model instance.
28
     *
29
     * @var \Encore\Admin\Grid\Model
30
     */
31
    protected $model;
32
33
    /**
34
     * Collection of all grid columns.
35
     *
36
     * @var \Illuminate\Support\Collection
37
     */
38
    protected $columns;
39
40
    /**
41
     * Collection of all data rows.
42
     *
43
     * @var \Illuminate\Support\Collection
44
     */
45
    protected $rows;
46
47
    /**
48
     * Rows callable fucntion.
49
     *
50
     * @var \Closure
51
     */
52
    protected $rowsCallback;
53
54
    /**
55
     * All column names of the grid.
56
     *
57
     * @var array
58
     */
59
    public $columnNames = [];
60
61
    /**
62
     * Grid builder.
63
     *
64
     * @var \Closure
65
     */
66
    protected $builder;
67
68
    /**
69
     * Mark if the grid is builded.
70
     *
71
     * @var bool
72
     */
73
    protected $builded = false;
74
75
    /**
76
     * All variables in grid view.
77
     *
78
     * @var array
79
     */
80
    protected $variables = [];
81
82
    /**
83
     * The grid Filter.
84
     *
85
     * @var \Encore\Admin\Grid\Filter
86
     */
87
    protected $filter;
88
89
    /**
90
     * Resource path of the grid.
91
     *
92
     * @var
93
     */
94
    protected $resourcePath;
95
96
    /**
97
     * Default primary key name.
98
     *
99
     * @var string
100
     */
101
    protected $keyName = 'id';
102
103
    /**
104
     * Export driver.
105
     *
106
     * @var string
107
     */
108
    protected $exporter;
109
110
    /**
111
     * View for grid to render.
112
     *
113
     * @var string
114
     */
115
    protected $view = 'admin::grid.table';
116
117
    /**
118
     * Per-page options.
119
     *
120
     * @var array
121
     */
122
    public $perPages = [10, 20, 30, 50, 100];
123
124
    /**
125
     * Default items count per-page.
126
     *
127
     * @var int
128
     */
129
    public $perPage = 20;
130
131
    /**
132
     * Header tools.
133
     *
134
     * @var Tools
135
     */
136
    public $tools;
137
138
    /**
139
     * Callback for grid actions.
140
     *
141
     * @var Closure
142
     */
143
    protected $actionsCallback;
144
145
    /**
146
     * Actions column display class.
147
     *
148
     * @var string
149
     */
150
    protected $actionsClass = Displayers\Actions::class;
151
152
    /**
153
     * Options for grid.
154
     *
155
     * @var array
156
     */
157
    protected $options = [
158
        'show_pagination'   => true,
159
        'show_tools'        => true,
160
        'show_filter'       => true,
161
        'show_exporter'     => true,
162
        'show_actions'      => true,
163
        'show_row_selector' => true,
164
        'show_create_btn'   => true,
165
    ];
166
167
    /**
168
     * @var Closure
169
     */
170
    protected $header;
171
172
    /**
173
     * @var Closure
174
     */
175
    protected $footer;
176
177
    /**
178
     * Initialization closure array.
179
     *
180
     * @var []Closure
181
     */
182
    protected static $initCallbacks = [];
183
184
    /**
185
     * Create a new grid instance.
186
     *
187
     * @param Eloquent $model
188
     * @param Closure  $builder
189
     */
190
    public function __construct(Eloquent $model, Closure $builder = null)
191
    {
192
        $this->keyName = $model->getKeyName();
193
        $this->model = new Model($model);
194
        $this->columns = new Collection();
195
        $this->rows = new Collection();
196
        $this->builder = $builder;
197
198
        $this->model()->setGrid($this);
199
200
        $this->setupTools();
201
        $this->setupFilter();
202
203
        $this->handleExportRequest();
204
205
        $this->callInitCallbacks();
206
    }
207
208
    /**
209
     * Initialize with user pre-defined default disables and exporter, etc.
210
     *
211
     * @param Closure $callback
212
     */
213
    public static function init(Closure $callback = null)
214
    {
215
        static::$initCallbacks[] = $callback;
216
    }
217
218
    /**
219
     * Call the initialization closure array in sequence.
220
     */
221
    protected function callInitCallbacks()
222
    {
223
        if (empty(static::$initCallbacks)) {
224
            return;
225
        }
226
227
        foreach (static::$initCallbacks as $callback) {
228
            call_user_func($callback, $this);
229
        }
230
    }
231
232
    /**
233
     * Setup grid tools.
234
     */
235
    public function setupTools()
236
    {
237
        $this->tools = new Tools($this);
238
    }
239
240
    /**
241
     * Setup grid filter.
242
     *
243
     * @return void
244
     */
245
    protected function setupFilter()
246
    {
247
        $this->filter = new Filter($this->model());
248
    }
249
250
    /**
251
     * Handle export request.
252
     *
253
     * @param bool $forceExport
254
     */
255
    protected function handleExportRequest($forceExport = false)
256
    {
257
        if (!$scope = request(Exporter::$queryName)) {
258
            return;
259
        }
260
261
        // clear output buffer.
262
        if (ob_get_length()) {
263
            ob_end_clean();
264
        }
265
266
        $this->model()->usePaginate(false);
267
268
        if ($this->builder) {
269
            call_user_func($this->builder, $this);
270
271
            $this->getExporter($scope)->export();
272
        }
273
274
        if ($forceExport) {
275
            $this->getExporter($scope)->export();
276
        }
277
    }
278
279
    /**
280
     * @param string $scope
281
     *
282
     * @return AbstractExporter
283
     */
284
    protected function getExporter($scope)
285
    {
286
        return (new Exporter($this))->resolve($this->exporter)->withScope($scope);
287
    }
288
289
    /**
290
     * Get or set option for grid.
291
     *
292
     * @param string $key
293
     * @param mixed  $value
294
     *
295
     * @return $this|mixed
296
     */
297
    public function option($key, $value = null)
298
    {
299
        if (is_null($value)) {
300
            return $this->options[$key];
301
        }
302
303
        $this->options[$key] = $value;
304
305
        return $this;
306
    }
307
308
    /**
309
     * Get primary key name of model.
310
     *
311
     * @return string
312
     */
313
    public function getKeyName()
314
    {
315
        return $this->keyName ?: 'id';
316
    }
317
318
    /**
319
     * Add column to Grid.
320
     *
321
     * @param string $name
322
     * @param string $label
323
     *
324
     * @return Column
325
     */
326
    public function column($name, $label = '')
327
    {
328
        $relationName = $relationColumn = '';
329
330
        if (strpos($name, '.') !== false) {
331
            list($relationName, $relationColumn) = explode('.', $name);
332
333
            $relation = $this->model()->eloquent()->$relationName();
334
335
            $label = empty($label) ? ucfirst($relationColumn) : $label;
336
337
            $name = snake_case($relationName).'.'.$relationColumn;
338
        }
339
340
        $column = $this->addColumn($name, $label);
341
342
        if (isset($relation) && $relation instanceof Relations\Relation) {
343
            $this->model()->with($relationName);
344
            $column->setRelation($relationName, $relationColumn);
345
        }
346
347
        return $column;
348
    }
349
350
    /**
351
     * Batch add column to grid.
352
     *
353
     * @example
354
     * 1.$grid->columns(['name' => 'Name', 'email' => 'Email' ...]);
355
     * 2.$grid->columns('name', 'email' ...)
356
     *
357
     * @param array $columns
358
     *
359
     * @return Collection|null
360
     */
361
    public function columns($columns = [])
362
    {
363
        if (func_num_args() == 0) {
364
            return $this->columns;
365
        }
366
367
        if (func_num_args() == 1 && is_array($columns)) {
368
            foreach ($columns as $column => $label) {
369
                $this->column($column, $label);
370
            }
371
372
            return;
373
        }
374
375
        foreach (func_get_args() as $column) {
376
            $this->column($column);
377
        }
378
    }
379
380
    /**
381
     * Get all visible column instances.
382
     *
383
     * @return Collection|static
384
     */
385 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...
386
    {
387
        $visible = array_filter(explode(',', request(Tools\ColumnSelector::SELECT_COLUMN_NAME)));
388
389
        if (empty($visible)) {
390
            return $this->columns;
391
        }
392
393
        array_push($visible, '__row_selector__', '__actions__');
394
395
        return $this->columns->filter(function (Column $column) use ($visible) {
396
            return in_array($column->getName(), $visible);
397
        });
398
    }
399
400
    /**
401
     * Get all visible column names.
402
     *
403
     * @return array|static
404
     */
405 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...
406
    {
407
        $visible = array_filter(explode(',', request(Tools\ColumnSelector::SELECT_COLUMN_NAME)));
408
409
        if (empty($visible)) {
410
            return $this->columnNames;
411
        }
412
413
        array_push($visible, '__row_selector__', '__actions__');
414
415
        return collect($this->columnNames)->filter(function ($column) use ($visible) {
416
            return in_array($column, $visible);
417
        });
418
    }
419
420
    /**
421
     * Add column to grid.
422
     *
423
     * @param string $column
424
     * @param string $label
425
     *
426
     * @return Column
427
     */
428 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...
429
    {
430
        $column = new Column($column, $label);
431
        $column->setGrid($this);
432
433
        return tap($column, function ($value) {
434
            $this->columns->push($value);
435
        });
436
    }
437
438
    /**
439
     * Prepend column to grid.
440
     *
441
     * @param string $column
442
     * @param string $label
443
     *
444
     * @return Column
445
     */
446 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...
447
    {
448
        $column = new Column($column, $label);
449
        $column->setGrid($this);
450
451
        return tap($column, function ($value) {
452
            $this->columns->prepend($value);
453
        });
454
    }
455
456
    /**
457
     * Get Grid model.
458
     *
459
     * @return Model
460
     */
461
    public function model()
462
    {
463
        return $this->model;
464
    }
465
466
    /**
467
     * Paginate the grid.
468
     *
469
     * @param int $perPage
470
     *
471
     * @return void
472
     */
473
    public function paginate($perPage = 20)
474
    {
475
        $this->perPage = $perPage;
476
477
        $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...
478
    }
479
480
    /**
481
     * Get the grid paginator.
482
     *
483
     * @return mixed
484
     */
485
    public function paginator()
486
    {
487
        return new Tools\Paginator($this);
488
    }
489
490
    /**
491
     * Disable grid pagination.
492
     *
493
     * @return $this
494
     */
495
    public function disablePagination(bool $disable = true)
496
    {
497
        $this->model->usePaginate(!$disable);
498
499
        return $this->option('show_pagination', !$disable);
500
    }
501
502
    /**
503
     * If this grid use pagination.
504
     *
505
     * @return bool
506
     */
507
    public function showPagination()
508
    {
509
        return $this->option('show_pagination');
510
    }
511
512
    /**
513
     * Set per-page options.
514
     *
515
     * @param array $perPages
516
     */
517
    public function perPages(array $perPages)
518
    {
519
        $this->perPages = $perPages;
520
    }
521
522
    /**
523
     * Disable all actions.
524
     *
525
     * @return $this
526
     */
527
    public function disableActions(bool $disable = true)
528
    {
529
        return $this->option('show_actions', !$disable);
530
    }
531
532
    /**
533
     * Set grid action callback.
534
     *
535
     * @param Closure|string $actions
536
     *
537
     * @return $this
538
     */
539
    public function actions($actions)
540
    {
541
        if ($actions instanceof Closure) {
542
            $this->actionsCallback = $actions;
543
        }
544
545
        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...
546
            $this->actionsClass = $actions;
547
        }
548
549
        return $this;
550
    }
551
552
    /**
553
     * Add `actions` column for grid.
554
     *
555
     * @return void
556
     */
557
    protected function appendActionsColumn()
558
    {
559
        if (!$this->option('show_actions')) {
560
            return;
561
        }
562
563
        $this->addColumn('__actions__', trans('admin.action'))
564
            ->displayUsing($this->actionsClass, [$this->actionsCallback]);
565
    }
566
567
    /**
568
     * Disable row selector.
569
     *
570
     * @return Grid|mixed
571
     */
572
    public function disableRowSelector(bool $disable = true)
573
    {
574
        $this->tools->disableBatchActions($disable);
575
576
        return $this->option('show_row_selector', !$disable);
577
    }
578
579
    /**
580
     * Prepend checkbox column for grid.
581
     *
582
     * @return void
583
     */
584
    protected function prependRowSelectorColumn()
585
    {
586
        if (!$this->option('show_row_selector')) {
587
            return;
588
        }
589
590
        $this->prependColumn(Column::SELECT_COLUMN_NAME, ' ')
591
            ->displayUsing(Displayers\RowSelector::class);
592
    }
593
594
    /**
595
     * Build the grid.
596
     *
597
     * @return void
598
     */
599
    public function build()
600
    {
601
        if ($this->builded) {
602
            return;
603
        }
604
605
        $collection = $this->processFilter(false);
606
607
        $data = $collection->toArray();
608
609
        $this->prependRowSelectorColumn();
610
        $this->appendActionsColumn();
611
612
        Column::setOriginalGridModels($collection);
613
614
        $this->columns->map(function (Column $column) use (&$data) {
615
            $data = $column->fill($data);
616
617
            $this->columnNames[] = $column->getName();
618
        });
619
620
        $this->buildRows($data);
621
622
        $this->builded = true;
623
    }
624
625
    /**
626
     * Disable header tools.
627
     *
628
     * @return $this
629
     */
630
    public function disableTools(bool $disable = true)
631
    {
632
        return $this->option('show_tools', !$disable);
633
    }
634
635
    /**
636
     * Disable grid filter.
637
     *
638
     * @return $this
639
     */
640
    public function disableFilter(bool $disable = true)
641
    {
642
        $this->tools->disableFilterButton($disable);
643
644
        return $this->option('show_filter', !$disable);
645
    }
646
647
    /**
648
     * Get filter of Grid.
649
     *
650
     * @return Filter
651
     */
652
    public function getFilter()
653
    {
654
        return $this->filter;
655
    }
656
657
    /**
658
     * Process the grid filter.
659
     *
660
     * @param bool $toArray
661
     *
662
     * @return array|Collection|mixed
663
     */
664
    public function processFilter($toArray = true)
665
    {
666
        if ($this->builder) {
667
            call_user_func($this->builder, $this);
668
        }
669
670
        return $this->filter->execute($toArray);
671
    }
672
673
    /**
674
     * Set the grid filter.
675
     *
676
     * @param Closure $callback
677
     */
678
    public function filter(Closure $callback)
679
    {
680
        call_user_func($callback, $this->filter);
681
    }
682
683
    /**
684
     * Render the grid filter.
685
     *
686
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|string
687
     */
688
    public function renderFilter()
689
    {
690
        if (!$this->option('show_filter')) {
691
            return '';
692
        }
693
694
        return $this->filter->render();
695
    }
696
697
    /**
698
     * Expand filter.
699
     *
700
     * @return $this
701
     */
702
    public function expandFilter()
703
    {
704
        $this->filter->expand();
705
706
        return $this;
707
    }
708
709
    /**
710
     * Build the grid rows.
711
     *
712
     * @param array $data
713
     *
714
     * @return void
715
     */
716
    protected function buildRows(array $data)
717
    {
718
        $this->rows = collect($data)->map(function ($model, $number) {
719
            return new Row($number, $model);
720
        });
721
722
        if ($this->rowsCallback) {
723
            $this->rows->map($this->rowsCallback);
724
        }
725
    }
726
727
    /**
728
     * Set grid row callback function.
729
     *
730
     * @param Closure $callable
731
     *
732
     * @return Collection|null
733
     */
734
    public function rows(Closure $callable = null)
735
    {
736
        if (is_null($callable)) {
737
            return $this->rows;
738
        }
739
740
        $this->rowsCallback = $callable;
741
    }
742
743
    /**
744
     * Setup grid tools.
745
     *
746
     * @param Closure $callback
747
     *
748
     * @return void
749
     */
750
    public function tools(Closure $callback)
751
    {
752
        call_user_func($callback, $this->tools);
753
    }
754
755
    /**
756
     * Render custom tools.
757
     *
758
     * @return string
759
     */
760
    public function renderHeaderTools()
761
    {
762
        return $this->tools->render();
763
    }
764
765
    /**
766
     * Set exporter driver for Grid to export.
767
     *
768
     * @param $exporter
769
     *
770
     * @return $this
771
     */
772
    public function exporter($exporter)
773
    {
774
        $this->exporter = $exporter;
775
776
        return $this;
777
    }
778
779
    /**
780
     * Get the export url.
781
     *
782
     * @param int  $scope
783
     * @param null $args
784
     *
785
     * @return string
786
     */
787
    public function getExportUrl($scope = 1, $args = null)
788
    {
789
        $input = array_merge(Input::all(), Exporter::formatExportQuery($scope, $args));
790
791
        if ($constraints = $this->model()->getConstraints()) {
792
            $input = array_merge($input, $constraints);
793
        }
794
795
        return $this->resource().'?'.http_build_query($input);
796
    }
797
798
    /**
799
     * Get create url.
800
     *
801
     * @return string
802
     */
803
    public function getCreateUrl()
804
    {
805
        $queryString = '';
806
807
        if ($constraints = $this->model()->getConstraints()) {
808
            $queryString = http_build_query($constraints);
809
        }
810
811
        return sprintf('%s/create%s',
812
            $this->resource(),
813
            $queryString ? ('?'.$queryString) : ''
814
        );
815
    }
816
817
    /**
818
     * If grid show header tools.
819
     *
820
     * @return bool
821
     */
822
    public function showTools()
823
    {
824
        return $this->option('show_tools');
825
    }
826
827
    /**
828
     * If grid show export btn.
829
     *
830
     * @return bool
831
     */
832
    public function showExportBtn()
833
    {
834
        return $this->option('show_exporter');
835
    }
836
837
    /**
838
     * Disable export.
839
     *
840
     * @return $this
841
     */
842
    public function disableExport(bool $disable = true)
843
    {
844
        return $this->option('show_exporter', !$disable);
845
    }
846
847
    /**
848
     * Render export button.
849
     *
850
     * @return string
851
     */
852
    public function renderExportButton()
853
    {
854
        return (new Tools\ExportButton($this))->render();
855
    }
856
857
    /**
858
     * Alias for method `disableCreateButton`.
859
     *
860
     * @return $this
861
     *
862
     * @deprecated
863
     */
864
    public function disableCreation()
865
    {
866
        return $this->disableCreateButton();
867
    }
868
869
    /**
870
     * Remove create button on grid.
871
     *
872
     * @return $this
873
     */
874
    public function disableCreateButton(bool $disable = true)
875
    {
876
        return $this->option('show_create_btn', !$disable);
877
    }
878
879
    /**
880
     * If allow creation.
881
     *
882
     * @return bool
883
     */
884
    public function showCreateBtn()
885
    {
886
        return $this->option('show_create_btn');
887
    }
888
889
    /**
890
     * Render create button for grid.
891
     *
892
     * @return string
893
     */
894
    public function renderCreateButton()
895
    {
896
        return (new Tools\CreateButton($this))->render();
897
    }
898
899
    /**
900
     * @return string
901
     */
902
    public function renderColumnSelector()
903
    {
904
        return (new Grid\Tools\ColumnSelector($this))->render();
905
    }
906
907
    /**
908
     * Set grid header.
909
     *
910
     * @param Closure|null $closure
911
     *
912
     * @return $this|Closure
913
     */
914
    public function header(Closure $closure = null)
915
    {
916
        if (!$closure) {
917
            return $this->header;
918
        }
919
920
        $this->header = $closure;
921
922
        return $this;
923
    }
924
925
    /**
926
     * Render grid header.
927
     *
928
     * @return Tools\Header|string
929
     */
930
    public function renderHeader()
931
    {
932
        if (!$this->header) {
933
            return '';
934
        }
935
936
        return (new Tools\Header($this))->render();
937
    }
938
939
    /**
940
     * Set grid footer.
941
     *
942
     * @param Closure|null $closure
943
     *
944
     * @return $this|Closure
945
     */
946
    public function footer(Closure $closure = null)
947
    {
948
        if (!$closure) {
949
            return $this->footer;
950
        }
951
952
        $this->footer = $closure;
953
954
        return $this;
955
    }
956
957
    /**
958
     * Render grid footer.
959
     *
960
     * @return Tools\Footer|string
961
     */
962
    public function renderFooter()
963
    {
964
        if (!$this->footer) {
965
            return '';
966
        }
967
968
        return (new Tools\Footer($this))->render();
969
    }
970
971
    /**
972
     * Get current resource uri.
973
     *
974
     * @param string $path
975
     *
976
     * @return string
977
     */
978
    public function resource($path = null)
979
    {
980
        if (!empty($path)) {
981
            $this->resourcePath = $path;
982
983
            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...
984
        }
985
986
        if (!empty($this->resourcePath)) {
987
            return $this->resourcePath;
988
        }
989
990
        return app('request')->getPathInfo();
991
    }
992
993
    /**
994
     * Handle get mutator column for grid.
995
     *
996
     * @param string $method
997
     * @param string $label
998
     *
999
     * @return bool|Column
1000
     */
1001
    protected function handleGetMutatorColumn($method, $label)
1002
    {
1003
        if ($this->model()->eloquent()->hasGetMutator($method)) {
1004
            return $this->addColumn($method, $label);
1005
        }
1006
1007
        return false;
1008
    }
1009
1010
    /**
1011
     * Handle relation column for grid.
1012
     *
1013
     * @param string $method
1014
     * @param string $label
1015
     *
1016
     * @return bool|Column
1017
     */
1018
    protected function handleRelationColumn($method, $label)
1019
    {
1020
        $model = $this->model()->eloquent();
1021
1022
        if (!method_exists($model, $method)) {
1023
            return false;
1024
        }
1025
1026
        if (!($relation = $model->$method()) instanceof Relations\Relation) {
1027
            return false;
1028
        }
1029
1030
        if ($relation instanceof Relations\HasOne ||
1031
            $relation instanceof Relations\BelongsTo ||
1032
            $relation instanceof Relations\MorphOne
1033
        ) {
1034
            $this->model()->with($method);
1035
1036
            return $this->addColumn($method, $label)->setRelation(snake_case($method));
1037
        }
1038
1039
        if ($relation instanceof Relations\HasMany
1040
            || $relation instanceof Relations\BelongsToMany
1041
            || $relation instanceof Relations\MorphToMany
1042
        ) {
1043
            $this->model()->with($method);
1044
1045
            return $this->addColumn(snake_case($method), $label);
1046
        }
1047
1048
        return false;
1049
    }
1050
1051
    /**
1052
     * Dynamically add columns to the grid view.
1053
     *
1054
     * @param $method
1055
     * @param $arguments
1056
     *
1057
     * @return Column
1058
     */
1059
    public function __call($method, $arguments)
1060
    {
1061
        $label = isset($arguments[0]) ? $arguments[0] : ucfirst($method);
1062
1063
        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...
1064
            return $this->addColumn($method, $label);
1065
        }
1066
1067
        if ($column = $this->handleGetMutatorColumn($method, $label)) {
1068
            return $column;
1069
        }
1070
1071
        if ($column = $this->handleRelationColumn($method, $label)) {
1072
            return $column;
1073
        }
1074
1075
        return $this->addColumn($method, $label);
1076
    }
1077
1078
    /**
1079
     * Register column displayers.
1080
     *
1081
     * @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...
1082
     */
1083
    public static function registerColumnDisplayer()
1084
    {
1085
        $map = [
1086
            'editable'    => Displayers\Editable::class,
1087
            'switch'      => Displayers\SwitchDisplay::class,
1088
            'switchGroup' => Displayers\SwitchGroup::class,
1089
            'select'      => Displayers\Select::class,
1090
            'image'       => Displayers\Image::class,
1091
            'label'       => Displayers\Label::class,
1092
            'button'      => Displayers\Button::class,
1093
            'link'        => Displayers\Link::class,
1094
            'badge'       => Displayers\Badge::class,
1095
            'progressBar' => Displayers\ProgressBar::class,
1096
            'radio'       => Displayers\Radio::class,
1097
            'checkbox'    => Displayers\Checkbox::class,
1098
            'orderable'   => Displayers\Orderable::class,
1099
            'table'       => Displayers\Table::class,
1100
            'expand'      => Displayers\Expand::class,
1101
            'modal'       => Displayers\Modal::class,
1102
        ];
1103
1104
        foreach ($map as $abstract => $class) {
1105
            Column::extend($abstract, $class);
1106
        }
1107
    }
1108
1109
    /**
1110
     * Add variables to grid view.
1111
     *
1112
     * @param array $variables
1113
     *
1114
     * @return $this
1115
     */
1116
    public function with($variables = [])
1117
    {
1118
        $this->variables = $variables;
1119
1120
        return $this;
1121
    }
1122
1123
    /**
1124
     * Get all variables will used in grid view.
1125
     *
1126
     * @return array
1127
     */
1128
    protected function variables()
1129
    {
1130
        $this->variables['grid'] = $this;
1131
1132
        return $this->variables;
1133
    }
1134
1135
    /**
1136
     * Set a view to render.
1137
     *
1138
     * @param string $view
1139
     * @param array  $variables
1140
     */
1141
    public function setView($view, $variables = [])
1142
    {
1143
        if (!empty($variables)) {
1144
            $this->with($variables);
1145
        }
1146
1147
        $this->view = $view;
1148
    }
1149
1150
    /**
1151
     * Set grid title.
1152
     *
1153
     * @param string $title
1154
     *
1155
     * @return $this
1156
     */
1157
    public function setTitle($title)
1158
    {
1159
        $this->variables['title'] = $title;
1160
1161
        return $this;
1162
    }
1163
1164
    /**
1165
     * Set relation for grid.
1166
     *
1167
     * @param Relations\Relation $relation
1168
     *
1169
     * @return $this
1170
     */
1171
    public function setRelation(Relations\Relation $relation)
1172
    {
1173
        $this->model()->setRelation($relation);
1174
1175
        return $this;
1176
    }
1177
1178
    /**
1179
     * Set resource path for grid.
1180
     *
1181
     * @param string $path
1182
     *
1183
     * @return $this
1184
     */
1185
    public function setResource($path)
1186
    {
1187
        $this->resourcePath = $path;
1188
1189
        return $this;
1190
    }
1191
1192
    /**
1193
     * Get the string contents of the grid view.
1194
     *
1195
     * @return string
1196
     */
1197
    public function render()
1198
    {
1199
        $this->handleExportRequest(true);
1200
1201
        try {
1202
            $this->build();
1203
        } catch (\Exception $e) {
1204
            return Handler::renderException($e);
1205
        }
1206
1207
        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...
1208
    }
1209
}
1210