Completed
Push — master ( 54d45f...a52a56 )
by Renato
10:44
created

AbstractRepository::update()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 15
c 2
b 0
f 0
nc 2
nop 2
dl 0
loc 29
ccs 16
cts 16
cp 1
crap 2
rs 8.8571
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 BadMethodCallException;
14
use RuntimeException;
15
16
/**
17
 * Class AbstractRepository
18
 *
19
 * @abstract
20
 */
21
abstract class AbstractRepository extends BaseRepository implements RepositoryInterface
22
{
23
    /**
24
     * @var string
25
     */
26
    protected $orderBy;
27
28
    /**
29
     * @var bool
30
     */
31
    protected $skipPresenter = true;
32
33
    /**
34
     * Reset Model
35
     *
36
     * @return AbstractRepository
37
     * @throws RepositoryException
38
     */
39 9
    public function resetModel()
40
    {
41 9
        parent::resetModel();
42 9
        return $this;
43
    }
44
45
    /**
46
     * Get Query
47
     *
48
     * @return Builder
49
     */
50 2
    public function getQuery()
51
    {
52 2
        $this->applyCriteria();
53 2
        $this->applyScope();
54
55 2
        return ($this->model instanceof Builder) ? $this->model : $this->model->newQuery();
56
    }
57
58
    /**
59
     * Search All
60
     *
61
     * @param array  $input         Array Imput
62
     * @param string $orderBy       String Order By
63
     * @param int    $limit         Integer Limit
64
     * @param bool   $skipPresenter Boolean Skip Presenter
65
     *
66
     * @return BuilderResultset
67
     */
68 1
    public function searchAll(array $input, $orderBy = '', $limit = null, $skipPresenter = true)
69
    {
70 1
        $orderBy = $orderBy?:$this->orderBy;
71
72 1
        $query = $this
73 1
            ->whereInputCriteria($input)
74 1
            ->orderBy($orderBy)
75 1
            ->skipPresenter($skipPresenter)
76 1
            ->getQuery()
77 1
            ->limit($limit);
78
79 1
        $this->resetModel();
80 1
        return app(BuilderResultset::class, [$query]);
81
    }
82
83
    /**
84
     * Search Paginator
85
     *
86
     * @param array    $input         Array Input
87
     * @param string   $orderBy       String Order By
88
     * @param int|null $limit         Integer Limit
89
     * @param bool     $skipPresenter Boolean Skip Presenter
90
     *
91
     * @return Paginator
92
     */
93 1
    public function search(array $input, $orderBy = '', $limit = null, $skipPresenter = true)
94
    {
95 1
        $orderBy = $orderBy?:$this->orderBy;
96
97 1
        return $this
98 1
            ->whereInputCriteria($input)
99 1
            ->orderBy($orderBy)
100 1
            ->skipPresenter($skipPresenter)
101 1
            ->paginate($limit);
0 ignored issues
show
Bug introduced by
It seems like $limit defined by parameter $limit on line 93 can also be of type integer; however, Prettus\Repository\Eloqu...eRepository::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...
102
    }
103
104
    /**
105
     * Get an array with the values of a given column.
106
     *
107
     * @param  string $column String Column
108
     * @param  string $key    String Key
109
     *
110
     * @return \Illuminate\Support\Collection
111
     */
112 1
    public function pluck($column, $key = null)
113
    {
114 1
        $this->applyCriteria();
115 1
        $this->applyScope();
116
117 1
        $lists = $this->model->pluck($column, $key);
118
119 1
        $this->resetModel();
120 1
        return $lists;
121
    }
122
123
    /**
124
     * Handle dynamic method calls into the method.
125
     *
126
     * @param  string  $method
127
     * @param  array   $parameters
128
     * @return mixed
129
     *
130
     * @throws BadMethodCallException
131
     */
132 7
    public function __call($method, $parameters)
133
    {
134 7
        $pattern = '/^(((where|orWhere).*)|groupBy|join|leftJoin|rightJoin|crossJoin)$/';
135 7
        if (preg_match($pattern, $method)) {
136 6
            call_user_func_array([$this->model, $method], $parameters);
137 6
            return $this;
138
        }
139
140 1
        $className = static::class;
141 1
        throw new BadMethodCallException("Call to undefined method {$className}::{$method}()");
142
    }
143
144
    /**
145
     * Add an "order by" clause to the query.
146
     *
147
     * @param  string $columns   String Columns
148
     * @param  string $direction String Direction
149
     *
150
     * @return $this
151
     */
152 1
    public function orderBy($columns, $direction = 'asc')
153
    {
154 1
        if (!empty($columns)) {
155 1
            $columns = explode(',', $columns);
156 1
            foreach ($columns as $key => $column) {
157 1
                $column = explode(' ', $column);
158 1
                $column = array_filter($column);
159 1
                $column = array_pad($column, 2, '');
160 1
                list($field, $sort) = array_values($column);
161 1
                if (!empty($sort)) {
162 1
                    $direction = $sort;
163 1
                    $this->model = $this->model->orderBy($field, $direction);
164 1
                }
165 1
            }
166 1
        }
167
168 1
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (NwLaravel\Repositories\Eloquent\AbstractRepository) is incompatible with the return type declared by the interface NwLaravel\Repositories\R...itoryInterface::orderBy of type NwLaravel\Repositories\AbstractRepository.

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...
169
    }
170
    
171
    /**
172
     * Count
173
     *
174
     * @param array $input Array Input
175
     *
176
     * @return int
177
     */
178 1
    public function count(array $input = array())
179
    {
180 1
        $this->applyCriteria();
181 1
        $this->applyScope();
182
        
183 1
        $this->whereInputCriteria($input);
184
        
185 1
        $count = $this->model->count();
186
        
187 1
        $this->resetModel();
188 1
        return $count;
189
    }
190
    
191
    /**
192
     * Max
193
     *
194
     * @param mixed $field Mixed Field
195
     * @param array $input Array Input
196
     *
197
     * @return mixed
198
     */
199 1
    public function max($field, array $input = array())
200
    {
201 1
        $this->applyCriteria();
202 1
        $this->applyScope();
203
    
204 1
        $this->whereInputCriteria($input);
205
    
206 1
        $max = $this->model->max($field);
207
    
208 1
        $this->resetModel();
209 1
        return $max;
210
    }
211
212
    /**
213
     * Min
214
     *
215
     * @param mixed $field Mixed Field
216
     * @param array $input Array Input
217
     *
218
     * @return mixed
219
     */
220 1
    public function min($field, array $input = array())
221
    {
222 1
        $this->applyCriteria();
223 1
        $this->applyScope();
224
    
225 1
        $this->whereInputCriteria($input);
226
    
227 1
        $max = $this->model->min($field);
228
    
229 1
        $this->resetModel();
230 1
        return $max;
231
    }
232
233
    /**
234
     * Sum
235
     *
236
     * @param mixed $field Mixed Field
237
     * @param array $input Array Input
238
     *
239
     * @return float
240
     */
241 1
    public function sum($field, array $input = array())
242
    {
243 1
        $this->applyCriteria();
244 1
        $this->applyScope();
245
    
246 1
        $this->whereInputCriteria($input);
247
    
248 1
        $max = $this->model->sum($field);
249
    
250 1
        $this->resetModel();
251 1
        return $max;
252
    }
253
254
    /**
255
     * Average
256
     *
257
     * @param mixed $field Mixed Field
258
     * @param array $input Array Input
259
     *
260
     * @return int
261
     */
262 1
    public function avg($field, array $input = array())
263
    {
264 1
        $this->applyCriteria();
265 1
        $this->applyScope();
266
    
267 1
        $this->whereInputCriteria($input);
268
    
269 1
        $avg = $this->model->avg($field);
270
    
271 1
        $this->resetModel();
272 1
        return $avg;
273
    }
274
275
    /**
276
     * Reorder
277
     *
278
     * @param string $field Field Order
279
     *
280
     * @return boolean
281
     */
282 4
    public function reorder($field, $input = null)
283
    {
284 4
        $self = $this;
285 4
        $conn = $this->model->getConnection();
286
287 4
        $reorder = function ($statement, $value) use ($self, $conn, $input, $field) {
288 3
            $conn->statement($statement);
289 3
            $data = [$field => $conn->raw($value)];
290
291 3
            return $self->whereInputCriteria($input)
292 3
                        ->orderBy($field)
293 3
                        ->getQuery()
294 3
                        ->update($data);
295 4
        };
296
297 4
        switch (true) {
298 4
            case $conn instanceof \Illuminate\Database\MySqlConnection:
299 1
                $statement = "SET @rownum := 0";
300 1
                $value = "(@rownum := @rownum+1)";
301 1
                return $reorder($statement, $value);
302
303 3
            case $conn instanceof \Illuminate\Database\PostgresConnection:
304 1
                $statement = "CREATE TEMPORARY SEQUENCE rownum_seq";
305 1
                $value = "NETVAL('rownum_seq')";
306 1
                return $reorder($statement, $value);
307
308 2
            case $conn instanceof \Illuminate\Database\SqlServerConnection:
309 1
                $statement = "DECLARE @rownum int; SET @rownum = 0";
310 1
                $value = "(@rownum = @rownum+1)";
311 1
                return $reorder($statement, $value);
312
313 1
            case $conn instanceof \Illuminate\Database\SQLiteConnection:
314 1
            default:
315 1
                throw new RuntimeException(sprintf("Reorder not valid for connection (%s)", get_class($conn)));
316
        }
317
    }
318
319
    /**
320
     * Where InputCriteria
321
     *
322
     * @param array $input Array Input
323
     *
324
     * @return AbstractRepository
325
     */
326 8
    public function whereInputCriteria(array $input = array())
327
    {
328 8
        if (count($input)) {
329 8
            $criteria = app(InputCriteria::class, [$input]);
330 8
            $this->model = $criteria->apply($this->model, $this);
331 8
        }
332
333 8
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (NwLaravel\Repositories\Eloquent\AbstractRepository) is incompatible with the return type declared by the interface NwLaravel\Repositories\R...ace::whereInputCriteria of type NwLaravel\Repositories\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...
334
    }
335
    
336
    /**
337
     * Save a new model in repository
338
     *
339
     * @throws ValidatorException
340
     * @param array $attributes Array Attributes
341
     * @return mixed
342
     */
343 1
    public function create(array $attributes)
344
    {
345 1
        if (!is_null($this->validator)) {
346
            // we should pass data that has been casts by the model
347
            // to make sure data type are same because validator may need to use
348
            // this data to compare with data that fetch from database.
349 1
            $model = $this->model->newInstance()->forceFill($attributes);
350 1
            $attributes = array_merge($attributes, $model->toArray());
351
352 1
            $this->validator->with($attributes)->passesOrFail(ValidatorInterface::RULE_CREATE);
353 1
        }
354
355 1
        $model = $this->model->newInstance($attributes);
356 1
        $model->save();
357 1
        $this->resetModel();
358
359 1
        event(new RepositoryEntityCreated($this, $model));
360
361 1
        return $this->parserResult($model);
362
    }
363
364
    /**
365
     * Update a model in repository by id
366
     *
367
     * @throws ValidatorException
368
     * @param array $attributes Array Attributes
369
     * @param int   $id         Integer Id
370
     * @return mixed
371
     */
372 1
    public function update(array $attributes, $id)
373
    {
374 1
        $this->applyScope();
375
376 1
        if (!is_null($this->validator)) {
377
            // we should pass data that has been casts by the model
378
            // to make sure data type are same because validator may need to use
379
            // this data to compare with data that fetch from database.
380 1
            $model = $this->model->newInstance()->forceFill($attributes);
381 1
            $attributes = array_merge($attributes, $model->toArray());
382
383 1
            $this->validator->with($attributes)->setId($id)->passesOrFail(ValidatorInterface::RULE_UPDATE);
384 1
        }
385
386 1
        $temporarySkipPresenter = $this->skipPresenter;
387
388 1
        $this->skipPresenter(true);
389
390 1
        $model = $this->model->findOrFail($id);
391 1
        $model->fill($attributes);
392 1
        $model->save();
393
394 1
        $this->skipPresenter($temporarySkipPresenter);
395 1
        $this->resetModel();
396
397 1
        event(new RepositoryEntityUpdated($this, $model));
398
399 1
        return $this->parserResult($model);
400
    }
401
}
402