Completed
Push — master ( 622523...75f0c6 )
by Renato
04:37
created

AbstractRepository::resultset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 7
ccs 0
cts 4
cp 0
crap 2
rs 9.4285
c 0
b 0
f 0
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 11
    public function resetModel()
53
    {
54 11
        parent::resetModel();
55 11
        return $this;
56
    }
57
58
    /**
59
     * Get Query
60
     *
61
     * @return Builder
62
     */
63 2
    public function getQuery()
64
    {
65 2
        $this->applyCriteria();
66 2
        $this->applyScope();
67
68 2
        return $this->model;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->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...
69
    }
70
71
    /**
72
     * Search All
73
     *
74
     * @param array  $input         Array Imput
75
     * @param string $orderBy       String Order By
76
     * @param int    $limit         Integer Limit
77
     * @param bool   $skipPresenter Boolean Skip Presenter
78
     *
79
     * @return BuilderResultset
80
     */
81 1
    public function searchAll(array $input, $orderBy = '', $limit = null, $skipPresenter = true)
82
    {
83 1
        $orderBy = $orderBy?:$this->orderBy;
84
85 1
        $query = $this
86 1
            ->whereInputCriteria($input)
87 1
            ->orderBy($orderBy)
88 1
            ->skipPresenter($skipPresenter)
89 1
            ->getQuery()
90 1
            ->limit($limit);
91
92 1
        $this->resetModel();
93 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...
94
    }
95
96
    /**
97
     * Search Paginator
98
     *
99
     * @param array    $input         Array Input
100
     * @param string   $orderBy       String Order By
101
     * @param int|null $limit         Integer Limit
102
     * @param bool     $skipPresenter Boolean Skip Presenter
103
     *
104
     * @return Paginator
105
     */
106 1
    public function search(array $input, $orderBy = '', $limit = null, $skipPresenter = true)
107
    {
108 1
        $orderBy = $orderBy?:$this->orderBy;
109
110 1
        return $this
111 1
            ->whereInputCriteria($input)
112 1
            ->orderBy($orderBy)
113 1
            ->skipPresenter($skipPresenter)
114 1
            ->paginate($limit);
0 ignored issues
show
Bug introduced by
It seems like $limit defined by parameter $limit on line 106 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...
115
    }
116
117
    /**
118
     * ResultSet
119
     *
120
     * @param int $limit Integer Limit
121
     *
122
     * @return BuilderResultset
123
     */
124
    public function resultset($limit = null)
125
    {
126
        $query = $this->getQuery()->limit($limit);
127
128
        $this->resetModel();
129
        return new BuilderResultset($query);
130
    }
131
132
    /**
133
     * Get an array with the values of a given column.
134
     *
135
     * @param  string $column String Column
136
     * @param  string $key    String Key
137
     *
138
     * @return \Illuminate\Support\Collection
139
     */
140 1
    public function pluck($column, $key = null)
141
    {
142 1
        $this->applyCriteria();
143 1
        $this->applyScope();
144
145 1
        $lists = $this->model->pluck($column, $key);
146
147 1
        $this->resetModel();
148 1
        return $lists;
149
    }
150
151
    /**
152
     * Add an "order by" clause to the query.
153
     *
154
     * @param  string $columns   String Columns
155
     * @param  string $direction String Direction
156
     *
157
     * @return RepositoryInterface
158
     */
159 3
    public function orderBy($columns, $direction = 'asc')
160
    {
161 3
        if (!empty($columns)) {
162 3
            $columns = explode(',', $columns);
163 3
            foreach ($columns as $key => $column) {
164 3
                $column = explode(' ', $column);
165 3
                $column = array_filter($column);
166 3
                $column = array_pad($column, 2, '');
167 3
                list($field, $sort) = array_values($column);
168 3
                if (!empty($sort)) {
169 2
                    $direction = $sort;
170 2
                }
171 3
                $direction = strtoupper($direction);
172 3
                $direction = in_array($direction, ['ASC', 'DESC']) ? $direction : 'ASC';
173 3
                $this->model = $this->model->orderBy($field, $direction);
174 3
            }
175 3
        }
176
177 3
        return $this;
178
    }
179
180
    /**
181
     * Random
182
     *
183
     * @return RepositoryInterface
184
     */
185 4
    public function random()
186
    {
187 4
        $grammar = $this->model->getConnection()->getQueryGrammar();
188
189 4
        switch (true) {
190 4
            case $grammar instanceof Grammars\MySqlGrammar:
191 4
            case $grammar instanceof Grammars\SqlServerGrammar:
192 2
                $random = 'RAND()';
193 2
                break;
194 2
            case $grammar instanceof Grammars\PostgresGrammar:
195 2
            case $grammar instanceof Grammars\SQLiteGrammar:
196 2
                $random = 'RANDOM()';
197 2
        }
198
199 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...
200
201 4
        return $this;
202
    }
