BaseRepository::update()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 11

Duplication

Lines 18
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 2
dl 18
loc 18
ccs 0
cts 13
cp 0
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/*
3
 * This file is part of the Laravel Platfourm package.
4
 *
5
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Longman\Platfourm\Repository\Eloquent;
12
13
use Closure;
14
use Exception;
15
use Illuminate\Container\Container as Application;
16
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
17
use Illuminate\Database\Eloquent\Model;
18
use Illuminate\Pagination\LengthAwarePaginator;
19
use Illuminate\Support\Collection;
20
use Longman\Platfourm\Contracts\Repository\Criteria;
21
use Longman\Platfourm\Contracts\Repository\Presentable;
22
use Longman\Platfourm\Contracts\Repository\Presenter;
23
use Longman\Platfourm\Contracts\Repository\Repository;
24
use Longman\Platfourm\Contracts\Repository\RepositoryCriteria;
25
use Longman\Platfourm\Repository\Events\RepositoryEntityCreated;
26
use Longman\Platfourm\Repository\Events\RepositoryEntityDeleted;
27
use Longman\Platfourm\Repository\Events\RepositoryEntityRestored;
28
use Longman\Platfourm\Repository\Events\RepositoryEntityUpdated;
29
use Longman\Platfourm\Repository\Exceptions\RepositoryException;
30
31
/**
32
 * Class BaseRepository.
33
 */
