Completed
Push — master ( 7de3f0...5ca9ad )
by Song
06:09
created

Model::getTable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Grid;
4
5
use Encore\Admin\Grid;
6
use Encore\Admin\Middleware\Pjax;
7
use Illuminate\Database\Eloquent\Model as EloquentModel;
8
use Illuminate\Database\Eloquent\Relations\BelongsTo;
9
use Illuminate\Database\Eloquent\Relations\HasMany;
10
use Illuminate\Database\Eloquent\Relations\HasOne;
11
use Illuminate\Database\Eloquent\Relations\Relation;
12
use Illuminate\Pagination\LengthAwarePaginator;
13
use Illuminate\Support\Arr;
14
use Illuminate\Support\Collection;
15
use Illuminate\Support\Facades\Input;
16
use Illuminate\Support\Facades\Request;
17
use Illuminate\Support\Str;
18
19
class Model
20
{
21
    /**
22
     * Eloquent model instance of the grid model.
23
     *
24
     * @var EloquentModel
25
     */
26
    protected $model;
27
28
    /**
29
     * Array of queries of the eloquent model.
30
     *
31
     * @var \Illuminate\Support\Collection
32
     */
33
    protected $queries;
34
35
    /**
36
     * Sort parameters of the model.
37
     *
38
     * @var array
39
     */
40
    protected $sort;
41
42
    /**
43
     * @var array
44
     */
45
    protected $data = [];
46
47
    /*
48
     * 20 items per page as default.
49
     *
50
     * @var int
51
     */
52
    protected $perPage = 20;
53
54
    /**
55
     * If the model use pagination.
56
     *
57
     * @var bool
58
     */
59
    protected $usePaginate = true;
60
61
    /**
62
     * The query string variable used to store the per-page.
63
     *
64
     * @var string
65
     */
66
    protected $perPageName = 'per_page';
67
68
    /**
69
     * The query string variable used to store the sort.
70
     *
71
     * @var string
72
     */
73
    protected $sortName = '_sort';
74
75
    /**
76
     * Collection callback.
77
     *
78
     * @var \Closure
79
     */
80
    protected $collectionCallback;
81
82
    /**
83
     * @var Grid
84
     */
85
    protected $grid;
86
87
    /**
88
     * @var Relation
89
     */
90
    protected $relation;
91
92
    /**
93
     * @var array
94
     */
95
    protected $eagerLoads = [];
96
97
    /**
98
     * Create a new grid model instance.
99
     *
100
     * @param EloquentModel $model
101
     */
102
    public function __construct(EloquentModel $model)
103
    {
104
        $this->model = $model;
105
106
        $this->queries = collect();
107
108
//        static::doNotSnakeAttributes($this->model);
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
109
    }
110
111
    /**
112
     * Don't snake case attributes.
113
     *
114
     * @param EloquentModel $model
115
     *
116
     * @return void
117
     */
118
    protected static function doNotSnakeAttributes(EloquentModel $model)
119
    {
120
        $class = get_class($model);
121
122
        $class::$snakeAttributes = false;
123
    }
124
125
    /**
126
     * Get the eloquent model of the grid model.
127
     *
128
     * @return EloquentModel
129
     */
130
    public function eloquent()
131
    {
132
        return $this->model;
133
    }
134
135
    /**
136
     * Enable or disable pagination.
137
     *
138
     * @param bool $use
139
     */
140
    public function usePaginate($use = true)
141
    {
142
        $this->usePaginate = $use;
143
    }
144
145
    /**
146
     * Get the query string variable used to store the per-page.
147
     *
148
     * @return string
149
     */
150
    public function getPerPageName()
151
    {
152
        return $this->perPageName;
153
    }
154
155
    /**
156
     * Set the query string variable used to store the per-page.
157
     *
158
     * @param string $name
159
     *
160
     * @return $this
161
     */
162
    public function setPerPageName($name)
163
    {
164
        $this->perPageName = $name;
165
166
        return $this;
167
    }
168
169
    /**
170
     * Get the query string variable used to store the sort.
171
     *
172
     * @return string
173
     */
174
    public function getSortName()
175
    {
176
        return $this->sortName;
177
    }
178
179
    /**
180
     * Set the query string variable used to store the sort.
181
     *
182
     * @param string $name
183
     *
184
     * @return $this
185
     */
186
    public function setSortName($name)
187
    {
188
        $this->sortName = $name;
189
190
        return $this;
191
    }
192
193
    /**
194
     * Set parent grid instance.
195
     *
196
     * @param Grid $grid
197
     *
198
     * @return $this
199
     */
200
    public function setGrid(Grid $grid)
201
    {
202
        $this->grid = $grid;
203
204
        return $this;
205
    }
206
207
    /**
208
     * Get parent gird instance.
209
     *
210
     * @return Grid
211
     */
212
    public function getGrid()
213
    {
214
        return $this->grid;
215
    }
216
217
    /**
218
     * @param Relation $relation
219
     *
220
     * @return $this
221
     */
222
    public function setRelation(Relation $relation)
223
    {
224
        $this->relation = $relation;
225
226
        return $this;
227
    }
228
229
    /**
230
     * @return Relation
231
     */
232
    public function getRelation()
233
    {
234
        return $this->relation;
235
    }
236
237
    /**
238
     * Get constraints.
239
     *
240
     * @return array|bool
241
     */
242
    public function getConstraints()
243
    {
244
        if ($this->relation instanceof HasMany) {
245
            return [
246
                $this->relation->getForeignKeyName() => $this->relation->getParentKey(),
247
            ];
248
        }
249
250
        return false;
251
    }
252
253
    /**
254
     * Set collection callback.
255
     *
256
     * @param \Closure $callback
257
     *
258
     * @return $this
259
     */
260
    public function collection(\Closure $callback = null)
261
    {
262
        $this->collectionCallback = $callback;
263
264
        return $this;
265
    }
266
267
    /**
268
     * Build.
269
     *
270
     * @param bool $toArray
271
     *
272
     * @return array|Collection|mixed
273
     */
274
    public function buildData($toArray = true)
275
    {
276
        if (empty($this->data)) {
277
            $collection = $this->get();
278
279
            if ($this->collectionCallback) {
280
                $collection = call_user_func($this->collectionCallback, $collection);
281
            }
282
283
            if ($toArray) {
284
                $this->data = $collection->toArray();
285
            } else {
286
                $this->data = $collection;
0 ignored issues
show
Documentation Bug introduced by
It seems like $collection of type * is incompatible with the declared type array of property $data.

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...
287
            }
288
        }
289
290
        return $this->data;
291
    }
292
293
    /**
294
     * @param callable $callback
295
     * @param int      $count
296
     *
297
     * @return bool
298
     */
299
    public function chunk($callback, $count = 100)
300
    {
301
        if ($this->usePaginate) {
302
            return $this->buildData(false)->chunk($count)->each($callback);
303
        }
304
305
        $this->setSort();
306
307
        $this->queries->reject(function ($query) {
308
            return $query['method'] == 'paginate';
309
        })->each(function ($query) {
310
            $this->model = $this->model->{$query['method']}(...$query['arguments']);
311
        });
312
313
        return $this->model->chunk($count, $callback);
314
    }
315
316
    /**
317
     * Add conditions to grid model.
318
     *
319
     * @param array $conditions
320
     *
321
     * @return $this
322
     */
323
    public function addConditions(array $conditions)
324
    {
325
        foreach ($conditions as $condition) {
326
            call_user_func_array([$this, key($condition)], current($condition));
327
        }
328
329
        return $this;
330
    }
331
332
    /**
333
     * Get table of the model.
334
     *
335
     * @return string
336
     */
337
    public function getTable()
338
    {
339
        return $this->model->getTable();
340
    }
341
342
    /**
343
     * @throws \Exception
344
     *
345
     * @return Collection
346
     */
347
    protected function get()
348
    {
349
        if ($this->model instanceof LengthAwarePaginator) {
350
            return $this->model;
351
        }
352
353
        if ($this->relation) {
354
            $this->model = $this->relation->getQuery();
355
        }
356
357
        $this->setSort();
358
        $this->setPaginate();
359
360
        $this->queries->unique()->each(function ($query) {
361
            $this->model = call_user_func_array([$this->model, $query['method']], $query['arguments']);
362
        });
363
364
        if ($this->model instanceof Collection) {
365
            return $this->model;
366
        }
367
368
        if ($this->model instanceof LengthAwarePaginator) {
369
            $this->handleInvalidPage($this->model);
370
371
            return $this->model->getCollection();
372
        }
373
374
        throw new \Exception('Grid query error');
375
    }
376
377
    /**
378
     * If current page is greater than last page, then redirect to last page.
379
     *
380
     * @param LengthAwarePaginator $paginator
381
     *
382
     * @return void
383
     */
384
    protected function handleInvalidPage(LengthAwarePaginator $paginator)
385
    {
386
        if ($paginator->lastPage() && $paginator->currentPage() > $paginator->lastPage()) {
387
            $lastPageUrl = Request::fullUrlWithQuery([
388
                $paginator->getPageName() => $paginator->lastPage(),
389
            ]);
390
391
            Pjax::respond(redirect($lastPageUrl));
392
        }
393
    }
394
395
    /**
396
     * Set the grid paginate.
397
     *
398
     * @return void
399
     */
400
    protected function setPaginate()
401
    {
402
        $paginate = $this->findQueryByMethod('paginate');
403
404
        $this->queries = $this->queries->reject(function ($query) {
405
            return $query['method'] == 'paginate';
406
        });
407
408
        if (!$this->usePaginate) {
409
            $query = [
410
                'method'    => 'get',
411
                'arguments' => [],
412
            ];
413
        } else {
414
            $query = [
415
                'method'    => 'paginate',
416
                'arguments' => $this->resolvePerPage($paginate),
0 ignored issues
show
Documentation introduced by
$paginate is of type this<Encore\Admin\Grid\Model>, but the function expects a array|null.

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...
417
            ];
418
        }
419
420
        $this->queries->push($query);
421
    }
422
423
    /**
424
     * Resolve perPage for pagination.
425
     *
426
     * @param array|null $paginate
427
     *
428
     * @return array
429
     */
430
    protected function resolvePerPage($paginate)
431
    {
432
        if ($perPage = app('request')->input($this->perPageName)) {
433
            if (is_array($paginate)) {
434
                $paginate['arguments'][0] = (int) $perPage;
435
436
                return $paginate['arguments'];
437
            }
438
439
            $this->perPage = (int) $perPage;
440
        }
441
442
        if (isset($paginate['arguments'][0])) {
443
            return $paginate['arguments'];
444
        }
445
446
        if ($name = $this->grid->getName()) {
447
            return [$this->perPage, null, "{$name}_page"];
448
        }
449
450
        return [$this->perPage];
451
    }
452
453
    /**
454
     * Find query by method name.
455
     *
456
     * @param $method
457
     *
458
     * @return static
459
     */
460
    protected function findQueryByMethod($method)
461
    {
462
        return $this->queries->first(function ($query) use ($method) {
463
            return $query['method'] == $method;
464
        });
465
    }
466
467
    /**
468
     * Set the grid sort.
469
     *
470
     * @return void
471
     */
472
    protected function setSort()
473
    {
474
        $this->sort = Input::get($this->sortName, []);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Illuminate\Support\Faca...his->sortName, array()) of type * is incompatible with the declared type array of property $sort.

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...
475
        if (!is_array($this->sort)) {
476
            return;
477
        }
478
479
        if (empty($this->sort['column']) || empty($this->sort['type'])) {
480
            return;
481
        }
482
483
        if (str_contains($this->sort['column'], '.')) {
484
            $this->setRelationSort($this->sort['column']);
485
        } else {
486
            $this->resetOrderBy();
487
488
            $this->queries->push([
489
                'method'    => 'orderBy',
490
                'arguments' => [$this->sort['column'], $this->sort['type']],
491
            ]);
492
        }
493
    }
