Completed
Push — master ( 370418...bb30ab )
by Song
02:23
created

Model::setPerPage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 8
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
     * @var EloquentModel
30
     */
31
    protected $originalModel;
32
33
    /**
34
     * Array of queries of the eloquent model.
35
     *
36
     * @var \Illuminate\Support\Collection
37
     */
38
    protected $queries;
39
40
    /**
41
     * Sort parameters of the model.
42
     *
43
     * @var array
44
     */
45
    protected $sort;
46
47
    /**
48
     * @var array
49
     */
50
    protected $data = [];
51
52
    /*
53
     * 20 items per page as default.
54
     *
55
     * @var int
56
     */
57
    protected $perPage = 20;
58
59
    /**
60
     * If the model use pagination.
61
     *
62
     * @var bool
63
     */
64
    protected $usePaginate = true;
65
66
    /**
67
     * The query string variable used to store the per-page.
68
     *
69
     * @var string
70
     */
71
    protected $perPageName = 'per_page';
72
73
    /**
74
     * The query string variable used to store the sort.
75
     *
76
     * @var string
77
     */
78
    protected $sortName = '_sort';
79
80
    /**
81
     * Collection callback.
82
     *
83
     * @var \Closure
84
     */
85
    protected $collectionCallback;
86
87
    /**
88
     * @var Grid
89
     */
90
    protected $grid;
91
92
    /**
93
     * @var Relation
94
     */
95
    protected $relation;
96
97
    /**
98
     * @var array
99
     */
100
    protected $eagerLoads = [];
101
102
    /**
103
     * Create a new grid model instance.
104
     *
105
     * @param EloquentModel $model
106
     */
107
    public function __construct(EloquentModel $model)
108
    {
109
        $this->model = $model;
110
111
        $this->originalModel = $model;
112
113
        $this->queries = collect();
114
115
//        static::doNotSnakeAttributes($this->model);
116
    }
117
118
    /**
119
     * Don't snake case attributes.
120
     *
121
     * @param EloquentModel $model
122
     *
123
     * @return void
124
     */
125
    protected static function doNotSnakeAttributes(EloquentModel $model)
126
    {
127
        $class = get_class($model);
128
129
        $class::$snakeAttributes = false;
130
    }
131
132
    /**
133
     * @return EloquentModel
134
     */
135
    public function getOriginalModel()
136
    {
137
        return $this->originalModel;
138
    }
139
140
    /**
141
     * Get the eloquent model of the grid model.
142
     *
143
     * @return EloquentModel
144
     */
145
    public function eloquent()
146
    {
147
        return $this->model;
148
    }
149
150
    /**
151
     * Enable or disable pagination.
152
     *
153
     * @param bool $use
154
     */
155
    public function usePaginate($use = true)
156
    {
157
        $this->usePaginate = $use;
158
    }
159
160
    /**
161
     * Get the query string variable used to store the per-page.
162
     *
163
     * @return string
164
     */
165
    public function getPerPageName()
166
    {
167
        return $this->perPageName;
168
    }
169
170
    /**
171
     * Set the query string variable used to store the per-page.
172
     *
173
     * @param string $name
174
     *
175
     * @return $this
176
     */
177
    public function setPerPageName($name)
178
    {
179
        $this->perPageName = $name;
180
181
        return $this;
182
    }
183
184
    /**
185
     * Get per-page number.
186
     *
187
     * @return int
188
     */
189
    public function getPerPage()
190
    {
191
        return $this->perPage;
192
    }
193
194
    /**
195
     * Set per-page number.
196
     *
197
     * @param int $perPage
198
     *
199
     * @return $this
200
     */
201
    public function setPerPage($perPage)
202
    {
203
        $this->perPage = $perPage;
204
205
        $this->__call('paginate', [$perPage]);
206
207
        return $this;
208
    }
209
210
    /**
211
     * Get the query string variable used to store the sort.
212
     *
213
     * @return string
214
     */
215
    public function getSortName()
216
    {
217
        return $this->sortName;
218
    }
219
220
    /**
221
     * Set the query string variable used to store the sort.
222
     *
223
     * @param string $name
224
     *
225
     * @return $this
226
     */
227
    public function setSortName($name)