203
    
204
    /**
205
     * Count
206
     *
207
     * @param array $input Array Input
208
     *
209
     * @return int
210
     */
211 1
    public function count(array $input = array())
212
    {
213 1
        $this->applyCriteria();
214 1
        $this->applyScope();
215
        
216 1
        $this->whereInputCriteria($input);
217
        
218 1
        $count = $this->model->count();
219
        
220 1
        $this->resetModel();
221 1
        return $count;
222
    }
223
    
224
    /**
225
     * Max
226
     *
227
     * @param mixed $field Mixed Field
228
     * @param array $input Array Input
229
     *
230
     * @return mixed
231
     */
232 1
    public function max($field, array $input = array())
233
    {
234 1
        $this->applyCriteria();
235 1
        $this->applyScope();
236
    
237 1
        $this->whereInputCriteria($input);
238
    
239 1
        $max = $this->model->max($field);
240
    
241 1
        $this->resetModel();
242 1
        return $max;
243
    }
244
245
    /**
246
     * Min
247
     *
248
     * @param mixed $field Mixed Field
249
     * @param array $input Array Input
250
     *
251
     * @return mixed
252
     */
253 1
    public function min($field, array $input = array())
254
    {
255 1
        $this->applyCriteria();
256 1
        $this->applyScope();
257
    
258 1
        $this->whereInputCriteria($input);
259
    
260 1
        $max = $this->model->min($field);
261
    
262 1
        $this->resetModel();
263 1
        return $max;
264
    }
265
266
    /**
267
     * Sum
268
     *
269
     * @param mixed $field Mixed Field
270
     * @param array $input Array Input
271
     *
272
     * @return float
273
     */
274 1
    public function sum($field, array $input = array())
275
    {
276 1
        $this->applyCriteria();
277 1
        $this->applyScope();
278
    
279 1
        $this->whereInputCriteria($input);
280
    
281 1
        $max = $this->model->sum($field);
282
    
283 1
        $this->resetModel();
284 1
        return $max;
285
    }
286
287
    /**
288
     * Average
289
     *
290
     * @param mixed $field Mixed Field
291
     * @param array $input Array Input
292
     *
293
     * @return int
294
     */
295 1
    public function avg($field, array $input = array())
296
    {
297 1
        $this->applyCriteria();
298 1
        $this->applyScope();
299
    
300 1
        $this->whereInputCriteria($input);
301
    
302 1
        $avg = $this->model->avg($field);
303
    
304 1
        $this->resetModel();
305 1
        return $avg;
306
    }
307
308
    /**
309
     * Order Up
310
     *
311
     * @param Model  $model
312
     * @param string $field Field Order
313
     * @param array  $input Array Where
314
     *
315
     * @return boolean
316
     */
317
    public function orderUp($model, $field, array $input = [])
318
    {
319
        $input["{$field} <= ?"] = $model->{$field};
320
        $input["id != ?"] = $model->id;
321
        return $this->reorder($model, $field, $input, 'DESC');
322
    }
323
324
    /**
325
     * Order Down
326
     *
327
     * @param Model  $model
328
     * @param string $field Field Order
329
     * @param array  $input Array Where
330
     *
331
     * @return boolean
332
     */
333
    public function orderDown($model, $field, array $input = [])
334
    {
335
        $input["{$field} >= ?"] = $model->{$field};
336
        $input["id != ?"] = $model->id;
337
        return $this->reorder($model, $field, $input, 'ASC');
338
    }
339
340
    /**
341
     * Reorder
342
     *
343
     * @param Model  $model
344
     * @param string $field Field Order
345
     * @param array  $input Array Where
346
     * @param string $sort  Sort
347
     *
348
     * @return boolean
349
     */
350
    protected function reorder($model, $field, array $input, $sort)
351
    {
352
        if (!$model->exists) {
353
            return false;
354
        }
355
356
        $order = $model->{$field};
357
358
        $anterior = $this->whereInputCriteria($input)->orderBy($field, $sort)->first();
359
360
        if ($anterior) {
361
            $model->{$field} = $anterior->{$field};
362
            $model->save();
363
364
            $anterior->{$field} = $order;
365
            $anterior->save();
366
        }
367
368
        event(new RepositoryEntityUpdated($this, $model));
369
370
        return true;
371
    }
372
373
    /**
374
     * Where InputCriteria
375
     *
376
     * @param array $input Array Input
377
     *
378
     * @return RepositoryInterface
379
     */
380 7
    public function whereInputCriteria(array $input = array())
381
    {
382 7
        if (count($input)) {
383 6
            $criteria = new InputCriteria($input);
384 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...
385 6
        }
386
387 7
        return $this;
388
    }
389
    
390
    /**
391
     * Validar
392
     *
393
     * @param array  $attributes
394
     * @param string $action
395
     * @param string $id
396
     *
397
     * @return bool
398
     */