494
495
    /**
496
     * Set relation sort.
497
     *
498
     * @param string $column
499
     *
500
     * @return void
501
     */
502
    protected function setRelationSort($column)
503
    {
504
        list($relationName, $relationColumn) = explode('.', $column);
505
506
        if ($this->queries->contains(function ($query) use ($relationName) {
507
            return $query['method'] == 'with' && in_array($relationName, $query['arguments']);
508
        })) {
509
            $relation = $this->model->$relationName();
510
511
            $this->queries->push([
512
                'method'    => 'join',
513
                'arguments' => $this->joinParameters($relation),
514
            ]);
515
516
            $this->resetOrderBy();
517
518
            $this->queries->push([
519
                'method'    => 'orderBy',
520
                'arguments' => [
521
                    $relation->getRelated()->getTable().'.'.$relationColumn,
522
                    $this->sort['type'],
523
                ],
524
            ]);
525
        }
526
    }
527
528
    /**
529
     * Reset orderBy query.
530
     *
531
     * @return void
532
     */
533
    public function resetOrderBy()
534
    {
535
        $this->queries = $this->queries->reject(function ($query) {
536
            return $query['method'] == 'orderBy' || $query['method'] == 'orderByDesc';
537
        });
538
    }
539
540
    /**
541
     * Build join parameters for related model.
542
     *
543
     * `HasOne` and `BelongsTo` relation has different join parameters.
544
     *
545
     * @param Relation $relation
546
     *
547
     * @throws \Exception
548
     *
549
     * @return array
550
     */