228
    {
229
        $this->sortName = $name;
230
231
        return $this;
232
    }
233
234
    /**
235
     * Set parent grid instance.
236
     *
237
     * @param Grid $grid
238
     *
239
     * @return $this
240
     */
241
    public function setGrid(Grid $grid)
242
    {
243
        $this->grid = $grid;
244
245
        return $this;
246
    }
247
248
    /**
249
     * Get parent gird instance.
250
     *
251
     * @return Grid
252
     */
253
    public function getGrid()
254
    {
255
        return $this->grid;
256
    }
257
258
    /**
259
     * @param Relation $relation
260
     *
261
     * @return $this
262
     */
263
    public function setRelation(Relation $relation)
264
    {
265
        $this->relation = $relation;
266
267
        return $this;
268
    }
269
270
    /**
271
     * @return Relation
272
     */
273
    public function getRelation()
274
    {
275
        return $this->relation;
276
    }
277
278
    /**
279
     * Get constraints.
280
     *
281
     * @return array|bool
282
     */
283
    public function getConstraints()
284
    {
285
        if ($this->relation instanceof HasMany) {
286
            return [
287
                $this->relation->getForeignKeyName() => $this->relation->getParentKey(),
288
            ];
289
        }
290
291
        return false;
292
    }
293
294
    /**
295
     * Set collection callback.
296
     *
297
     * @param \Closure $callback
298
     *
299
     * @return $this
300
     */
301
    public function collection(\Closure $callback = null)
302
    {
303
        $this->collectionCallback = $callback;
304
305
        return $this;
306
    }
307
308
    /**
309
     * Build.
310
     *
311
     * @param bool $toArray
312
     *
313
     * @return array|Collection|mixed
314
     */
315
    public function buildData($toArray = true)
316
    {
317
        if (empty($this->data)) {
318
            $collection = $this->get();
319
320
            if ($this->collectionCallback) {
321
                $collection = call_user_func($this->collectionCallback, $collection);
322
            }
323
324
            if ($toArray) {
325
                $this->data = $collection->toArray();
326
            } else {
327
                $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...
328
            }
329
        }
330
331
        return $this->data;
332
    }
333
334
    /**
335
     * @param callable $callback
336
     * @param int      $count
337
     *
338
     * @return bool
339
     */
340
    public function chunk($callback, $count = 100)
341
    {
342
        if ($this->usePaginate) {
343
            return $this->buildData(false)->chunk($count)->each($callback);
344
        }
345
346
        $this->setSort();
347
348
        $this->queries->reject(function ($query) {
349
            return $query['method'] == 'paginate';
350
        })->each(function ($query) {
351
            $this->model = $this->model->{$query['method']}(...$query['arguments']);
352
        });
353
354
        return $this->model->chunk($count, $callback);
355
    }
356
357
    /**
358
     * Add conditions to grid model.
359
     *
360
     * @param array $conditions
361
     *
362
     * @return $this
363
     */
364
    public function addConditions(array $conditions)
365
    {
366
        foreach ($conditions as $condition) {
367
            call_user_func_array([$this, key($condition)], current($condition));
368
        }
369
370
        return $this;
371
    }
372
373
    /**
374
     * Get table of the model.
375
     *
376
     * @return string
377
     */
378
    public function getTable()
379
    {
380
        return $this->model->getTable();
381
    }
382
383
    /**
384
     * @throws \Exception
385
     *
386
     * @return Collection
387
     */
388
    protected function get()
389
    {
390
        if ($this->model instanceof LengthAwarePaginator) {
391
            return $this->model;
392
        }
393
394
        if ($this->relation) {
395
            $this->model = $this->relation->getQuery();
396
        }
397
398
        $this->setSort();
399
        $this->setPaginate();
400
401
        $this->queries->unique()->each(function ($query) {
402
            $this->model = call_user_func_array([$this->model, $query['method']], $query['arguments']);
403
        });
404
405
        if ($this->model instanceof Collection) {
406
            return $this->model;
407
        }
408
409
        if ($this->model instanceof LengthAwarePaginator) {
410
            $this->handleInvalidPage($this->model);
411
412
            return $this->model->getCollection();
413
        }
414
415
        throw new \Exception('Grid query error');
416
    }
