Completed
Push — master ( eb02f9...7f1c36 )
by Renato
04:46
created

AbstractRepository::makeModel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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