399 2
    public function validar(array $attributes, $action, $id = null)
400
    {
401 2
        $return = false;
402
403 2
        if (!is_null($this->validator)) {
404
            // we should pass data that has been casts by the model
405
            // to make sure data type are same because validator may need to use
406
            // this data to compare with data that fetch from database.
407 2
            $model = $this->model->newModelInstance()->forceFill($attributes);
408 2
            $attributes = array_merge($attributes, $model->toArray());
409
410 2
            $validator = $this->validator->with($attributes);
411
412 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...
413 1
                $validator->setId($id);
414 1
            }
415
416 2
            $return = $validator->passesOrFail($action);
417 2
        }
418
419 2
        return $return;
420
    }
421
422
    /**
423
     * Save a new model in repository
424
     *
425
     * @throws ValidatorException
426
     * @param array $attributes Array Attributes
427
     * @return mixed
428
     */
429 1
    public function create(array $attributes)
430
    {
431 1
        $this->validar($attributes, ValidatorInterface::RULE_CREATE);
432
433 1
        $model = $this->model->newModelInstance($attributes);
434 1
        $model->save();
435 1
        $this->resetModel();
436
437 1
        event(new RepositoryEntityCreated($this, $model));
438
439 1
        return $this->parserResult($model);
440
    }
441
442
    /**
443
     * Update a model in repository by id
444
     *
445
     * @throws ValidatorException
446
     * @param array $attributes Array Attributes
447
     * @param int   $id         Integer Id
448
     * @return mixed
449
     */
450 1
    public function update(array $attributes, $id)
451
    {
452 1
        $this->applyScope();
453
454 1
        $this->validar($attributes, ValidatorInterface::RULE_UPDATE, $id);
455
456 1
        $temporarySkipPresenter = $this->skipPresenter;
457
458 1
        $this->skipPresenter(true);
459
460 1
        $model = $this->model->findOrFail($id);
461 1
        $model->fill($attributes);
462 1
        $model->save();
463
464 1
        $this->skipPresenter($temporarySkipPresenter);
465 1
        $this->resetModel();
466
467 1
        event(new RepositoryEntityUpdated($this, $model));
468
469 1
        return $this->parserResult($model);
470
    }
471
472
    /**
473
     * Delete multiple entities by given criteria.
474
     *
475
     * @param array $where
476
     *
477
     * @return boolean|null
478
     */
479 1
    public function deleteWhere(array $where)
480
    {
481 1
        $this->applyCriteria();
482 1
        $this->applyScope();
483
484 1
        $temporarySkipPresenter = $this->skipPresenter;
485 1
        $this->skipPresenter(true);
486
487 1
        $this->whereInputCriteria($where);
488
489 1
        $deleted = $this->model->delete();
490
491 1
        $model = $this->model instanceof Builder ? $this->model->getModel() : $this->model;
492 1
        event(new RepositoryEntityDeleted($this, $model));
493
494 1
        $this->skipPresenter($temporarySkipPresenter);
495 1
        $this->resetModel();
496
497 1
        return $deleted;
498
    }
499
500
    /**
501
     * Update multiple entities by given criteria.
502
     *
503
     * @param array $where
504
     *
505
     * @return boolean|null
506
     */
507 1
    public function updateWhere(array $attributes, array $where)
508
    {
509 1
        $this->applyCriteria();
510 1
        $this->applyScope();
511
512 1
        $temporarySkipPresenter = $this->skipPresenter;
513 1
        $this->skipPresenter(true);
514
515 1
        $this->whereInputCriteria($where);
516
517 1
        $updated = $this->model->update($attributes);
518
519 1
        $this->skipPresenter($temporarySkipPresenter);
520 1
        $this->resetModel();
521
522 1
        $model = $this->model instanceof Builder ? $this->model->getModel() : $this->model;
523 1
        event(new RepositoryEntityUpdated($this, $model));
524
525 1
        return $updated;
526
    }
527
528
    /**
529
     * Handle dynamic method calls into the method.
530
     *
531
     * @param  string  $method
532
     * @param  array   $parameters
533
     *
534
     * @return AbstractRepository
535
     *
536
     * @throws BadMethodCallException
537
     */
538 7
    public function __call($method, $parameters)
539
    {
540 7
        $pattern = '/^(((where|orWhere).*)|select|limit|groupBy|join|leftJoin|rightJoin|crossJoin)$/';
541 7
        if (preg_match($pattern, $method)) {
542 6
            $this->model = call_user_func_array([$this->model, $method], $parameters);
543 6
            return $this;
544
        }
545
546 1
        $pattern = '/^(toSql|getBindings)$/';
547 1
        if (preg_match($pattern, $method)) {
548
            return call_user_func_array([$this->model, $method], $parameters);
549
        }
550
551 1
        $className = static::class;
552 1
        throw new BadMethodCallException("Call to undefined method {$className}::{$method}()");
553
    }
554
}
555