Completed
Pull Request — master (#1281)
by
unknown
02:32
created

Model::chunk()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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