417
418
    /**
419
     * @return \Illuminate\Database\Eloquent\Builder|EloquentModel
420
     */
421
    public function getQueryBuilder()
422
    {
423
        if ($this->relation) {
424
            return $this->relation->getQuery();
425
        }
426
427
        $this->setSort();
428
429
        $queryBuilder = $this->originalModel;
430
431
        $this->queries->reject(function ($query) {
432
            return in_array($query['method'], ['get', 'paginate']);
433
        })->each(function ($query) use (&$queryBuilder) {
434
            $queryBuilder = $queryBuilder->{$query['method']}(...$query['arguments']);
435
        });
436
437
        return $queryBuilder;
438
    }
439
440
    /**
441
     * If current page is greater than last page, then redirect to last page.
442
     *
443
     * @param LengthAwarePaginator $paginator
444
     *
445
     * @return void
446
     */
447
    protected function handleInvalidPage(LengthAwarePaginator $paginator)
448
    {
449
        if ($paginator->lastPage() && $paginator->currentPage() > $paginator->lastPage()) {
450
            $lastPageUrl = Request::fullUrlWithQuery([
451
                $paginator->getPageName() => $paginator->lastPage(),
452
            ]);
453
454
            Pjax::respond(redirect($lastPageUrl));
455
        }
456
    }
457
458
    /**
459
     * Set the grid paginate.
460
     *
461
     * @return void
462
     */
463
    protected function setPaginate()
464
    {
465
        $paginate = $this->findQueryByMethod('paginate');
466
467
        $this->queries = $this->queries->reject(function ($query) {
468
            return $query['method'] == 'paginate';
469
        });
470
471
        if (!$this->usePaginate) {
472
            $query = [
473
                'method'    => 'get',
474
                'arguments' => [],
475
            ];
476
        } else {
477
            $query = [
478
                'method'    => 'paginate',
479
                '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...
480
            ];
481
        }
482
483
        $this->queries->push($query);
484
    }
485
486
    /**
487
     * Resolve perPage for pagination.
488
     *
489
     * @param array|null $paginate
490
     *
491
     * @return array
492
     */
493
    protected function resolvePerPage($paginate)
494
    {
495
        if ($perPage = request($this->perPageName)) {
496
            if (is_array($paginate)) {
497
                $paginate['arguments'][0] = (int) $perPage;
498
499
                return $paginate['arguments'];
500
            }
501
502
            $this->perPage = (int) $perPage;
503
        }
504
505
        if (isset($paginate['arguments'][0])) {
506
            return $paginate['arguments'];
507
        }
508
509
        if ($name = $this->grid->getName()) {
510
            return [$this->perPage, ['*'], "{$name}_page"];
511
        }
512
513
        return [$this->perPage];
514
    }
515
516
    /**
517
     * Find query by method name.
518
     *
519
     * @param $method
520
     *
521
     * @return static
522
     */
523
    protected function findQueryByMethod($method)
524
    {
525
        return $this->queries->first(function ($query) use ($method) {
526
            return $query['method'] == $method;
527
        });
528
    }
529
530
    /**
531
     * Set the grid sort.
532
     *
533
     * @return void
534
     */
535
    protected function setSort()
536
    {
537
        $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...
538
        if (!is_array($this->sort)) {
539
            return;
540
        }
541
542
        if (empty($this->sort['column']) || empty($this->sort['type'])) {
543
            return;
544
        }
545
546
        if (Str::contains($this->sort['column'], '.')) {
547
            $this->setRelationSort($this->sort['column']);
548
        } else {
549
            $this->resetOrderBy();
550
551
            // get column. if contains "cast", set set column as cast
552
            if (!empty($this->sort['cast'])) {
553
                $column = "CAST({$this->sort['column']} AS {$this->sort['cast']}) {$this->sort['type']}";
554
                $method = 'orderByRaw';
555
                $arguments = [$column];
556
            } else {
557
                $column = $this->sort['column'];
558
                $method = 'orderBy';
559
                $arguments = [$column, $this->sort['type']];
560
            }
561
562
            $this->queries->push([
563
                'method'    => $method,
564
                'arguments' => $arguments,
565
            ]);
566
        }
567
    }
