Completed
Push — master ( 3fda7f...fe99a9 )
by Song
02:46
created

Model::getQueryBuilder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 18
rs 9.6666
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
     * Get the eloquent model of the grid model.
134
     *
135
     * @return EloquentModel
136
     */
137
    public function eloquent()
138
    {
139
        return $this->model;
140
    }
141
142
    /**
143
     * Enable or disable pagination.
144
     *
145
     * @param bool $use
146
     */
147
    public function usePaginate($use = true)
148
    {
149
        $this->usePaginate = $use;
150
    }
151
152
    /**
153
     * Get the query string variable used to store the per-page.
154
     *
155
     * @return string
156
     */
157
    public function getPerPageName()
158
    {
159
        return $this->perPageName;
160
    }
161
162
    /**
163
     * Set the query string variable used to store the per-page.
164
     *
165
     * @param string $name
166
     *
167
     * @return $this
168
     */
169
    public function setPerPageName($name)
170
    {
171
        $this->perPageName = $name;
172
173
        return $this;
174
    }
175
176
    /**
177
     * Get the query string variable used to store the sort.
178
     *
179
     * @return string
180
     */
181
    public function getSortName()
182
    {
183
        return $this->sortName;
184
    }
185
186
    /**
187
     * Set the query string variable used to store the sort.
188
     *
189
     * @param string $name
190
     *
191
     * @return $this
192
     */
193
    public function setSortName($name)
194
    {
195
        $this->sortName = $name;
196
197
        return $this;
198
    }
199
200
    /**
201
     * Set parent grid instance.
202
     *
203
     * @param Grid $grid
204
     *
205
     * @return $this
206
     */
207
    public function setGrid(Grid $grid)
208
    {
209
        $this->grid = $grid;
210
211
        return $this;
212
    }
213
214
    /**
215
     * Get parent gird instance.
216
     *
217
     * @return Grid
218
     */
219
    public function getGrid()
220
    {
221
        return $this->grid;
222
    }
223
224
    /**
225
     * @param Relation $relation
226
     *
227
     * @return $this
228
     */
229
    public function setRelation(Relation $relation)
230
    {
231
        $this->relation = $relation;
232
233
        return $this;
234
    }
235
236
    /**
237
     * @return Relation
238
     */
239
    public function getRelation()
240
    {
241
        return $this->relation;
242
    }
243
244
    /**
245
     * Get constraints.
246
     *
247
     * @return array|bool
248
     */
249
    public function getConstraints()
250
    {
251
        if ($this->relation instanceof HasMany) {
252
            return [
253
                $this->relation->getForeignKeyName() => $this->relation->getParentKey(),
254
            ];
255
        }
256
257
        return false;
258
    }
259
260
    /**
261
     * Set collection callback.
262
     *
263
     * @param \Closure $callback
264
     *
265
     * @return $this
266
     */
267
    public function collection(\Closure $callback = null)
268
    {
269
        $this->collectionCallback = $callback;
270
271
        return $this;
272
    }
273
274
    /**
275
     * Build.
276
     *
277
     * @param bool $toArray
278
     *
279
     * @return array|Collection|mixed
280
     */
281
    public function buildData($toArray = true)
282
    {
283
        if (empty($this->data)) {
284
            $collection = $this->get();
285
286
            if ($this->collectionCallback) {
287
                $collection = call_user_func($this->collectionCallback, $collection);
288
            }
289
290
            if ($toArray) {
291
                $this->data = $collection->toArray();
292
            } else {
293
                $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...
294
            }
295
        }
296
297
        return $this->data;
298
    }
299
300
    /**
301
     * @param callable $callback
302
     * @param int      $count
303
     *
304
     * @return bool
305
     */
306
    public function chunk($callback, $count = 100)
307
    {
308
        if ($this->usePaginate) {
309
            return $this->buildData(false)->chunk($count)->each($callback);
310
        }
311
312
        $this->setSort();
313
314
        $this->queries->reject(function ($query) {
315
            return $query['method'] == 'paginate';
316
        })->each(function ($query) {
317
            $this->model = $this->model->{$query['method']}(...$query['arguments']);
318
        });
319
320
        return $this->model->chunk($count, $callback);
321
    }
322
323
    /**
324
     * Add conditions to grid model.
325
     *
326
     * @param array $conditions
327
     *
328
     * @return $this
329
     */
330
    public function addConditions(array $conditions)
