Completed
Push — master ( 024746...0b49f1 )
by Song
04:00
created

Model::__get()   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\Collection;
14
use Illuminate\Support\Facades\Input;
15
use Illuminate\Support\Facades\Request;
16
17
class Model
18
{
19
    /**
20
     * Eloquent model instance of the grid model.
21
     *
22
     * @var EloquentModel
23
     */
24
    protected $model;
25
26
    /**
27
     * Array of queries of the eloquent model.
28
     *
29
     * @var \Illuminate\Support\Collection
30
     */
31
    protected $queries;
32
33
    /**
34
     * Sort parameters of the model.
35
     *
36
     * @var array
37
     */
38
    protected $sort;
39
40
    /**
41
     * @var array
42
     */
43
    protected $data = [];
44
45
    /*
46
     * 20 items per page as default.
47
     *
48
     * @var int
49
     */
50
    protected $perPage = 20;
51
52
    /**
53
     * If the model use pagination.
54
     *
55
     * @var bool
56
     */
57
    protected $usePaginate = true;
58
59
    /**
60
     * The query string variable used to store the per-page.
61
     *
62
     * @var string
63
     */
64
    protected $perPageName = 'per_page';
65
66
    /**
67
     * The query string variable used to store the sort.
68
     *
69
     * @var string
70
     */
71
    protected $sortName = '_sort';
72
73
    /**
74
     * Collection callback.
75
     *
76
     * @var \Closure
77
     */
78
    protected $collectionCallback;
79
80
    /**
81
     * @var Grid
82
     */
83
    protected $grid;
84
85
    /**
86
     * @var Relation
87
     */
88
    protected $relation;
89
90
    /**
91
     * Create a new grid model instance.
92
     *
93
     * @param EloquentModel $model
94
     */
95
    public function __construct(EloquentModel $model)
96
    {
97
        $this->model = $model;
98
99
        $this->queries = collect();
100
101
//        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...
102
    }
103
104
    /**
105
     * Don't snake case attributes.
106
     *
107
     * @param EloquentModel $model
108
     *
109
     * @return void
110
     */
111
    protected static function doNotSnakeAttributes(EloquentModel $model)
112
    {
113
        $class = get_class($model);
114
115
        $class::$snakeAttributes = false;
116
    }
117
118
    /**
119
     * Get the eloquent model of the grid model.
120
     *
121
     * @return EloquentModel
122
     */
123
    public function eloquent()
124
    {
125
        return $this->model;
126
    }
127
128
    /**
129
     * Enable or disable pagination.
130
     *
131
     * @param bool $use
132
     */
133
    public function usePaginate($use = true)
134
    {
135
        $this->usePaginate = $use;
136
    }
137
138
    /**
139
     * Get the query string variable used to store the per-page.
140
     *
141
     * @return string
142
     */
143
    public function getPerPageName()
144
    {
145
        return $this->perPageName;
146
    }
147
148
    /**
149
     * Set the query string variable used to store the per-page.
150
     *
151
     * @param string $name
152
     *
153
     * @return $this
154
     */
155
    public function setPerPageName($name)
156
    {
157
        $this->perPageName = $name;
158
159
        return $this;
160
    }
161
162
    /**
163
     * Get the query string variable used to store the sort.
164
     *
165
     * @return string
166
     */
167
    public function getSortName()
168
    {
169
        return $this->sortName;
170
    }
171
172
    /**
173
     * Set the query string variable used to store the sort.
174
     *
175
     * @param string $name
176
     *
177
     * @return $this
178
     */
179
    public function setSortName($name)
180
    {
181
        $this->sortName = $name;
182
183
        return $this;
184
    }
185
186
    /**
187
     * @param Grid $grid
188
     * @return $this
189
     */
190
    public function setGrid(Grid $grid)
191
    {
192
        $this->grid = $grid;
193
194
        return $this;
195
    }
196
197
    /**
198
     * @param Relation $relation
199
     * @return $this
200
     */
201
    public function setRelation(Relation $relation)
202
    {
203
        $this->relation = $relation;
204
205
        return $this;
206
    }
207
208
    /**
209
     * @return Relation
210
     */
211
    public function getRelation()
212
    {
213
        return $this->relation;
214
    }
215
216
    /**
217
     * Get constraints.
218
     *
219
     * @return array|bool
220
     */
221
    public function getConstraints()
222
    {
223
        if ($this->relation instanceof HasMany) {
224
            return [
225
                $this->relation->getForeignKeyName() => $this->relation->getParentKey(),
226
            ];
227
        }
228
229
        return false;
230
    }
231
232
    /**
233
     * Set collection callback.
234
     *
235
     * @param \Closure $callback
236
     *
237
     * @return $this
238
     */
239
    public function collection(\Closure $callback = null)
240
    {
241
        $this->collectionCallback = $callback;
242
243
        return $this;
244
    }
245
246
    /**
247
     * Build.
248
     *
249
     * @param bool $toArray
250
     *
251
     * @return array|Collection|mixed
252
     */
253
    public function buildData($toArray = true)
254
    {
255
        if (empty($this->data)) {
256
            $collection = $this->get();
257
258
            if ($this->collectionCallback) {
259
                $collection = call_user_func($this->collectionCallback, $collection);
260
            }
261
262
            if ($toArray) {
263
                $this->data = $collection->toArray();
264
            } else {
265
                $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...
266
            }
267
        }
268
269
        return $this->data;
270
    }
271
272
    /**
273
     * @param callable $callback
274
     * @param int      $count
275
     *
276
     * @return bool
277
     */
278
    public function chunk($callback, $count = 100)
279
    {
280
        if ($this->usePaginate) {
281
            return $this->buildData(false)->chunk($count)->each($callback);
282
        }
283
284
        $this->setSort();
285
286
        $this->queries->reject(function ($query) {
287
            return $query['method'] == 'paginate';
288
        })->each(function ($query) {
289
            $this->model = $this->model->{$query['method']}(...$query['arguments']);
290
        });
291
292
        return $this->model->chunk($count, $callback);
293
    }
294
295
    /**
296
     * Add conditions to grid model.
297
     *
298
     * @param array $conditions
299
     *
300
     * @return $this
301
     */
302
    public function addConditions(array $conditions)
303
    {
304
        foreach ($conditions as $condition) {
305
            call_user_func_array([$this, key($condition)], current($condition));
306
        }
307
308
        return $this;
309
    }
310
311
    /**
312
     * Get table of the model.
313
     *
314
     * @return string
315
     */
316
    public function getTable()
317
    {
318
        return $this->model->getTable();
319
    }
320
321
    /**
322
     * @throws \Exception
323
     *
324
     * @return Collection
325
     */
326
    protected function get()
327
    {
328
        if ($this->model instanceof LengthAwarePaginator) {
329
            return $this->model;
330
        }
331
332
        if ($this->relation) {
333
            $this->model = $this->relation->getQuery();
334
        }
335
336
        $this->setSort();
337
        $this->setPaginate();
338
339
        $this->queries->unique()->each(function ($query) {
340
            $this->model = call_user_func_array([$this->model, $query['method']], $query['arguments']);
341
        });
342
343
        if ($this->model instanceof Collection) {
344
            return $this->model;
345
        }
346
347
        if ($this->model instanceof LengthAwarePaginator) {
348
            $this->handleInvalidPage($this->model);
349
350
            return $this->model->getCollection();
351
        }
352
353
        throw new \Exception('Grid query error');
354
    }
355
356
    /**
357
     * If current page is greater than last page, then redirect to last page.
358
     *
359
     * @param LengthAwarePaginator $paginator
360
     *
361
     * @return void
362
     */
363
    protected function handleInvalidPage(LengthAwarePaginator $paginator)
364
    {
365
        if ($paginator->lastPage() && $paginator->currentPage() > $paginator->lastPage()) {
366
            $lastPageUrl = Request::fullUrlWithQuery([
367
                $paginator->getPageName() => $paginator->lastPage(),
368
            ]);
369
370
            Pjax::respond(redirect($lastPageUrl));
371
        }
372
    }
373
374
    /**
375
     * Set the grid paginate.
376
     *
377
     * @return void
378
     */
379
    protected function setPaginate()
380
    {
381
        $paginate = $this->findQueryByMethod('paginate');
382
383
        $this->queries = $this->queries->reject(function ($query) {
384
            return $query['method'] == 'paginate';
385
        });
386
387
        if (!$this->usePaginate) {
388
            $query = [
389
                'method'    => 'get',
390
                'arguments' => [],
391
            ];
392
        } else {
393
            $query = [
394
                'method'    => 'paginate',
395
                '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...
396
            ];
397
        }
398
399
        $this->queries->push($query);
400
    }
401
402
    /**
403
     * Resolve perPage for pagination.
404
     *
405
     * @param array|null $paginate
406
     *
407
     * @return array
408
     */
409
    protected function resolvePerPage($paginate)
410
    {
411
        if ($perPage = app('request')->input($this->perPageName)) {
412
            if (is_array($paginate)) {
413
                $paginate['arguments'][0] = (int) $perPage;
414
415
                return $paginate['arguments'];
416
            }
417
418
            $this->perPage = (int) $perPage;
419
        }
420
421
        if (isset($paginate['arguments'][0])) {
422
            return $paginate['arguments'];
423
        }
424
425
        if ($name = $this->grid->getName()) {
426
            return [$this->perPage, null, "{$name}_page"];
427
        }
428
429
        return [$this->perPage];
430
    }
431
432
    /**
433
     * Find query by method name.
434
     *
435
     * @param $method
436
     *
437
     * @return static
438
     */
439
    protected function findQueryByMethod($method)
440
    {
441
        return $this->queries->first(function ($query) use ($method) {
442
            return $query['method'] == $method;
443
        });
444
    }
445
446
    /**
447
     * Set the grid sort.
448
     *
449
     * @return void
450
     */
451
    protected function setSort()
452
    {
453
        $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...
454
        if (!is_array($this->sort)) {
455
            return;
456
        }
457
458
        if (empty($this->sort['column']) || empty($this->sort['type'])) {
459
            return;
460
        }
461
462
        if (str_contains($this->sort['column'], '.')) {
463
            $this->setRelationSort($this->sort['column']);
464
        } else {
465
            $this->resetOrderBy();
466
467
            $this->queries->push([
468
                'method'    => 'orderBy',
469
                'arguments' => [$this->sort['column'], $this->sort['type']],
470
            ]);
471
        }
472
    }
473
474
    /**
475
     * Set relation sort.
476
     *
477
     * @param string $column
478
     *
479
     * @return void
480
     */
481
    protected function setRelationSort($column)
482
    {
483
        list($relationName, $relationColumn) = explode('.', $column);
484
485
        if ($this->queries->contains(function ($query) use ($relationName) {
486
            return $query['method'] == 'with' && in_array($relationName, $query['arguments']);
487
        })) {
488
            $relation = $this->model->$relationName();
489
490
            $this->queries->push([
491
                'method'    => 'join',
492
                'arguments' => $this->joinParameters($relation),
493
            ]);
494
495
            $this->resetOrderBy();
496
497
            $this->queries->push([
498
                'method'    => 'orderBy',
499
                'arguments' => [
500
                    $relation->getRelated()->getTable().'.'.$relationColumn,
501
                    $this->sort['type'],
502
                ],
503
            ]);
504
        }
505
    }
506
507
    /**
508
     * Reset orderBy query.
509
     *
510
     * @return void
511
     */
512
    public function resetOrderBy()
513
    {
514
        $this->queries = $this->queries->reject(function ($query) {
515
            return $query['method'] == 'orderBy' || $query['method'] == 'orderByDesc';
516
        });
517
    }
518
519
    /**
520
     * Build join parameters for related model.
521
     *
522
     * `HasOne` and `BelongsTo` relation has different join parameters.
523
     *
524
     * @param Relation $relation
525
     *
526
     * @throws \Exception
527
     *
528
     * @return array
529
     */
530
    protected function joinParameters(Relation $relation)
531
    {
532
        $relatedTable = $relation->getRelated()->getTable();
533
534
        if ($relation instanceof BelongsTo) {
535
            return [
536
                $relatedTable,
537
                $relation->getForeignKey(),
538
                '=',
539
                $relatedTable.'.'.$relation->getRelated()->getKeyName(),
540
            ];
541
        }
542
543
        if ($relation instanceof HasOne) {
544
            return [
545
                $relatedTable,
546
                $relation->getQualifiedParentKeyName(),
547
                '=',
548
                $relation->getQualifiedForeignKeyName(),
549
            ];
550
        }
551
552
        throw new \Exception('Related sortable only support `HasOne` and `BelongsTo` relation.');
553
    }
554
555
    /**
556
     * @param string $method
557
     * @param array  $arguments
558
     *
559
     * @return $this
560
     */
561
    public function __call($method, $arguments)
562
    {
563
        $this->queries->push([
564
            'method'    => $method,
565
            'arguments' => $arguments,
566
        ]);
567
568
        return $this;
569
    }
570
571
    /**
572
     * @param $key
573
     *
574
     * @return mixed
575
     */
576
    public function __get($key)
577
    {
578
        $data = $this->buildData();
579
580
        if (array_key_exists($key, $data)) {
581
            return $data[$key];
582
        }
583
    }
584
}
585