551
    protected function joinParameters(Relation $relation)
552
    {
553
        $relatedTable = $relation->getRelated()->getTable();
554
555
        if ($relation instanceof BelongsTo) {
556
            return [
557
                $relatedTable,
558
                $relation->getForeignKey(),
559
                '=',
560
                $relatedTable.'.'.$relation->getRelated()->getKeyName(),
561
            ];
562
        }
563
564
        if ($relation instanceof HasOne) {
565
            return [
566
                $relatedTable,
567
                $relation->getQualifiedParentKeyName(),
568
                '=',
569
                $relation->getQualifiedForeignKeyName(),
570
            ];
571
        }
572
573
        throw new \Exception('Related sortable only support `HasOne` and `BelongsTo` relation.');
574
    }
575
576
    /**
577
     * @param string $method
578
     * @param array  $arguments
579
     *
580
     * @return $this
581
     */
582
    public function __call($method, $arguments)
583
    {
584
        $this->queries->push([
585
            'method'    => $method,
586
            'arguments' => $arguments,
587
        ]);
588
589
        return $this;
590
    }
591
592
    /**
593
     * Set the relationships that should be eager loaded.
594
     *
595
     * @param mixed $relations
596
     *
597
     * @return $this|Model
598
     */
599
    public function with($relations)
600
    {
601
        if (is_array($relations)) {
602
            if (Arr::isAssoc($relations)) {
603
                $relations = array_keys($relations);
604
            }
605
606
            $this->eagerLoads = array_merge($this->eagerLoads, $relations);
607
        }
608
609
        if (is_string($relations)) {
610
            if (Str::contains($relations, '.')) {
611
                $relations = explode('.', $relations)[0];
612
            }
613
614
            if (Str::contains($relations, ':')) {
615
                $relations = explode(':', $relations)[0];
616
            }
617
618
            if (in_array($relations, $this->eagerLoads)) {
619
                return $this;
620
            }
621
622
            $this->eagerLoads[] = $relations;
623
        }
624
625
        return $this->__call('with', (array) $relations);
626
    }
627
628
    /**
629
     * @param $key
630
     *
631
     * @return mixed
632
     */
633
    public function __get($key)
634
    {
635
        $data = $this->buildData();
636
637
        if (array_key_exists($key, $data)) {
638
            return $data[$key];
639
        }
640
    }
641
}
642