331
    {
332
        foreach ($conditions as $condition) {
333
            call_user_func_array([$this, key($condition)], current($condition));
334
        }
335
336
        return $this;
337
    }
338
339
    /**
340
     * Get table of the model.
341
     *
342
     * @return string
343
     */
344
    public function getTable()
345
    {
346
        return $this->model->getTable();
347
    }
348
349
    /**
350
     * @throws \Exception
351
     *
352
     * @return Collection
353
     */
354
    protected function get()
355
    {
356
        if ($this->model instanceof LengthAwarePaginator) {
357
            return $this->model;
358
        }
359
360
        if ($this->relation) {
361
            $this->model = $this->relation->getQuery();
362
        }
363
364
        $this->setSort();
365
        $this->setPaginate();
366
367
        $this->queries->unique()->each(function ($query) {
368
            $this->model = call_user_func_array([$this->model, $query['method']], $query['arguments']);
369
        });
370
371
        if ($this->model instanceof Collection) {
372
            return $this->model;
373
        }
374
375
        if ($this->model instanceof LengthAwarePaginator) {
376
            $this->handleInvalidPage($this->model);
377
378
            return $this->model->getCollection();
379
        }
380
381
        throw new \Exception('Grid query error');
382
    }
383
384
    /**
385
     * @return \Illuminate\Database\Eloquent\Builder|EloquentModel
386
     */
387
    public function getQueryBuilder()
388
    {
389
        if ($this->relation) {
390
            return $this->relation->getQuery();
391
        }
392
393
        $this->setSort();
394
395
        $queryBuilder = $this->originalModel;
396
397
        $this->queries->reject(function ($query) {
398
            return in_array($query['method'], ['get', 'paginate']);
399
        })->each(function ($query) use (&$queryBuilder) {
400
            $queryBuilder = $queryBuilder->{$query['method']}(...$query['arguments']);
401
        });
402
403
        return $queryBuilder;
404
    }
405
406
    /**
407
     * If current page is greater than last page, then redirect to last page.
408
     *
409
     * @param LengthAwarePaginator $paginator
410
     *
411
     * @return void
412
     */
413
    protected function handleInvalidPage(LengthAwarePaginator $paginator)
414
    {
415
        if ($paginator->lastPage() && $paginator->currentPage() > $paginator->lastPage()) {
416
            $lastPageUrl = Request::fullUrlWithQuery([
417
                $paginator->getPageName() => $paginator->lastPage(),
418
            ]);
419
420
            Pjax::respond(redirect($lastPageUrl));
421
        }
422
    }
423
424
    /**
425
     * Set the grid paginate.
426
     *
427
     * @return void
428
     */
429
    protected function setPaginate()
430
    {
431
        $paginate = $this->findQueryByMethod('paginate');
432
433
        $this->queries = $this->queries->reject(function ($query) {
434
            return $query['method'] == 'paginate';
435
        });
436
437
        if (!$this->usePaginate) {
438
            $query = [
439
                'method'    => 'get',
440
                'arguments' => [],
441
            ];
442
        } else {
443
            $query = [
444
                'method'    => 'paginate',
445
                '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...
446
            ];
447
        }
448
449
        $this->queries->push($query);
450
    }
451
452
    /**
453
     * Resolve perPage for pagination.
454
     *
455
     * @param array|null $paginate
456
     *
457
     * @return array
458
     */
459
    protected function resolvePerPage($paginate)
460
    {
461
        if ($perPage = app('request')->input($this->perPageName)) {
462
            if (is_array($paginate)) {
463
                $paginate['arguments'][0] = (int) $perPage;
464
465
                return $paginate['arguments'];
466
            }
467
468
            $this->perPage = (int) $perPage;
469
        }
470
471
        if (isset($paginate['arguments'][0])) {
472
            return $paginate['arguments'];
473
        }
474
475
        if ($name = $this->grid->getName()) {
476
            return [$this->perPage, null, "{$name}_page"];
477
        }
478
479
        return [$this->perPage];
480
    }
481
482
    /**
483
     * Find query by method name.
484
     *
485
     * @param $method
486
     *
487
     * @return static
488
     */
489
    protected function findQueryByMethod($method)
490
    {
491
        return $this->queries->first(function ($query) use ($method) {
492
            return $query['method'] == $method;
493
        });
494
    }
495
496
    /**
497
     * Set the grid sort.
498
     *
499
     * @return void
500
     */
501
    protected function setSort()
