AbstractRepository   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 606
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 74.36%

Importance

Changes 0
Metric Value
dl 0
loc 606
ccs 145
cts 195
cp 0.7436
rs 8.474
c 0
b 0
f 0
wmc 49
lcom 1
cbo 11

30 Methods

Rating   Name   Duplication   Size   Complexity  
A makeModel() 0 5 1
A resetModel() 0 5 1
A getQuery() 0 10 1
A searchAll() 0 13 2
A search() 0 10 2
A resultset() 0 6 1
A pluck() 0 4 1
A orderBy() 0 20 5
A random() 0 18 5
A count() 0 6 1
A max() 0 6 1
A min() 0 6 1
A sum() 0 6 1
A avg() 0 6 1
A orderUp() 0 6 1
A orderTop() 0 4 1
A orderDown() 0 6 1
A orderBottom() 0 4 1
A reorder() 0 22 3
A whereInputCriteria() 0 9 2
A validar() 0 22 3
A create() 0 12 1
A update() 0 21 1
A deleteForce() 0 19 1
A deleteWhere() 0 20 2
A updateWhere() 0 20 2
A joinSub() 0 9 1
A leftJoinSub() 0 4 1
A rightJoinSub() 0 4 1
A __call() 0 16 3

How to fix   Complexity   

Complex Class

Complex classes like AbstractRepository often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractRepository, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace NwLaravel\Repositories\Eloquent;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Prettus\Repository\Eloquent\BaseRepository;
7
use NwLaravel\Repositories\RepositoryInterface;
8
use NwLaravel\Repositories\Criterias\InputCriteria;
9
use NwLaravel\Resultset\BuilderResultset;
10
use Prettus\Validator\Contracts\ValidatorInterface;
11
use Prettus\Repository\Events\RepositoryEntityCreated;
12
use Prettus\Repository\Events\RepositoryEntityUpdated;
13
use Prettus\Repository\Events\RepositoryEntityDeleted;
14
use Illuminate\Database\Query\Expression;
15
use Illuminate\Database\Query\Grammars;
16
use BadMethodCallException;
17
use RuntimeException;
18
19
/**
20
 * Class AbstractRepository
21
 *
22
 * @abstract
23
 */
