Completed
Push — master ( 024746...0b49f1 )
by Song
04:00
created

Grid::setRelation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
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\Actions;
9
use Encore\Admin\Grid\Displayers\RowSelector;
10
use Encore\Admin\Grid\Exporter;
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\BelongsTo;
18
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
19
use Illuminate\Database\Eloquent\Relations\HasMany;
20
use Illuminate\Database\Eloquent\Relations\HasOne;
21
use Illuminate\Database\Eloquent\Relations\MorphToMany;
22
use Illuminate\Database\Eloquent\Relations\Relation;
23
use Illuminate\Support\Collection;
24
use Illuminate\Support\Facades\Input;
25
use Illuminate\Support\Facades\Schema;
26
use Jenssegers\Mongodb\Eloquent\Model as MongodbModel;
27
28
class Grid
29
{
30
    use HasElementNames;
31
32
    /**
33
     * The grid data model instance.
34
     *
35
     * @var \Encore\Admin\Grid\Model
36
     */
37
    protected $model;
38
39
    /**
40
     * Collection of all grid columns.
41
     *
42
     * @var \Illuminate\Support\Collection
43
     */
44
    protected $columns;
45
46
    /**
47
     * Collection of table columns.
48
     *
49
     * @var \Illuminate\Support\Collection
50
     */
51
    protected $dbColumns;
52
53
    /**
54
     * Collection of all data rows.
55
     *
56
     * @var \Illuminate\Support\Collection
57
     */
58
    protected $rows;
59
60
    /**
61
     * Rows callable fucntion.
62
     *
63
     * @var \Closure
64
     */
65
    protected $rowsCallback;
66
67
    /**
68
     * All column names of the grid.
69
     *
70
     * @var array
71
     */
72
    public $columnNames = [];
73
74
    /**
75
     * Grid builder.
76
     *
77
     * @var \Closure
78
     */
79
    protected $builder;
80
81
    /**
82
     * Mark if the grid is builded.
83
     *
84
     * @var bool
85
     */
86
    protected $builded = false;
87
88
    /**
89
     * All variables in grid view.
90
     *
91
     * @var array
92
     */
93
    protected $variables = [];
94
95
    /**
96
     * The grid Filter.
97
     *
98
     * @var \Encore\Admin\Grid\Filter
99
     */
100
    protected $filter;
101
102
    /**
103
     * Resource path of the grid.
104
     *
105
     * @var
106
     */
107
    protected $resourcePath;
108
109
    /**
110
     * Default primary key name.
111
     *
112
     * @var string
113
     */
114
    protected $keyName = 'id';
115
116
    /**
117
     * Export driver.
118
     *
119
     * @var string
120
     */
121
    protected $exporter;
122
123
    /**
124
     * View for grid to render.
125
     *
126
     * @var string
127
     */
128
    protected $view = 'admin::grid.table';
129
130
    /**
131
     * Per-page options.
132
     *
133
     * @var array
134
     */
135
    public $perPages = [10, 20, 30, 50, 100];
136
137
    /**
138
     * Default items count per-page.
139
     *
140
     * @var int
141
     */
142
    public $perPage = 20;
143
144
    /**
145
     * Header tools.
146
     *
147
     * @var Tools
148
     */
149
    public $tools;
150
151
    /**
152
     * Callback for grid actions.
153
     *
154
     * @var Closure
155
     */
156
    protected $actionsCallback;
157
158
    /**
159
     * Options for grid.
160
     *
161
     * @var array
162
     */
163
    protected $options = [
164
        'usePagination'  => true,
165
        'useFilter'      => true,
166
        'useExporter'    => true,
167
        'useActions'     => true,
168
        'useRowSelector' => true,
169
        'allowCreate'    => true,
170
    ];
171
172
    /**
173
     * @var Tools\Footer
174
     */
175
    protected $footer;
176
177
    /**
178
     * Create a new grid instance.
179
     *
180
     * @param Eloquent $model
181
     * @param Closure  $builder
182
     */
183
    public function __construct(Eloquent $model, Closure $builder)
184
    {
185
        $this->keyName = $model->getKeyName();
186
        $this->model = new Model($model);
187
        $this->columns = new Collection();
188
        $this->rows = new Collection();
189
        $this->builder = $builder;
190
191
        $this->model()->setGrid($this);
192
193
        $this->setupTools();
194
        $this->setupFilter();
195
        $this->setupExporter();
196
    }
197
198
    /**
199
     * Setup grid tools.
200
     */
201
    public function setupTools()
202
    {
203
        $this->tools = new Tools($this);
204
    }
205
206
    /**
207
     * Setup grid filter.
208
     *
209
     * @return void
210
     */
211
    protected function setupFilter()
212
    {
213
        $this->filter = new Filter($this->model());
214
    }
215
216
    /**
217
     * Setup grid exporter.
218
     *
219
     * @return void
220
     */
221
    protected function setupExporter()
222
    {
223
        if ($scope = Input::get(Exporter::$queryName)) {
224
            $this->model()->usePaginate(false);
225
226
            call_user_func($this->builder, $this);
227
228
            (new Exporter($this))->resolve($this->exporter)->withScope($scope)->export();
229
        }
230
    }
231
232
    /**
233
     * Get or set option for grid.
234
     *
235
     * @param string $key
236
     * @param mixed  $value
237
     *
238
     * @return $this|mixed
239
     */
240
    public function option($key, $value = null)
241
    {
242
        if (is_null($value)) {
243
            return $this->options[$key];
244
        }
245
246
        $this->options[$key] = $value;
247
248
        return $this;
249
    }
250
251
    /**
252
     * Get primary key name of model.
253
     *
254
     * @return string
255
     */
256
    public function getKeyName()
257
    {
258
        return $this->keyName ?: 'id';
259
    }
260
261
    /**
262
     * Add column to Grid.
263
     *
264
     * @param string $name
265
     * @param string $label
266
     *
267
     * @return Column
268
     */
269
    public function column($name, $label = '')
270
    {
271
        $relationName = $relationColumn = '';
272
273
        if (strpos($name, '.') !== false) {
274
            list($relationName, $relationColumn) = explode('.', $name);
275
276
            $relation = $this->model()->eloquent()->$relationName();
277
278
            $label = empty($label) ? ucfirst($relationColumn) : $label;
279
280
            $name = snake_case($relationName).'.'.$relationColumn;
281
        }
282
283
        $column = $this->addColumn($name, $label);
284
285
        if (isset($relation) && $relation instanceof Relation) {
286
            $this->model()->with($relationName);
0 ignored issues
show
Documentation Bug introduced by
The method with does not exist on object<Encore\Admin\Grid\Model>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
287
            $column->setRelation($relationName, $relationColumn);
288
        }
289
290
        return $column;
291
    }
292
293
    /**
294
     * Batch add column to grid.
295
     *
296
     * @example
297
     * 1.$grid->columns(['name' => 'Name', 'email' => 'Email' ...]);
298
     * 2.$grid->columns('name', 'email' ...)
299
     *
300
     * @param array $columns
301
     *
302
     * @return Collection|null
303
     */
304
    public function columns($columns = [])
305
    {
306
        if (func_num_args() == 0) {
307
            return $this->columns;
308
        }
309
310
        if (func_num_args() == 1 && is_array($columns)) {
311
            foreach ($columns as $column => $label) {
312
                $this->column($column, $label);
313
            }
314
315
            return;
316
        }
317
318
        foreach (func_get_args() as $column) {
319
            $this->column($column);
320
        }
321
    }
322
323
    /**
324
     * Add column to grid.
325
     *
326
     * @param string $column
327
     * @param string $label
328
     *
329
     * @return Column
330
     */
331
    protected function addColumn($column = '', $label = '')
332
    {
333
        $column = new Column($column, $label);
334
        $column->setGrid($this);
335
336
        return $this->columns[] = $column;
337
    }
338
339
    /**
340
     * Get Grid model.
341
     *
342
     * @return Model
343
     */
344
    public function model()
345
    {
346
        return $this->model;
347
    }
348
349
    /**
350
     * Paginate the grid.
351
     *
352
     * @param int $perPage
353
     *
354
     * @return void
355
     */
356
    public function paginate($perPage = 20)
357
    {
358
        $this->perPage = $perPage;
359
360
        $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...
361
    }
362
363
    /**
364
     * Get the grid paginator.
365
     *
366
     * @return mixed
367
     */
368
    public function paginator()
369
    {
370
        return new Tools\Paginator($this);
371
    }
372
373
    /**
374
     * Disable grid pagination.
375
     *
376
     * @return $this
377
     */
378
    public function disablePagination()
379
    {
380
        $this->model->usePaginate(false);
381
382
        $this->option('usePagination', false);
383
384
        return $this;
385
    }
386
387
    /**
388
     * If this grid use pagination.
389
     *
390
     * @return bool
391
     */
392
    public function usePagination()
393
    {
394
        return $this->option('usePagination');
395
    }
396
397
    /**
398
     * Set per-page options.
399
     *
400
     * @param array $perPages
401
     */
402
    public function perPages(array $perPages)
403
    {
404
        $this->perPages = $perPages;
405
    }
406
407
    /**
408
     * Disable all actions.
409
     *
410
     * @return $this
411
     */
412
    public function disableActions()
413
    {
414
        return $this->option('useActions', false);
415
    }
416
417
    /**
418
     * Set grid action callback.
419
     *
420
     * @param Closure $callback
421
     *
422
     * @return $this
423
     */
424
    public function actions(Closure $callback)
425
    {
426
        $this->actionsCallback = $callback;
427
428
        return $this;
429
    }
430
431
    /**
432
     * Add `actions` column for grid.
433
     *
434
     * @return void
435
     */
436
    protected function appendActionsColumn()
437
    {
438
        if (!$this->option('useActions')) {
439
            return;
440
        }
441
442
        $grid = $this;
443
        $callback = $this->actionsCallback;
444
        $column = $this->addColumn('__actions__', trans('admin.action'));
445
446
        $column->display(function ($value) use ($grid, $column, $callback) {
447
            $actions = new Actions($value, $grid, $column, $this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Encore\Admin\Grid>, but the function expects a object<stdClass>.

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...
448
449
            return $actions->display($callback);
450
        });
451
    }
452
453
    /**
454
     * Disable row selector.
455
     *
456
     * @return Grid|mixed
457
     */
458
    public function disableRowSelector()
459
    {
460
        $this->tools(function ($tools) {
461
            /* @var Grid\Tools $tools */
462
            $tools->disableBatchActions();
463
        });
464
465
        return $this->option('useRowSelector', false);
466
    }
467
468
    /**
469
     * Prepend checkbox column for grid.
470
     *
471
     * @return void
472
     */
473
    protected function prependRowSelectorColumn()
474
    {
475
        if (!$this->option('useRowSelector')) {
476
            return;
477
        }
478
479
        $grid = $this;
480
481
        $column = new Column(Column::SELECT_COLUMN_NAME, ' ');
482
        $column->setGrid($this);
483
484
        $column->display(function ($value) use ($grid, $column) {
485
            $actions = new RowSelector($value, $grid, $column, $this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Encore\Admin\Grid>, but the function expects a object<stdClass>.

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...
486
487
            return $actions->display();
488
        });
489
490
        $this->columns->prepend($column);
491
    }
492
493
    /**
494
     * Build the grid.
495
     *
496
     * @return void
497
     */
498
    public function build()
499
    {
500
        if ($this->builded) {
501
            return;
502
        }
503
504
        $data = $this->processFilter();
505
506
        $this->prependRowSelectorColumn();
507
        $this->appendActionsColumn();
508
509
        Column::setOriginalGridData($data);
510
511
        $this->columns->map(function (Column $column) use (&$data) {
512
            $data = $column->fill($data);
513
514
            $this->columnNames[] = $column->getName();
515
        });
516
517
        $this->buildRows($data);
518
519
        $this->builded = true;
520
    }
521
522
    /**
523
     * Disable grid filter.
524
     *
525
     * @return $this
526
     */
527
    public function disableFilter()
528
    {
529
        $this->option('useFilter', false);
530
531
        return $this;
532
    }
533
534
    /**
535
     * Get filter of Grid.
536
     *
537
     * @return Filter
538
     */
539
    public function getFilter()
540
    {
541
        return $this->filter;
542
    }
543
544
    /**
545
     * Process the grid filter.
546
     *
547
     * @return array
548
     */
549
    public function processFilter()
550
    {
551
        call_user_func($this->builder, $this);
552
553
        return $this->filter->execute();
554
    }
555
556
    /**
557
     * Set the grid filter.
558
     *
559
     * @param Closure $callback
560
     */
561
    public function filter(Closure $callback)
562
    {
563
        call_user_func($callback, $this->filter);
564
    }
565
566
    /**
567
     * Render the grid filter.
568
     *
569
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|string
570
     */
571
    public function renderFilter()
572
    {
573
        if (!$this->option('useFilter')) {
574
            return '';
575
        }
576
577
        return $this->filter->render();
578
    }
579
580
    /**
581
     * Build the grid rows.
582
     *
583
     * @param array $data
584
     *
585
     * @return void
586
     */
587
    protected function buildRows(array $data)
588
    {
589
        $this->rows = collect($data)->map(function ($model, $number) {
590
            return new Row($number, $model);
591
        });
592
593
        if ($this->rowsCallback) {
594
            $this->rows->map($this->rowsCallback);
595
        }
596
    }
597
598
    /**
599
     * Set grid row callback function.
600
     *
601
     * @param Closure $callable
602
     *
603
     * @return Collection|null
604
     */
605
    public function rows(Closure $callable = null)
606
    {
607
        if (is_null($callable)) {
608
            return $this->rows;
609
        }
610
611
        $this->rowsCallback = $callable;
612
    }
613
614
    /**
615
     * Setup grid tools.
616
     *
617
     * @param Closure $callback
618
     *
619
     * @return void
620
     */
621
    public function tools(Closure $callback)
622
    {
623
        call_user_func($callback, $this->tools);
624
    }
625
626
    /**
627
     * Render custom tools.
628
     *
629
     * @return string
630
     */
631
    public function renderHeaderTools()
632
    {
633
        return $this->tools->render();
634
    }
635
636
    /**
637
     * Set exporter driver for Grid to export.
638
     *
639
     * @param $exporter
640
     *
641
     * @return $this
642
     */
643
    public function exporter($exporter)
644
    {
645
        $this->exporter = $exporter;
646
647
        return $this;
648
    }
649
650
    /**
651
     * Get the export url.
652
     *
653
     * @param int  $scope
654
     * @param null $args
655
     *
656
     * @return string
657
     */
658
    public function getExportUrl($scope = 1, $args = null)
659
    {
660
        $input = array_merge(Input::all(), Exporter::formatExportQuery($scope, $args));
661
662
        if ($constraints = $this->model()->getConstraints()) {
663
            $input = array_merge($input, $constraints);
664
        }
665
666
        return $this->resource().'?'.http_build_query($input);
667
    }
668
669
    /**
670
     * Get create url.
671
     *
672
     * @return string
673
     */
674
    public function getCreateUrl()
675
    {
676
        $queryString = '';
677
678
        if ($constraints = $this->model()->getConstraints()) {
679
            $queryString = http_build_query($constraints);
680
        }
681
682
        return sprintf('%s/create%s',
683
            $this->resource(),
684
            $queryString ? ('?' . $queryString) : ''
685
        );
686
    }
687
688
    /**
689
     * If grid allows export.s.
690
     *
691
     * @return bool
692
     */
693
    public function allowExport()
694
    {
695
        return $this->option('useExporter');
696
    }
697
698
    /**
699
     * Disable export.
700
     *
701
     * @return $this
702
     */
703
    public function disableExport()
704
    {
705
        return $this->option('useExporter', false);
706
    }
707
708
    /**
709
     * Render export button.
710
     *
711
     * @return string
712
     */
713
    public function renderExportButton()
714
    {
715
        return (new Tools\ExportButton($this))->render();
716
    }
717
718
    /**
719
     * Alias for method `disableCreateButton`.
720
     *
721
     * @return $this
722
     *
723
     * @deprecated
724
     */
725
    public function disableCreation()
726
    {
727
        return $this->disableCreateButton();
728
    }
729
730
    /**
731
     * Remove create button on grid.
732
     *
733
     * @return $this
734
     */
735
    public function disableCreateButton()
736
    {
737
        return $this->option('allowCreate', false);
738
    }
739
740
    /**
741
     * If allow creation.
742
     *
743
     * @return bool
744
     */
745
    public function allowCreation()
746
    {
747
        return $this->option('allowCreate');
748
    }
749
750
    /**
751
     * Render create button for grid.
752
     *
753
     * @return string
754
     */
755
    public function renderCreateButton()
756
    {
757
        return (new Tools\CreateButton($this))->render();
758
    }
759
760
    /**
761
     * Set grid footer.
762
     *
763
     * @param Closure|null $closure
764
     *
765
     * @return $this|Tools\Footer
766
     */
767
    public function footer(Closure $closure = null)
768
    {
769
        if (!$closure) {
770
            return $this->footer;
771
        }
772
773
        $this->footer = $closure;
0 ignored issues
show
Documentation Bug introduced by
It seems like $closure of type object<Closure> is incompatible with the declared type object<Encore\Admin\Grid\Tools\Footer> of property $footer.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
774
775
        return $this;
776
    }
777
778
    /**
779
     * Render grid footer.
780
     *
781
     * @return Tools\Footer|string
782
     */
783
    public function renderFooter()
784
    {
785
        if (!$this->footer) {
786
            return '';
787
        }
788
789
        return new Tools\Footer($this);
790
    }
791
792
    /**
793
     * Get current resource uri.
794
     *
795
     * @param string $path
796
     *
797
     * @return string
798
     */
799
    public function resource($path = null)
800
    {
801
        if (!empty($path)) {
802
            $this->resourcePath = $path;
803
804
            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...
805
        }
806
807
        if (!empty($this->resourcePath)) {
808
            return $this->resourcePath;
809
        }
810
811
        return app('request')->getPathInfo();
812
    }
813
814
    /**
815
     * Get the table columns for grid.
816
     *
817
     * @return void
818
     */
819
    protected function setDbColumns()
820
    {
821
        $connection = $this->model()->eloquent()->getConnectionName();
822
823
        $this->dbColumns = collect(Schema::connection($connection)->getColumnListing($this->model()->getTable()));
824
    }
825
826
    /**
827
     * Handle table column for grid.
828
     *
829
     * @param string $method
830
     * @param string $label
831
     *
832
     * @return bool|Column
833
     */
834
    protected function handleTableColumn($method, $label)
835
    {
836
        if (empty($this->dbColumns)) {
837
            $this->setDbColumns();
838
        }
839
840
        if ($this->dbColumns->has($method)) {
841
            return $this->addColumn($method, $label);
842
        }
843
844
        return false;
845
    }
846
847
    /**
848
     * Handle get mutator column for grid.
849
     *
850
     * @param string $method
851
     * @param string $label
852
     *
853
     * @return bool|Column
854
     */
855
    protected function handleGetMutatorColumn($method, $label)
856
    {
857
        if ($this->model()->eloquent()->hasGetMutator($method)) {
858
            return $this->addColumn($method, $label);
859
        }
860
861
        return false;
862
    }
863
864
    /**
865
     * Handle relation column for grid.
866
     *
867
     * @param string $method
868
     * @param string $label
869
     *
870
     * @return bool|Column
871
     */
872
    protected function handleRelationColumn($method, $label)
873
    {
874
        $model = $this->model()->eloquent();
875
876
        if (!method_exists($model, $method)) {
877
            return false;
878
        }
879
880
        if (!($relation = $model->$method()) instanceof Relation) {
881
            return false;
882
        }
883
884
        if ($relation instanceof HasOne || $relation instanceof BelongsTo) {
885
            $this->model()->with($method);
0 ignored issues
show
Documentation Bug introduced by
The method with does not exist on object<Encore\Admin\Grid\Model>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
886
887
            return $this->addColumn($method, $label)->setRelation(snake_case($method));
888
        }
889
890
        if ($relation instanceof HasMany || $relation instanceof BelongsToMany || $relation instanceof MorphToMany) {
891
            $this->model()->with($method);
0 ignored issues
show
Documentation Bug introduced by
The method with does not exist on object<Encore\Admin\Grid\Model>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
892
893
            return $this->addColumn(snake_case($method), $label);
894
        }
895
896
        return false;
897
    }
898
899
    /**
900
     * Dynamically add columns to the grid view.
901
     *
902
     * @param $method
903
     * @param $arguments
904
     *
905
     * @return Column
906
     */
907
    public function __call($method, $arguments)
908
    {
909
        $label = isset($arguments[0]) ? $arguments[0] : ucfirst($method);
910
911
        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...
912
            return $this->addColumn($method, $label);
913
        }
914
915
        if ($column = $this->handleGetMutatorColumn($method, $label)) {
916
            return $column;
917
        }
918
919
        if ($column = $this->handleRelationColumn($method, $label)) {
920
            return $column;
921
        }
922
923
        if ($column = $this->handleTableColumn($method, $label)) {
924
            return $column;
925
        }
926
927
        return $this->addColumn($method, $label);
928
    }
929
930
    /**
931
     * Register column displayers.
932
     *
933
     * @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...
934
     */
935
    public static function registerColumnDisplayer()
936
    {
937
        $map = [
938
            'editable'    => \Encore\Admin\Grid\Displayers\Editable::class,
939
            'switch'      => \Encore\Admin\Grid\Displayers\SwitchDisplay::class,
940
            'switchGroup' => \Encore\Admin\Grid\Displayers\SwitchGroup::class,
941
            'select'      => \Encore\Admin\Grid\Displayers\Select::class,
942
            'image'       => \Encore\Admin\Grid\Displayers\Image::class,
943
            'label'       => \Encore\Admin\Grid\Displayers\Label::class,
944
            'button'      => \Encore\Admin\Grid\Displayers\Button::class,
945
            'link'        => \Encore\Admin\Grid\Displayers\Link::class,
946
            'badge'       => \Encore\Admin\Grid\Displayers\Badge::class,
947
            'progressBar' => \Encore\Admin\Grid\Displayers\ProgressBar::class,
948
            'radio'       => \Encore\Admin\Grid\Displayers\Radio::class,
949
            'checkbox'    => \Encore\Admin\Grid\Displayers\Checkbox::class,
950
            'orderable'   => \Encore\Admin\Grid\Displayers\Orderable::class,
951
        ];
952
953
        foreach ($map as $abstract => $class) {
954
            Column::extend($abstract, $class);
955
        }
956
    }
957
958
    /**
959
     * Add variables to grid view.
960
     *
961
     * @param array $variables
962
     *
963
     * @return $this
964
     */
965
    public function with($variables = [])
966
    {
967
        $this->variables = $variables;
968
969
        return $this;
970
    }
971
972
    /**
973
     * Get all variables will used in grid view.
974
     *
975
     * @return array
976
     */
977
    protected function variables()
978
    {
979
        $this->variables['grid'] = $this;
980
981
        return $this->variables;
982
    }
983
984
    /**
985
     * Set a view to render.
986
     *
987
     * @param string $view
988
     * @param array  $variables
989
     */
990
    public function setView($view, $variables = [])
991
    {
992
        if (!empty($variables)) {
993
            $this->with($variables);
994
        }
995
996
        $this->view = $view;
997
    }
998
999
    /**
1000
     * Set grid title.
1001
     *
1002
     * @param string $title
1003
     * @return $this
1004
     */
1005
    public function setTitle($title)
1006
    {
1007
        $this->variables['title'] = $title;
1008
1009
        return $this;
1010
    }
1011
1012
    /**
1013
     * Set relation for grid.
1014
     *
1015
     * @param Relation $relation
1016
     * @return $this
1017
     */
1018
    public function setRelation(Relation $relation)
1019
    {
1020
        $this->model()->setRelation($relation);
1021
1022
        return $this;
1023
    }
1024
1025
    /**
1026
     * Get the string contents of the grid view.
1027
     *
1028
     * @return string
1029
     */
1030
    public function render()
1031
    {
1032
        try {
1033
            $this->build();
1034
        } catch (\Exception $e) {
1035
            return Handler::renderException($e);
1036
        }
1037
1038
        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...
1039
    }
1040
}
1041