502
    {
503
        $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...
504
        if (!is_array($this->sort)) {
505
            return;
506
        }
507
508
        if (empty($this->sort['column']) || empty($this->sort['type'])) {
509
            return;
510
        }
511
512
        if (str_contains($this->sort['column'], '.')) {
513
            $this->setRelationSort($this->sort['column']);
514
        } else {
515
            $this->resetOrderBy();
516
517
            $this->queries->push([
518
                'method'    => 'orderBy',
519
                'arguments' => [$this->sort['column'], $this->sort['type']],
520
            ]);
521
        }
522
    }
523
524
    /**
525
     * Set relation sort.
526
     *
527
     * @param string $column
528
     *
529
     * @return void
530
     */
531
    protected function setRelationSort($column)
532
    {
533
        list($relationName, $relationColumn) = explode('.', $column);
534
535
        if ($this->queries->contains(function ($query) use ($relationName) {
536
            return $query['method'] == 'with' && in_array($relationName, $query['arguments']);
537
        })) {
538
            $relation = $this->model->$relationName();
539
540
            $this->queries->push([
541
                'method'    => 'join',
542
                'arguments' => $this->joinParameters($relation),
543
            ]);
544
545
            $this->resetOrderBy();
546
547
            $this->queries->push([
548
                'method'    => 'orderBy',
549
                'arguments' => [
550
                    $relation->getRelated()->getTable().'.'.$relationColumn,
551
                    $this->sort['type'],
552
                ],
553
            ]);
554
        }
555
    }
556
557
    /**
558
     * Reset orderBy query.
559
     *
560
     * @return void
561
     */
562
    public function resetOrderBy()
563
    {
564
        $this->queries = $this->queries->reject(function ($query) {
565
            return $query['method'] == 'orderBy' || $query['method'] == 'orderByDesc';
566
        });
567
    }
568
569
    /**
570
     * Build join parameters for related model.
571
     *
572
     * `HasOne` and `BelongsTo` relation has different join parameters.
573
     *
574
     * @param Relation $relation
575
     *
576
     * @throws \Exception
577
     *
578
     * @return array
579
     */
580
    protected function joinParameters(Relation $relation)
581
    {
582
        $relatedTable = $relation->getRelated()->getTable();
583
584
        if ($relation instanceof BelongsTo) {
585
            return [
586
                $relatedTable,
587
                $relation->getForeignKey(),
588
                '=',
589
                $relatedTable.'.'.$relation->getRelated()->getKeyName(),
590
            ];
591
        }
592
593
        if ($relation instanceof HasOne) {
594
            return [
595
                $relatedTable,
596
                $relation->getQualifiedParentKeyName(),
597
                '=',
598
                $relation->getQualifiedForeignKeyName(),
599
            ];
600
        }
601
602
        throw new \Exception('Related sortable only support `HasOne` and `BelongsTo` relation.');
603
    }
604
605
    /**
606
     * @param string $method
607
     * @param array  $arguments
608
     *
609
     * @return $this
610
     */
611
    public function __call($method, $arguments)
612
    {
613
        $this->queries->push([
614
            'method'    => $method,
615
            'arguments' => $arguments,
616
        ]);
617
618
        return $this;
619
    }
620
621
    /**
622
     * Set the relationships that should be eager loaded.
623
     *
624
     * @param mixed $relations
625
     *
626
     * @return $this|Model
627
     */
628
    public function with($relations)
629
    {
630
        if (is_array($relations)) {
631
            if (Arr::isAssoc($relations)) {
632
                $relations = array_keys($relations);
633
            }
634
635
            $this->eagerLoads = array_merge($this->eagerLoads, $relations);
636
        }
637
638
        if (is_string($relations)) {
639
            if (Str::contains($relations, '.')) {
640
                $relations = explode('.', $relations)[0];
641
            }
642
643
            if (Str::contains($relations, ':')) {
644
                $relations = explode(':', $relations)[0];
645
            }
646
647
            if (in_array($relations, $this->eagerLoads)) {
648
                return $this;
649
            }
650
651
            $this->eagerLoads[] = $relations;
652
        }
653
654
        return $this->__call('with', (array) $relations);
655
    }
656
657
    /**
658
     * @param $key
659
     *
660
     * @return mixed
661
     */
662
    public function __get($key)
663
    {
664
        $data = $this->buildData();
665
666
        if (array_key_exists($key, $data)) {
667
            return $data[$key];
668
        }
669
    }
670
}
671