24
abstract class AbstractRepository extends BaseRepository implements RepositoryInterface
25
{
26
    /**
27
     * @var string
28
     */
29
    protected $orderBy;
30
31
    /**
32
     * @var bool
33
     */
34
    protected $skipPresenter = true;
35
36
    /**
37
     * @return Model
38
     * @throws RepositoryException
39
     */
40 29
    public function makeModel()
41
    {
42 29
        parent::makeModel();
43 29
        return $this->model = $this->model->newQuery();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->model->newQuery() of type object<Illuminate\Database\Eloquent\Builder> is incompatible with the declared type object<Illuminate\Database\Eloquent\Model> of property $model.

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...
Bug Best Practice introduced by
The return type of return $this->model = $this->model->newQuery(); (Illuminate\Database\Eloquent\Builder) is incompatible with the return type of the parent method Prettus\Repository\Eloqu...seRepository::makeModel of type Illuminate\Database\Eloquent\Model.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
44
    }
45
46
    /**
47
     * Reset Model
48
     *
49
     * @return AbstractRepository
50
     * @throws RepositoryException
51
     */
52 12
    public function resetModel()
53
    {
54 12
        parent::resetModel();
55 12
        return $this;
56
    }
57
58
    /**
59
     * Get Query
60
     *
61
     * @return Builder
62
     */
63 8
    public function getQuery()
64
    {
65 8
        $this->applyCriteria();
66 8
        $this->applyScope();
67
68 8
        $model = $this->model;
69
70 8
        $this->resetModel();
71 8
        return $model;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $model; (Illuminate\Database\Eloquent\Model) is incompatible with the return type declared by the interface NwLaravel\Repositories\R...toryInterface::getQuery of type Illuminate\Database\Eloquent\Builder.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
72
    }
73
74
    /**
75
     * Search All
76
     *
77
     * @param array  $input         Array Imput
78
     * @param string $orderBy       String Order By
79
     * @param int    $limit         Integer Limit
80
     * @param bool   $skipPresenter Boolean Skip Presenter
81
     *
82
     * @return BuilderResultset
83
     */
84 1
    public function searchAll(array $input, $orderBy = '', $limit = null, $skipPresenter = true)
85
    {
86 1
        $orderBy = $orderBy?:$this->orderBy;
87
88 1
        $query = $this
89 1
            ->whereInputCriteria($input)
90 1
            ->orderBy($orderBy)
91 1
            ->skipPresenter($skipPresenter)
92 1
            ->getQuery()
93 1
            ->limit($limit);
94
95 1
        return new BuilderResultset($query);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \NwLaravel\Re...ilderResultset($query); (NwLaravel\Resultset\BuilderResultset) is incompatible with the return type declared by the interface NwLaravel\Repositories\R...oryInterface::searchAll of type NwLaravel\Repositories\N...ultset\BuilderResultset.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
96
    }
97
98
    /**
99
     * Search Paginator
100
     *
101
     * @param array    $input         Array Input
102
     * @param string   $orderBy       String Order By
103
     * @param int|null $limit         Integer Limit
104
     * @param bool     $skipPresenter Boolean Skip Presenter
105
     *
106
     * @return Paginator
107
     */
108 1
    public function search(array $input, $orderBy = '', $limit = null, $skipPresenter = true)
109
    {
110 1
        $orderBy = $orderBy?:$this->orderBy;
111
112 1
        return $this
113 1
            ->whereInputCriteria($input)
114 1
            ->orderBy($orderBy)
115 1
            ->skipPresenter($skipPresenter)
116 1
            ->paginate($limit);
0 ignored issues
show
Bug introduced by
It seems like $limit defined by parameter $limit on line 108 can also be of type integer; however, Prettus\Repository\Contr...ryInterface::paginate() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
117
    }
118
119
    /**
120
     * ResultSet
121
     *
122
     * @param int $limit Integer Limit
123
     *
124
     * @return BuilderResultset
125
     */
126
    public function resultset($limit = null)
127
    {
128
        $query = $this->getQuery()->limit($limit);
129
130
        return new BuilderResultset($query);
131
    }
132
133
    /**
134
     * Get an array with the values of a given column.
135
     *
136
     * @param  string $column String Column
137
     * @param  string $key    String Key
138
     *
139
     * @return \Illuminate\Support\Collection
140
     */
141 1
    public function pluck($column, $key = null)
142
    {
143 1
        return $this->getQuery()->pluck($column, $key);
144
    }
145
146
    /**
147
     * Add an "order by" clause to the query.
148
     *
149
     * @param  string $columns   String Columns
150
     * @param  string $direction String Direction
151
     *
152
     * @return RepositoryInterface
153
     */
154 3
    public function orderBy($columns, $direction = 'asc')
155
    {
156 3
        if (!empty($columns)) {
157 3
            $columns = explode(',', $columns);
158 3
            foreach ($columns as $key => $column) {
159 3
                $column = explode(' ', $column);
160 3
                $column = array_filter($column);
161 3
                $column = array_pad($column, 2, '');
162 3
                list($field, $sort) = array_values($column);
163 3
                if (!empty($sort)) {
164 2
                    $direction = $sort;
165 2
                }
166 3
                $direction = strtoupper($direction);
167 3
                $direction = in_array($direction, ['ASC', 'DESC']) ? $direction : 'ASC';
168 3
                $this->model = $this->model->orderBy($field, $direction);
169 3
            }
170 3
        }
171
172 3
        return $this;
173
    }
174
175
    /**
176
     * Random
177
     *
178
     * @return RepositoryInterface
179
     */
180 4
    public function random()
181
    {
182 4
        $grammar = $this->model->getConnection()->getQueryGrammar();
183
184 4
        switch (true) {
185 4
            case $grammar instanceof Grammars\MySqlGrammar:
186 4
            case $grammar instanceof Grammars\SqlServerGrammar:
187 2
                $random = 'RAND()';
188 2
                break;
189 2
            case $grammar instanceof Grammars\PostgresGrammar:
190 2
            case $grammar instanceof Grammars\SQLiteGrammar:
191 2
                $random = 'RANDOM()';
192 2
        }
193
194 4
        $this->model = $this->model->orderBy(new Expression($random));
0 ignored issues
show
Bug introduced by
The variable $random 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...
195
196 4
        return $this;
197
    }
198
199
    /**
200
     * Count results of repository
201
     *
202
     * @param array  $where
203
     * @param string $columns
204
     *
205
     * @return int
206
     */
207 1
    public function count(array $where = [], $columns = '*')
208
    {
209 1
        $this->whereInputCriteria($where);
210
211 1
        return $this->getQuery()->count($columns);
212
    }
213
214
    /**
215
     * Max
216
     *
217
     * @param mixed $field Mixed Field
218
     * @param array $input Array Input
219
     *
220
     * @return mixed
221
     */
222 1
    public function max($field, array $input = array())
223
    {
224 1
        $this->whereInputCriteria($input);
225
226 1
        return $this->getQuery()->max($field);
227
    }
228
229
    /**
230
     * Min
231
     *
232
     * @param mixed $field Mixed Field
233
     * @param array $input Array Input
234
     *
235
     * @return mixed
236
     */
237 1
    public function min($field, array $input = array())
238
    {
239 1
        $this->whereInputCriteria($input);
240
241 1
        return $this->getQuery()->min($field);
242
    }
243
244
    /**
245
     * Sum
246
     *
247
     * @param mixed $field Mixed Field
248
     * @param array $input Array Input
249
     *
250
     * @return float
251
     */
252 1
    public function sum($field, array $input = array())
253
    {
254 1
        $this->whereInputCriteria($input);
255
256 1
        return $this->getQuery()->sum($field);
257
    }
258
259
    /**
260
     * Average
261
     *
262
     * @param mixed $field Mixed Field
263
     * @param array $input Array Input
264
     *
265
     * @return int
266
     */
267 1
    public function avg($field, array $input = array())
268
    {
269 1
        $this->whereInputCriteria($input);
270
271 1
        return $this->getQuery()->avg($field);
272
    }
273
274
    /**
275
     * Order Up
276
     *
277
     * @param Model  $model
278
     * @param string $field Field Order
279
     * @param array  $input Array Where
280
     *
281
     * @return boolean
282
     */
283
    public function orderUp($model, $field, array $input = [])
284
    {
285
        $input["{$field} <= ?"] = $model->{$field};
286
        $input["id != ?"] = $model->id;
287
        return $this->reorder($model, $field, $input, 'DESC');
288
    }
289
290
    /**
291
     * Order Top
292
     *
293
     * @param Model  $model
294
     * @param string $field Field Order
295
     * @param array  $input Array Where
296
     *
297
     * @return boolean
298
     */
299
    public function orderTop($model, $field, array $input = [])
300
    {
301
        return $this->reorder($model, $field, $input, 'ASC');
302
    }
303
304
    /**
305
     * Order Down
306
     *
307
     * @param Model  $model
308
     * @param string $field Field Order
309
     * @param array  $input Array Where
310
     *
311
     * @return boolean
312
     */
313
    public function orderDown($model, $field, array $input = [])
314
    {
315
        $input["{$field} >= ?"] = $model->{$field};
316
        $input["id != ?"] = $model->id;
317
        return $this->reorder($model, $field, $input, 'ASC');
318
    }
319
320
    /**
321
     * Order Bottom
322
     *
323
     * @param Model  $model
324
     * @param string $field Field Order
325
     * @param array  $input Array Where
326
     *
327
     * @return boolean
328
     */
329
    public function orderBottom($model, $field, array $input = [])
330
    {
331
        return $this->reorder($model, $field, $input, 'DESC');
332
    }
333
334
    /**
335
     * Reorder
336
     *
337
     * @param Model  $model
338
     * @param string $field Field Order
339
     * @param array  $input Array Where
340
     * @param string $sort  Sort
341
     *
342
     * @return boolean
343
     */
344
    protected function reorder($model, $field, array $input, $sort)
345
    {
346
        if (!$model->exists) {
347
            return false;
348
        }
349
350
        $order = $model->{$field};
351
352
        $anterior = $this->whereInputCriteria($input)->orderBy($field, $sort)->first();
353
354
        if ($anterior) {
355
            $model->{$field} = $anterior->{$field};
356
            $model->save();
357
358
            $anterior->{$field} = $order;
359
            $anterior->save();
360
        }
361
362
        event(new RepositoryEntityUpdated($this, $model));
363
364
        return true;
365
    }
366
367
    /**
368
     * Where InputCriteria
369
     *
370
     * @param array $input Array Input
371
     *
372
     * @return RepositoryInterface
373
     */
374 7
    public function whereInputCriteria(array $input = array())
375
    {
376 7
        if (count($input)) {
377 6
            $criteria = new InputCriteria($input);
378 6
            $this->model = $criteria->apply($this->model, $this);
0 ignored issues
show
Documentation introduced by
$this->model is of type object<Illuminate\Database\Eloquent\Model>, but the function expects a object<NwLaravel\Repositories\Criterias\Builder>.

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...
379 6
        }
380
381 7
        return $this;
382
    }
383
384
    /**
385
     * Validar
386
     *
387
     * @param array  $attributes
388
     * @param string $action
389
     * @param string $id
390
     *
391
     * @return bool
392
     */
393 2
    public function validar(array $attributes, $action, $id = null)
394
    {
395 2
        $return = false;
396
397 2
        if (!is_null($this->validator)) {
398
            // we should pass data that has been casts by the model
399
            // to make sure data type are same because validator may need to use
400
            // this data to compare with data that fetch from database.
401 2
            $model = $this->model->newModelInstance()->fill($attributes);
402 2
            $attributes = array_merge($attributes, $model->toArray());
403
404 2
            $validator = $this->validator->with($attributes);
405
406 2
            if ($id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
407 1
                $validator->setId($id);
408 1
            }
409
410 2
            $return = $validator->passesOrFail($action);
411 2
        }
412
413 2
        return $return;
414
    }
415
416
    /**
417
     * Save a new model in repository
418
     *
419
     * @throws ValidatorException
420
     * @param array $attributes Array Attributes
421
     * @return mixed
422
     */
423 1
    public function create(array $attributes)
424
    {
425 1
        $this->validar($attributes, ValidatorInterface::RULE_CREATE);
426
427 1
        $model = $this->model->newModelInstance($attributes);
428 1
        $model->save();
429 1
        $this->resetModel();
430
431 1
        event(new RepositoryEntityCreated($this, $model));
432
433 1
        return $this->parserResult($model);
434
    }
435
436
    /**
437
     * Update a model in repository by id
438
     *
439
     * @throws ValidatorException
440
     * @param array $attributes Array Attributes
441
     * @param int   $id         Integer Id
442
     * @return mixed
443
     */
444 1
    public function update(array $attributes, $id)
445
    {
446 1
        $this->applyScope();
447
448 1
        $this->validar($attributes, ValidatorInterface::RULE_UPDATE, $id);
449
450 1
        $temporarySkipPresenter = $this->skipPresenter;
451
452 1
        $this->skipPresenter(true);
453
454 1
        $model = $this->model->findOrFail($id);
455 1
        $model->fill($attributes);
456 1
        $model->save();
457
458 1
        $this->skipPresenter($temporarySkipPresenter);
459 1
        $this->resetModel();
460
461 1
        event(new RepositoryEntityUpdated($this, $model));
462
463 1
        return $this->parserResult($model);
464
    }
465
466
    /**
467
     * Delete Force a entity in repository by id
468
     *
469
     * @param $id
470
     *
471
     * @return int
472
     */
473
    public function deleteForce($id)
474
    {
475
        $this->applyScope();
476
477
        $temporarySkipPresenter = $this->skipPresenter;
478
        $this->skipPresenter(true);
479
480
        $model = $this->find($id);
481
        $originalModel = clone $model;
482
483
        $this->skipPresenter($temporarySkipPresenter);
484
        $this->resetModel();
485
486
        $deleted = $model->forceDelete();
487
488
        event(new RepositoryEntityDeleted($this, $originalModel));
489
490
        return $deleted;
491
    }
492
493
    /**
494
     * Delete multiple entities by given criteria.
495
     *
496
     * @param array $where
497
     *
498
     * @return boolean|null
499
     */
500 1
    public function deleteWhere(array $where)
501
    {
502 1
        $this->applyCriteria();
503 1
        $this->applyScope();
504
505 1
        $temporarySkipPresenter = $this->skipPresenter;
506 1
        $this->skipPresenter(true);
507
508 1
        $this->whereInputCriteria($where);
509
510 1
        $deleted = $this->model->delete();
511
512 1
        $model = $this->model instanceof Builder ? $this->model->getModel() : $this->model;
513 1
        event(new RepositoryEntityDeleted($this, $model));
514
515 1
        $this->skipPresenter($temporarySkipPresenter);
516 1
        $this->resetModel();
517
518 1
        return $deleted;
519
    }
520
521
    /**
522
     * Update multiple entities by given criteria.
523
     *
524
     * @param array $where
525
     *
526
     * @return boolean|null
527
     */
528 1
    public function updateWhere(array $attributes, array $where)
529
    {
530 1
        $this->applyCriteria();
531 1
        $this->applyScope();
532
533 1
        $temporarySkipPresenter = $this->skipPresenter;
534 1
        $this->skipPresenter(true);
535
536 1
        $this->whereInputCriteria($where);
537
538 1
        $updated = $this->model->update($attributes);
539
540 1
        $this->skipPresenter($temporarySkipPresenter);
541 1
        $this->resetModel();
542
543 1
        $model = $this->model instanceof Builder ? $this->model->getModel() : $this->model;
544 1
        event(new RepositoryEntityUpdated($this, $model));
545
546 1
        return $updated;
547
    }
548
549
    /**
550
     * Add a subquery join clause to the query.
551
     *
552
     * @param  \Closure|\Illuminate\Database\Query\Builder|string $query
553
     * @param  string  $as
554
     * @param  string  $first
555
     * @param  string|null  $operator
556
     * @param  string|null  $second
557
     * @param  string  $type
558
     * @param  bool    $where
559
     * @return \Illuminate\Database\Query\Builder|static
560
     *
561
     * @throws \InvalidArgumentException
562
     */
563
    public function joinSub($query, $as, $first, $operator = null, $second = null, $type = 'inner', $where = false)
564
    {
565
        $sql = $query->toSql();
0 ignored issues
show
Bug introduced by
The method toSql does only exist in Illuminate\Database\Query\Builder, but not in Closure.

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...
566
        $bindings = $query->getBindings();
0 ignored issues
show
Bug introduced by
The method getBindings does only exist in Illuminate\Database\Query\Builder, but not in Closure.

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...
567
        $expression = '('.$sql.') as '.$this->model->getGrammar()->wrap($as);
568
        $this->model->addBinding($bindings, 'join');
569
570
        return $this->join(new Expression($expression), $first, $operator, $second, $type, $where);
0 ignored issues
show
Documentation Bug introduced by
The method join does not exist on object<NwLaravel\Reposit...ent\AbstractRepository>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
571
    }
572
573
    /**
574
     * Add a subquery left join to the query.
575
     *
576
     * @param  \Closure|\Illuminate\Database\Query\Builder|string $query
577
     * @param  string  $as
578
     * @param  string  $first
579
     * @param  string|null  $operator
580
     * @param  string|null  $second
581
     * @return \Illuminate\Database\Query\Builder|static
582
     */
583
    public function leftJoinSub($query, $as, $first, $operator = null, $second = null)
584
    {
585
        return $this->joinSub($query, $as, $first, $operator, $second, 'left');
586
    }
587
588
    /**
589
     * Add a subquery right join to the query.
590
     *
591
     * @param  \Closure|\Illuminate\Database\Query\Builder|string $query
592
     * @param  string  $as
593
     * @param  string  $first
594
     * @param  string|null  $operator
595
     * @param  string|null  $second
596
     * @return \Illuminate\Database\Query\Builder|static
597
     */
598
    public function rightJoinSub($query, $as, $first, $operator = null, $second = null)
599
    {
600
        return $this->joinSub($query, $as, $first, $operator, $second, 'right');
601
    }
602
603
    /**
604
     * Handle dynamic method calls into the method.
605
     *
606
     * @param  string  $method
607
     * @param  array   $parameters
608
     *
609
     * @return AbstractRepository
610
     *
611
     * @throws BadMethodCallException
612
     */
613 7
    public function __call($method, $parameters)
614
    {
615 7
        $pattern = '/^(((where|orWhere).*)|select|limit|groupBy|join|leftJoin|rightJoin|crossJoin|withTrashed|withoutTrashed|onlyTrashed)$/';
616 7
        if (preg_match($pattern, $method)) {
617 6
            $this->model = call_user_func_array([$this->model, $method], $parameters);
618 6
            return $this;
619
        }
620
621 1
        $pattern = '/^(toSql|getBindings)$/';
622 1
        if (preg_match($pattern, $method)) {
623
            return call_user_func_array([$this->model, $method], $parameters);
624
        }
625
626 1
        $className = static::class;
627 1
        throw new BadMethodCallException("Call to undefined method {$className}::{$method}()");
628
    }
629
}
630