Completed
Push — master ( b0d73a...d2854c )
by Song
02:52
created

Model::with()   B

Complexity

Conditions 7
Paths 27

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

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