568
569
    /**
570
     * Set relation sort.
571
     *
572
     * @param string $column
573
     *
574
     * @return void
575
     */
576
    protected function setRelationSort($column)
577
    {
578
        list($relationName, $relationColumn) = explode('.', $column);
579
580
        if ($this->queries->contains(function ($query) use ($relationName) {
581
            return $query['method'] == 'with' && in_array($relationName, $query['arguments']);
582
        })) {
583
            $relation = $this->model->$relationName();
584
585
            $this->queries->push([
586
                'method'    => 'select',
587
                'arguments' => [$this->model->getTable().'.*'],
588
            ]);
589
590
            $this->queries->push([
591
                'method'    => 'join',
592
                'arguments' => $this->joinParameters($relation),
593
            ]);
594
595
            $this->resetOrderBy();
596
597
            $this->queries->push([
598
                'method'    => 'orderBy',
599
                'arguments' => [
600
                    $relation->getRelated()->getTable().'.'.$relationColumn,
601
                    $this->sort['type'],
602
                ],
603
            ]);
604
        }
605
    }
606
607
    /**
608
     * Reset orderBy query.
609
     *
610
     * @return void
611
     */
612
    public function resetOrderBy()
613
    {
614
        $this->queries = $this->queries->reject(function ($query) {
615
            return $query['method'] == 'orderBy' || $query['method'] == 'orderByDesc';
616
        });
617
    }
618
619
    /**
620
     * Build join parameters for related model.
621
     *
622
     * `HasOne` and `BelongsTo` relation has different join parameters.
623
     *
624
     * @param Relation $relation
625
     *
626
     * @throws \Exception
627
     *
628
     * @return array
629
     */
630
    protected function joinParameters(Relation $relation)
631
    {
632
        $relatedTable = $relation->getRelated()->getTable();
633
634
        if ($relation instanceof BelongsTo) {
635
            $foreignKeyMethod = (app()->version() < '5.8.0') ? 'getForeignKey' : 'getForeignKeyName';
636
637
            return [
638
                $relatedTable,
639
                $relation->{$foreignKeyMethod}(),
640
                '=',
641
                $relatedTable.'.'.$relation->getRelated()->getKeyName(),
642
            ];
643
        }
644
645
        if ($relation instanceof HasOne) {
646
            return [
647
                $relatedTable,
648
                $relation->getQualifiedParentKeyName(),
649
                '=',
650
                $relation->getQualifiedForeignKeyName(),
651
            ];
652
        }
653
654
        throw new \Exception('Related sortable only support `HasOne` and `BelongsTo` relation.');
655
    }
656
657
    /**
658
     * @param string $method
659
     * @param array  $arguments
660
     *
661
     * @return $this
662
     */
663
    public function __call($method, $arguments)
664
    {
665
        $this->queries->push([
666
            'method'    => $method,
667
            'arguments' => $arguments,
668
        ]);
669
670
        return $this;
671
    }
672
673
    /**
674
     * Set the relationships that should be eager loaded.
675
     *
676
     * @param mixed $relations
677
     *
678
     * @return $this|Model
679
     */
680
    public function with($relations)
681
    {
682
        if (is_array($relations)) {
683
            if (Arr::isAssoc($relations)) {
684
                $relations = array_keys($relations);
685
            }
686
687
            $this->eagerLoads = array_merge($this->eagerLoads, $relations);
688
        }
689
690
        if (is_string($relations)) {
691
            if (Str::contains($relations, '.')) {
692
                $relations = explode('.', $relations)[0];
693
            }
694
695
            if (Str::contains($relations, ':')) {
696
                $relations = explode(':', $relations)[0];
697
            }
698
699
            if (in_array($relations, $this->eagerLoads)) {
700
                return $this;
701
            }
702
703
            $this->eagerLoads[] = $relations;
704
        }
705
706
        return $this->__call('with', (array) $relations);
707
    }
708
709
    /**
710
     * @param $key
711
     *
712
     * @return mixed
713
     */
714
    public function __get($key)
715
    {
716
        $data = $this->buildData();
717
718
        if (array_key_exists($key, $data)) {
719
            return $data[$key];
720
        }
721
    }
722
}
723