Completed
Push — master ( 24b53c...2117a3 )
by Song
02:20
created

Model::addConditions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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