34
abstract class BaseRepository implements Repository, RepositoryCriteria
35
{
36
    /**
37
     * @var Application
38
     */
39
    protected $app;
40
41
    /**
42
     * @var Model
43
     */
44
    protected $model;
45
46
    /**
47
     * @var Presenter
48
     */
49
    protected $presenter;
50
51
    /**
52
     * Validation Rules.
53
     *
54
     * @var array
55
     */
56
    protected $rules = null;
57
58
    /**
59
     * Collection of Criteria.
60
     *
61
     * @var Collection
62
     */
63
    protected $criteria;
64
65
    /**
66
     * @var bool
67
     */
68
    protected $skipCriteria = false;
69
70
    /**
71
     * @var bool
72
     */
73
    protected $skipPresenter = false;
74
75
    /**
76
     * @var \Closure
77
     */
78
    protected $scopeQuery = null;
79
80
    protected $availableSortDirections = ['asc', 'desc'];
81
82
    /**
83
     * @param Application $app
84
     */
85
    public function __construct(Application $app)
86
    {
87
        $this->app      = $app;
88
        $this->criteria = new Collection();
89
        $this->makeModel();
90
        $this->makePresenter();
91
        $this->boot();
92
    }
93
94
    public function boot()
95
    {
96
    }
97
98
    /**
99
     * @throws RepositoryException
100
     */
101
    public function resetModel()
102
    {
103
        $this->makeModel();
104
    }
105
106
    /**
107
     * Specify Model class name.
108
     *
109
     * @return string
110
     */
111
    abstract public function model();
112
113
    /**
114
     * Specify Presenter class name.
115
     *
116
     * @return string
117
     */
118
    public function presenter()
119
    {
120
    }
121
122
    /**
123
     * Set Presenter.
124
     *
125
     * @param  $presenter
126
     * @return $this
127
     */
128
    public function setPresenter($presenter)
129
    {
130
        $this->makePresenter($presenter);
131
132
        return $this;
133
    }
134
135
    /**
136
     * @throws RepositoryException
137
     * @return Model
138
     */
139
    public function makeModel()
140
    {
141
        $model = $this->app->make($this->model());
142
143
        if (!$model instanceof Model) {
144
            throw new RepositoryException("Class {$this->model()} must be an instance of Illuminate\\Database\\Eloquent\\Model");
145
        }
146
147
        return $this->model = $model;
148
    }
149
150
    /**
151
     * @param  null $presenter
152
     * @throws RepositoryException
153
     * @return Presenter
154
     */
155
    public function makePresenter($presenter = null)
156
    {
157
        $presenter = !is_null($presenter) ? $presenter : $this->presenter();
158
159
        if (!is_null($presenter)) {
160
            $this->presenter = is_string($presenter) ? $this->app->make($presenter) : $presenter;
161
162
            if (!$this->presenter instanceof Presenter) {
163
                throw new RepositoryException("Class {$presenter} must be an instance of Longman\Platfourm\\Contracts\\Repository\\Presenter");
164
            }
165
166
            return $this->presenter;
167
        }
168
    }
169
170
    /**
171
     * Query Scope.
172
     *
173
     * @param  \Closure $scope
174
     * @return $this
175
     */
176
    public function scopeQuery(\Closure $scope)
177
    {
178
        $this->scopeQuery = $scope;
179
180
        return $this;
181
    }
182
183
    /**
184
     * Retrieve count of records.
185
     *
186
     * @param  array $columns
0 ignored issues
show
Bug introduced by
There is no parameter named $columns. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
187
     * @return mixed
188
     */
189
    public function count()
190
    {
191
        $this->applyCriteria();
192
        $this->applyScope();
193
194
        if ($this->model instanceof EloquentBuilder) {
195
            $results = $this->model->count();
196
        } else {
197
            $results = $this->model->count();
198
        }
199
200
        $this->resetModel();
201
202
        return $results;
203
    }
204
205
    /**
206
     * Retrieve all data of repository.
207
     *
208
     * @param  array $columns
209
     * @return mixed
210
     */
211
    public function all($columns = ['*'])
212
    {
213
        $this->applyCriteria();
214
        $this->applyScope();
215
216
        if ($this->model instanceof EloquentBuilder) {
217
            $results = $this->model->get($columns);
218
        } else {
219
            $results = $this->model->all($columns);
220
        }
221
222
        $this->resetModel();
223
224
        return $this->parserResult($results);
225
    }
226
227
    public function findAll($columns = ['*'])
228
    {
229
230
        return $this->all($columns);
231
    }
232
233
    /**
234
     * Retrieve first data of repository.
235
     *
236
     * @param  array $columns
237
     * @return mixed
238
     */
239
    public function first($columns = ['*'])
240
    {
241
        $this->applyCriteria();
242
        $this->applyScope();
243
        $results = $this->model->first($columns);
244
        $this->resetModel();
245
246
        return $this->parserResult($results);
247
    }
248
249
    /**
250
     * Get entities
251
     *
252
     * @param string     $columns
253
     * @param array|null $options
254
     * @param null       $limit
255
     * @param null       $page
256
     * @param null       $sortBy
257
     * @return mixed
258
     */
259
    public function findBy($columns = '*', array $options = null, $limit = null, $page = null, $sortBy = null)
260
    {
261
        $this->applyCriteria();
262
        $this->applyScope();
263
264
        $model = $this->model;
265
266
        if (!is_array($columns)) {
267
            $columns = $this->parseColumns($columns);
268
        }
269
270
        if (!is_null($options)) {
271
            $model = $this->setWheres($model, $options);
272
        }
273
274
        if (is_string($sortBy)) {
275
            $sortBy = $this->parseSortByString($sortBy);
276
        }
277
278
        if (!empty($sortBy)) {
279
            if ($this->model instanceof EloquentBuilder) {
280
                $sortableFields = $this->model->getModel()->getSortableFields();
281
            } else {
282
                $sortableFields = $this->model->getSortableFields();
283
            }
284
            foreach ($sortBy as $field => $direction) {
285
                if (!in_array($field, $sortableFields)) {
286
                    continue;
287
                }
288
                if (!in_array($direction, $this->availableSortDirections)) {
289
                    $direction = 'asc';
290
                }
291
                $model = $model->orderBy($field, $direction);
292
            }
293
        }
294
295
        if (is_null($limit)) {
296
            $data    = $model->get($columns);
297
            $total   = $data->count();
298
            $results = new LengthAwarePaginator($data, $total, 1, 1, []);
299
        } else {
300
            $results = $model->paginate($limit, $columns, 'page', $page);
301
        }
302
        $this->resetModel();
303
304
        return $this->parserResult($results);
305
    }
306
307
    protected function parseColumns($columns)
308
    {
309
        if ($columns == '*') {
310
            return ['*'];
311
        }
312
313
        return explode(',', $columns);
314
    }
315
316
    protected function setWheres($model, array $options = null)
317
    {
318
319
        foreach ($options as $k => $v) {
0 ignored issues
show
Bug introduced by
The expression $options of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
320
            if ($v instanceof Closure) {
321
                $model = $model->where($v);
322
                continue;
323
            }
324
325
            $boolean = 'and';
326
327
            $ex = explode(':', $k);
328
329
            if (empty($ex[1])) {
330
                $operator = '=';
331
                $field    = trim($ex[0]);
332
            } else {
333
                $operator = trim($ex[0]);
334
                $field    = trim($ex[1]);
335
            }
336
337
            switch ($operator) {
338
                case 'in':
339
                    $model = $model->whereIn($field, $v, $boolean);
340
                    break;
341
342
                case 'not_in':
343
                    $model = $model->whereNotIn($field, $v, $boolean);
344
                    break;
345
346
                case '=':
347
                case '<':
348
                case '>':
349
                case '<=':
350
                case '>=':
351
                case '<>':
352
                case '!=':
353
                    $model = $model->where($field, $operator, $v, $boolean);
354
                    break;
355
356
                case 'not':
357
                    $model = $model->where($field, '!=', $v, $boolean);
358
                    break;
359
360
                case 'like':
361
                    $model = $model->where($field, 'like', $v, $boolean);
362
                    break;
363
364
                case 'not_like':
365
                    $model = $model->where($field, 'not like', $v, $boolean);
366
                    break;
367
368
                case 'between':
369
                    $model = $model->where($field, 'between', $v, $boolean);
370
                    break;
371
            }
372
        }
373
        return $model;
374
    }
375
376
    /**
377
     * Retrieve all data of repository, paginated.
378
     *
379
     * @param  null  $limit
380
     * @param  array $columns
381
     * @return mixed
382
     */
383
    public function paginate($columns = ['*'], $limit = null, $page = null, $sortBy = [])
384
    {
385
        $this->applyCriteria();
386
        $this->applyScope();
387
        $limit = is_null($limit) ? config('database.pagination.limit', 15) : $limit;
388
389
        if (is_string($sortBy)) {
390
            $sortBy = $this->parseSortByString($sortBy);
391
        }
392
393
        if (!empty($sortBy)) {
394
            $sortableFields = $model->getSortableFields();
0 ignored issues
show
Bug introduced by
The variable $model does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
395
            foreach ($sortBy as $field => $direction) {
396
                if (!in_array($field, $sortableFields)) {
397
                    continue;
398
                }
399
                if (!in_array($sortDir, $this->availableSortDirections)) {
0 ignored issues
show
Bug introduced by
The variable $sortDir does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
400
                    $sortDir = 'asc';
401
                }
402
                $this->model = $this->model->orderBy($sortField, $sortDir);
0 ignored issues
show
Bug introduced by
The variable $sortField does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
403
            }
404
        }
405
406
        $results = $this->model->paginate($limit, $columns, 'page', $page);
407
        $this->resetModel();
408
409
        return $this->parserResult($results);
410
    }
411
412
    protected function parseSortByString($sortBy)
413
    {
414
        $sortBy = explode(',', $sortBy);
415
        $return = [];
416
        foreach ($sortBy as $sortItem) {
417
            $sortEx = explode(':', $sortItem);
418
            if (empty($sortEx[0])) {
419
                continue;
420
            }
421
            $sortField = trim($sortEx[0]);
422
            $sortDir   = isset($sortEx[1]) ? trim($sortEx[1]) : 'asc';
423
            if (!in_array($sortDir, $this->availableSortDirections)) {
424
                $sortDir = 'asc';
425
            }
426
            $return[$sortField] = $sortDir;
427
        }
428
        return $return;
429
    }
430
431
    /**
432
     * Retrieve all data of repository, simple paginated.
433
     *
434
     * @param  null  $limit
435
     * @param  array $columns
436
     * @return mixed
437
     */
438
    public function simplePaginate($limit = null, $columns = ['*'], $pageName = 'page', $page = null)
439
    {
440
        $this->applyCriteria();
441
        $this->applyScope();
442
        $limit   = is_null($limit) ? config('repository.pagination.limit', 15) : $limit;
443
        $results = $this->model->simplePaginate($limit, $columns, 'page', $page);
444
        $this->resetModel();
445
446
        return $this->parserResult($results);
447
    }
448
449
    /**
450
     * Find data by id.
451
     *
452
     * @param        $id
453
     * @param  array $columns
454
     * @return mixed
455
     */
456
    public function find($id, $columns = ['*'])
457
    {
458
        $this->applyCriteria();
459
        $this->applyScope();
460
        $model = $this->model->findOrFail($id, $columns);
461
        $this->resetModel();
462
463
        return $this->parserResult($model);
464
    }
465
466
    /**
467
     * Find data by id or return new if not exists.
468
     *
469
     * @param        $id
470
     * @param  array $columns
471
     * @return mixed
472
     */
473
    public function findOrNew($id, $columns = ['*'])
474
    {
475
        $this->applyCriteria();
476
        $this->applyScope();
477
        try {
478
            $model = $this->model->findOrFail($id, $columns);
479
        } catch (Exception $e) {
480
            $model = $this->model->newInstance([]);
481
        }
482
483
        $this->resetModel();
484
485
        return $this->parserResult($model);
486
    }
487
488
    /**
489
     * Find data by field and value.
490
     *
491
     * @param        $field
492
     * @param        $value
493
     * @param  array $columns
494
     * @return mixed
495
     */
496 View Code Duplication
    public function findByField($field, $value = null, $columns = ['*'])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
497
    {
498
        $this->applyCriteria();
499
        $this->applyScope();
500
        $model = $this->model->where($field, '=', $value)->get($columns);
501
        $this->resetModel();
502
503
        return $this->parserResult($model);
504
    }
505
506
    /**
507
     * Find data by slug.
508
     *
509
     * @param        $value
510
     * @param  array $columns
511
     * @return mixed
512
     */
513
    public function findBySlug($value = null, $columns = ['*'])
514
    {
515
        $this->applyCriteria();
516
        $this->applyScope();
517
        $model = $this->model->whereSlug($value)->first($columns);
518
        $this->resetModel();
519
520
        return $this->parserResult($model);
521
    }
522
523
    /**
524
     * Find data by multiple fields.
525
     *
526
     * @param  array $where
527
     * @param  array $columns
528
     * @return mixed
529
     */
530
    public function findWhere(array $where, $columns = ['*'])
531
    {
532
        $this->applyCriteria();
533
        $this->applyScope();
534
535
        foreach ($where as $field => $value) {
536
            if (is_array($value)) {
537
                list($field, $condition, $val) = $value;
538
                $this->model = $this->model->where($field, $condition, $val);
539
            } else {
540
                $this->model = $this->model->where($field, '=', $value);
541
            }
542
        }
543
544
        $model = $this->model->get($columns);
545
        $this->resetModel();
546
547
        return $this->parserResult($model);
548
    }
549
550
    /**
551
     * Find data by multiple values in one field.
552
     *
553
     * @param        $field
554
     * @param  array $values
555
     * @param  array $columns
556
     * @return mixed
557
     */
558 View Code Duplication
    public function findWhereIn($field, array $values, $columns = ['*'])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
559
    {
560
        $this->applyCriteria();
561
        $model = $this->model->whereIn($field, $values)->get($columns);
562
        $this->resetModel();
563
564
        return $this->parserResult($model);
565
    }
566
567
    /**
568
     * Find data by excluding multiple values in one field.
569
     *
570
     * @param        $field
571
     * @param  array $values
572
     * @param  array $columns
573
     * @return mixed
574
     */
575 View Code Duplication
    public function findWhereNotIn($field, array $values, $columns = ['*'])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
576
    {
577
        $this->applyCriteria();
578
        $model = $this->model->whereNotIn($field, $values)->get($columns);
579
        $this->resetModel();
580
581
        return $this->parserResult($model);
582
    }
583
584
    /**
585
     * Create a new instance in repository.
586
     *
587
     * @param  array $attributes
588
     * @return mixed
589
     */
590
    public function newInstance(array $attributes)
591
    {
592
        $model = $this->model->newInstance($attributes);
593
594
        $this->resetModel();
595
596
        return $this->parserResult($model);
597
    }
598
599
    /**
600
     * Save a new entity in repository.
601
     *
602
     * @param  array $attributes
603
     * @return mixed
604
     */
605
    public function create(array $attributes)
606
    {
607
        $model = $this->model->newInstance($attributes);
608
        $model->save();
609
        $this->resetModel();
610
611
        event(new RepositoryEntityCreated($this, $model));
612
613
        return $this->parserResult($model);
614
    }
615
616
    /**
617
     * Update a entity in repository by id.
618
     *
619
     * @param  array $attributes
620
     * @param        $id
621
     * @return mixed
622
     */
623 View Code Duplication
    public function update($id, array $attributes)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
624
    {
625
        $this->applyScope();
626
        $_skipPresenter = $this->skipPresenter;
627
628
        $this->skipPresenter(true);
629
630
        $model = $this->model->findOrFail($id);
631
        $model->fill($attributes);
632
        $model->save();
633
634
        $this->skipPresenter($_skipPresenter);
635
        $this->resetModel();
636
637
        event(new RepositoryEntityUpdated($this, $model));
638
639
        return $this->parserResult($model);
640
    }
641
642
    /**
643
     * Delete a entity in repository by id.
644
     *
645
     * @param  $id
646
     * @return int
647
     */
648 View Code Duplication
    public function delete($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
649
    {
650
        $this->applyScope();
651
652
        $_skipPresenter = $this->skipPresenter;
653
        $this->skipPresenter(true);
654
655
        $model         = $this->find($id);
656
        $originalModel = clone $model;
657
658
        $this->skipPresenter($_skipPresenter);
659
        $this->resetModel();
660
661
        $deleted = $model->delete();
0 ignored issues
show
Unused Code introduced by
$deleted is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
662
663
        event(new RepositoryEntityDeleted($this, $originalModel));
664
665
        return $this->parserResult($model);
666
    }
667
668
    /**
669
     * Delete a entity in repository by id.
670
     *
671
     * @param  $id
672
     * @return int
673
     */
674
    public function restore($id)
675
    {
676
        $this->applyScope();
677
        $_skipPresenter = $this->skipPresenter;
678
679
        $this->skipPresenter(true);
680
681
        $model = $this->model->withTrashed()->findOrFail($id);
682
683
        if (!$model->trashed()) {
684
            return $model;
685
        }
686
687
        $restored = $model->restore();
0 ignored issues
show
Unused Code introduced by
$restored is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
688
689
        $this->skipPresenter($_skipPresenter);
690
        $this->resetModel();
691
692
        event(new RepositoryEntityRestored($this, $model));
693
694
        return $this->parserResult($model);
695
    }
696
697
    /**
698
     * Load relations.
699
     *
700
     * @param  array|string $relations
701
     * @return $this
702
     */
703
    public function with($relations)
704
    {
705
        $this->model = $this->model->with($relations);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->model->with($relations) can also be of type object<Illuminate\Database\Eloquent\Builder>. However, the property $model is declared as type object<Illuminate\Database\Eloquent\Model>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
706
707
        return $this;
708
    }
709
710
    /**
711
     * With trashed.
712
     *
713
     * @return $this
714
     */
715
    public function withTrashed()
716
    {
717
        $this->model = $this->model->withTrashed();
718
719
        return $this;
720
    }
721
722
    /**
723
     * Set hidden fields.
724
     *
725
     * @param  array $fields
726
     * @return $this
727
     */
728
    public function hidden(array $fields)
729
    {
730
        $this->model->setHidden($fields);
731
732
        return $this;
733
    }
734
735
    /**
736
     * Set visible fields.
737
     *
738
     * @param  array $fields
739
     * @return $this
740
     */
741
    public function visible(array $fields)
742
    {
743
        $this->model->setVisible($fields);
744
745
        return $this;
746
    }
747
748
    /**
749
     * Push Criteria for filter the query.
750
     *
751
     * @param  Criteria $criteria
752
     * @return $this
753
     */
754
    public function pushCriteria(Criteria $criteria)
755
    {
756
        $this->criteria->push($criteria);
757
758
        return $this;
759
    }
760
761
    /**
762
     * Get Collection of Criteria.
763
     *
764
     * @return Collection
765
     */
766
    public function getCriteria()
767
    {
768
        return $this->criteria;
769
    }
770
771
    /**
772
     * Find data by Criteria.
773
     *
774
     * @param  Criteria $criteria
775
     * @return mixed
776
     */
777
    public function getByCriteria(Criteria $criteria)
778
    {
779
        $this->model = $criteria->apply($this->model, $this);
780
        $results     = $this->model->get();
781
        $this->resetModel();
782
783
        return $this->parserResult($results);
784
    }
785
786
    /**
787
     * Skip Criteria.
788
     *
789
     * @param  bool $status
790
     * @return $this
791
     */
792
    public function skipCriteria($status = true)
793
    {
794
        $this->skipCriteria = $status;
795
796
        return $this;
797
    }
798
799
    /**
800
     * Apply scope in current Query.
801
     *
802
     * @return $this
803
     */
804
    protected function applyScope()
805
    {
806
807
        if (isset($this->scopeQuery) && is_callable($this->scopeQuery)) {
808
            $callback    = $this->scopeQuery;
809
            $this->model = $callback($this->model);
810
        }
811
812
        return $this;
813
    }
814
815
    /**
816
     * Apply criteria in current Query.
817
     *
818
     * @return $this
819
     */
820
    protected function applyCriteria()
821
    {
822
823
        if ($this->skipCriteria === true) {
824
            return $this;
825
        }
826
827
        $criteria = $this->getCriteria();
828
829
        if ($criteria) {
830
            foreach ($criteria as $c) {
831
                if ($c instanceof Criteria) {
832
                    $this->model = $c->apply($this->model, $this);
833
                }
834
            }
835
        }
836
837
        return $this;
838
    }
839
840
    /**
841
     * Skip Presenter Wrapper.
842
     *
843
     * @param  bool $status
844
     * @return $this
845
     */
846
    public function skipPresenter($status = true)
847
    {
848
        $this->skipPresenter = $status;
849
850
        return $this;
851
    }
852
853
    /**
854
     * Wrapper result data.
855
     *
856
     * @param  mixed $result
857
     * @return mixed
858
     */
859
    public function parserResult($result)
860
    {
861
862
        if ($this->presenter instanceof Presenter) {
863
            if ($result instanceof Collection || $result instanceof LengthAwarePaginator) {
864
                $result->each(function ($model) {
0 ignored issues
show
Bug introduced by
The method each does only exist in Illuminate\Support\Collection, but not in Illuminate\Pagination\LengthAwarePaginator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
865
866
                    if ($model instanceof Presentable) {
867
                        $model->setPresenter($this->presenter);
868
                    }
869
870
                    return $model;
871
                });
872
            } elseif ($result instanceof Presentable) {
873
                $result = $result->setPresenter($this->presenter);
874
            }
875
876
            if (!$this->skipPresenter) {
877
                return $this->presenter->present($result);
878
            }
879
        }
880
881
        return $result;
882
    }
883
}
884