Completed
Push — master ( 492857...2250ba )
by Sherif
02:53
created

AbstractRepository::search()   C

Complexity

Conditions 8
Paths 2

Size

Total Lines 78
Code Lines 20

Duplication

Lines 14
Ratio 17.95 %

Importance

Changes 4
Bugs 2 Features 1
Metric Value
c 4
b 2
f 1
dl 14
loc 78
rs 6.102
cc 8
eloc 20
nc 2
nop 6

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php namespace App\Modules\V1\Core\AbstractRepositories;
2
3
use App\Modules\V1\Core\Interfaces\RepositoryInterface;
4
5
abstract class AbstractRepository implements RepositoryInterface
6
{
7
    /**
8
     * The model implementation.
9
     * 
10
     * @var model
11
     */
12
    public $model;
13
    
14
    /**
15
     * The config implementation.
16
     * 
17
     * @var config
18
     */
19
    protected $config;
20
    
21
    /**
22
     * Create new AbstractRepository instance.
23
     */
24
    public function __construct()
25
    {   
26
        $this->config = \CoreConfig::getConfig();
27
        $this->model  = \App::make($this->getModel());
28
    }
29
30
    /**
31
     * Fetch all records with relations from the storage.
32
     *
33
     * @param  array   $relations
34
     * @param  string  $sortBy
35
     * @param  boolean $desc
36
     * @param  array   $columns
37
     * @return collection
38
     */
39
    public function all($relations = [], $sortBy = 'created_at', $desc = 1, $columns = array('*'))
40
    {
41
        $sort = $desc ? 'desc' : 'asc';
42
        return call_user_func_array("{$this->getModel()}::with", array($relations))->orderBy($sortBy, $sort)->get($columns);
43
    }
44
45
    /**
46
     * Fetch all records with relations from storage in pages 
47
     * that matche the given query.
48
     * 
49
     * @param  string  $query
50
     * @param  integer $perPage
51
     * @param  array   $relations
52
     * @param  string  $sortBy
53
     * @param  boolean $desc
54
     * @param  array   $columns
55
     * @return collection
56
     */
57
    public function search($query, $perPage = 15, $relations = [], $sortBy = 'created_at', $desc = 1, $columns = array('*'))
58
    {
59
        $model            = call_user_func_array("{$this->getModel()}::with", array($relations));
60
        $conditionColumns = $this->model->searchable;
61
        $sort             = $desc ? 'desc' : 'asc';
62
63
        /**
64
         * Construct the select conditions for the model.
65
         */
66
        $model->where(function ($q) use ($query, $conditionColumns, $relations){
67
68 View Code Duplication
            if (count($conditionColumns)) 
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
69
            {
70
                /**
71
                 * Use the first element in the model columns to construct the first condition.
72
                 */
73
                $q->where(\DB::raw('LOWER(' . array_shift($conditionColumns) . ')'), 'LIKE', '%' . strtolower($query) . '%');
74
            }
75
76
            /**
77
             * Loop through the rest of the columns to construct or where conditions.
78
             */
79
            foreach ($conditionColumns as $column) 
80
            {
81
                $q->orWhere(\DB::raw('LOWER(' . $column . ')'), 'LIKE', '%' . strtolower($query) . '%');
82
            }
83
84
            /**
85
             * Loop through the model relations.
86
             */
87
            foreach ($relations as $relation) 
88
            {
89
                /**
90
                 * Remove the sub relation if exists.
91
                 */
92
                $relation = explode('.', $relation)[0];
93
94
                /**
95
                 * Try to fetch the relation repository from the core.
96
                 */
97
                if (\Core::$relation()) 
98
                {
99
                    /**
100
                     * Construct the relation condition.
101
                     */
102
                    $q->orWhereHas($relation, function ($subModel) use ($query, $relation){
103
104
                        $subModel->where(function ($q) use ($query, $relation){
105
106
                            /**
107
                             * Get columns of the relation.
108
                             */
109
                            $subConditionColumns = \Core::$relation()->model->searchable;
110
111 View Code Duplication
                            if (count($subConditionColumns)) 
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
112
                            {
113
                                /**
114
                                * Use the first element in the relation model columns to construct the first condition.
115
                                 */
116
                                $q->where(\DB::raw('LOWER(' . array_shift($subConditionColumns) . ')'), 'LIKE', '%' . strtolower($query) . '%');
117
                            }
118
119
                            /**
120
                             * Loop through the rest of the columns to construct or where conditions.
121
                             */
122
                            foreach ($subConditionColumns as $subConditionColumn)
123
                            {
124
                                $q->orWhere(\DB::raw('LOWER(' . $subConditionColumn . ')'), 'LIKE', '%' . strtolower($query) . '%');
125
                            } 
126
                        });
127
128
                    });
129
                }
130
            }
131
        });
132
        
133
        return $model->orderBy($sortBy, $sort)->paginate($perPage, $columns);
134
    }
135
    
136
    /**
137
     * Fetch all records with relations from storage in pages.
138
     * 
139
     * @param  integer $perPage
140
     * @param  array   $relations
141
     * @param  string  $sortBy
142
     * @param  boolean $desc
143
     * @param  array   $columns
144
     * @return collection
145
     */
146
    public function paginate($perPage = 15, $relations = [], $sortBy = 'created_at', $desc = 1, $columns = array('*'))
147
    {
148
        $sort = $desc ? 'desc' : 'asc';
149
        return call_user_func_array("{$this->getModel()}::with", array($relations))->orderBy($sortBy, $sort)->paginate($perPage, $columns);
150
    }
151
152
    /**
153
     * Fetch all records with relations based on
154
     * the given condition from storage in pages.
155
     * 
156
     * @param  array   $conditions array of conditions
157
     * @param  integer $perPage
158
     * @param  array   $relations
159
     * @param  string  $sortBy
160
     * @param  boolean $desc
161
     * @param  array   $columns
162
     * @return collection
163
     */
164
    public function paginateBy($conditions, $perPage = 15, $relations = [], $sortBy = 'created_at', $desc = 1, $columns = array('*'))
165
    {
166
        unset($conditions['page']);
167
        $conditions = $this->constructConditions($conditions);
168
        $sort       = $desc ? 'desc' : 'asc';
169
        return call_user_func_array("{$this->getModel()}::with", array($relations))->whereRaw($conditions['conditionString'], $conditions['conditionValues'])->orderBy($sortBy, $sort)->paginate($perPage, $columns);
170
    }
171
    
172
    /**
173
     * Save the given model to the storage.
174
     * 
175
     * @param  array   $data
176
     * @param  boolean $saveLog
177
     * @return object
178
     */
179
    public function save(array $data, $saveLog = true)
180
    {
181
        $model      = false;
182
        $modelClass = $this->model;
183
        $relations  = [];
184
185
        \DB::transaction(function () use (&$model, &$relations, $data, $saveLog, $modelClass) {
186
            /**
187
             * If the id is present in the data then select the model for updating,
188
             * else create new model.
189
             * @var array
190
             */
191
            $model = array_key_exists('id', $data) ? $modelClass->lockForUpdate()->find($data['id']) : new $modelClass;
192
            if ( ! $model) 
193
            {
194
                \ErrorHandler::notFound(class_basename($modelClass) . ' with id : ' . $data['id']);
195
            }
196
197
            /**
198
             * Construct the model object with the given data,
199
             * and if there is a relation add it to relations array,
200
             * then save the model.
201
             */
202
            foreach ($data as $key => $value) 
203
            {
204
                /**
205
                 * If the attribute is a relation.
206
                 */
207
                $relation = camel_case($key);
208
                if (method_exists($model, $relation))
209
                {
210
211
                    /**
212
                     * Check if the relation is a collection.
213
                     */
214
                    if (class_basename($model->$relation) == 'Collection') 
215
                    {   
216
                        /**
217
                         * If the relation has no value then marke the relation data 
218
                         * related to the model to be deleted.
219
                         */
220
                        if ( ! $value || ! count($value)) 
221
                        {
222
                            $relations[$relation] = 'delete';
223
                        }   
224
                    }
225
                    if (is_array($value)) 
226
                    {
227
                        /**
228
                         * Loop through the relation data.
229
                         */
230
                        foreach ($value as $attr => $val) 
231
                        {
232
                            /**
233
                             * Get the relation model.
234
                             */
235
                            $relationBaseModel = \Core::$relation()->model;
236
237
                            /**
238
                             * Check if the relation is a collection.
239
                             */
240
                            if (class_basename($model->$relation) == 'Collection')
241
                            {
242
                                /**
243
                                 * If the id is present in the data then select the relation model for updating,
244
                                 * else create new model.
245
                                 */
246
                                $relationModel = array_key_exists('id', $val) ? $relationBaseModel->lockForUpdate()->find($val['id']) : new $relationBaseModel;
247
248
                                /**
249
                                 * If model doesn't exists.
250
                                 */
251
                                if ( ! $relationModel) 
252
                                {
253
                                    \ErrorHandler::notFound(class_basename($relationBaseModel) . ' with id : ' . $val['id']);
254
                                }
255
256
                                /**
257
                                 * Loop through the relation attributes.
258
                                 */
259
                                foreach ($val as $attr => $val) 
260
                                {
261
                                    /**
262
                                     * Prevent the sub relations or attributes not in the fillable.
263
                                     */
264
                                    if (gettype($val) !== 'object' && gettype($val) !== 'array' &&  array_search($attr, $relationModel->getFillable(), true) !== false)
265
                                    {
266
                                        $relationModel->$attr = $val;
267
                                    }
268
                                }
269
                                $relations[$relation][] = $relationModel;
270
                            }
271
                            /**
272
                             * If not collection.
273
                             */
274
                            else
275
                            {
276
                                /**
277
                                 * Prevent the sub relations.
278
                                 */
279
                                if (gettype($val) !== 'object' && gettype($val) !== 'array') 
280
                                {
281
                                    /**
282
                                     * If the id is present in the data then select the relation model for updating,
283
                                     * else create new model.
284
                                     */
285
                                    $relationModel = array_key_exists('id', $value) ? $relationBaseModel->lockForUpdate()->find($value['id']) : new $relationBaseModel;
286
287
                                    /**
288
                                     * If model doesn't exists.
289
                                     */
290
                                    if ( ! $relationModel) 
291
                                    {
292
                                        \ErrorHandler::notFound(class_basename($relationBaseModel) . ' with id : ' . $value['id']);
293
                                    }
294
295
                                    /**
296
                                     * Prevent attributes not in the fillable.
297
                                     */
298
                                    if (array_search($attr, $relationModel->getFillable(), true) !== false) 
299
                                    {
300
                                        $relationModel->$attr = $val;
301
                                        $relations[$relation] = $relationModel;
302
                                    }
303
                                }
304
                            }
305
                        }
306
                    }
307
                }
308
                /**
309
                 * If the attribute isn't a relation and prevent attributes not in the fillable.
310
                 */
311
                else if (array_search($key, $model->getFillable(), true) !== false)
312
                {
313
                    $model->$key = $value;   
314
                }
315
            }
316
            /**
317
             * Save the model.
318
             */
319
            $model->save();
320
321
            /**
322
             * Loop through the relations array.
323
             */
324
            foreach ($relations as $key => $value) 
325
            {
326
                /**
327
                 * If the relation is marked for delete then delete it.
328
                 */
329
                if ($value == 'delete' && $model->$key()->count())
330
                {
331
                    $model->$key()->delete();
332
                }
333
                /**
334
                 * If the relation is an array.
335
                 */
336
                else if (gettype($value) == 'array') 
337
                {
338
                    $ids = [];
339
                    /**
340
                     * Loop through the relations.
341
                     */
342
                    foreach ($value as $val) 
343
                    {
344
                        switch (class_basename($model->$key())) 
345
                        {
346
                            /**
347
                             * If the relation is one to many then update it's foreign key with
348
                             * the model id and save it then add its id to ids array to delete all 
349
                             * relations who's id isn't in the ids array.
350
                             */
351
                            case 'HasMany':
352
                                $foreignKeyName       = explode('.', $model->$key()->getForeignKey())[1];
353
                                $val->$foreignKeyName = $model->id;
354
                                $val->save();
355
                                $ids[] = $val->id;
356
                                break;
357
358
                            /**
359
                             * If the relation is many to many then add it's id to the ids array to
360
                             * attache these ids to the model.
361
                             */
362
                            case 'BelongsToMany':
363
                                $val->save();
364
                                $ids[] = $val->id;
365
                                break;
366
                        }
367
                    }
368
                    switch (class_basename($model->$key())) 
369
                    {
370
                        /**
371
                         * If the relation is one to many then delete all 
372
                         * relations who's id isn't in the ids array.
373
                         */
374
                        case 'HasMany':
375
                            $model->$key()->whereNotIn('id', $ids)->delete();
376
                            break;
377
378
                        /**
379
                         * If the relation is many to many then 
380
                         * detach the previous data and attach 
381
                         * the ids array to the model.
382
                         */
383
                        case 'BelongsToMany':
384
                            $model->$key()->detach();
385
                            $model->$key()->attach($ids);
386
                            break;
387
                    }
388
                }
389
                /**
390
                 * If the relation isn't array.
391
                 */
392
                else
393
                {
394
                    switch (class_basename($model->$key())) 
395
                    {
396
                        /**
397
                         * If the relation is one to many or one to one.
398
                         */
399
                        case 'BelongsTo':
400
                            $value->save();
401
                            $model->$key()->associate($value);
402
                            $model->save();
403
                            break;
404
                    }
405
                }
406
            }
407
408
            $saveLog ? \Logging::saveLog(array_key_exists('id', $data) ? 'update' : 'create', class_basename($modelClass), $this->getModel(), $model->id, $model) : false;
409
        });
410
    
411
        /**
412
         * return the saved mdel with the given relations.
413
         */
414
        return $model;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $model; (false) is incompatible with the return type declared by the interface App\Modules\V1\Core\Inte...positoryInterface::save of type object.

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...
415
    }
416
    
417
    /**
418
     * Delete record from the storage based on the given
419
     * condition.
420
     * 
421
     * @param  var $value condition value
422
     * @param  string $attribute condition column name
423
     * @return void
424
     */
425
    public function delete($value, $attribute = 'id', $saveLog = true)
426
    {
427
        if ($attribute == 'id') 
428
        {
429
            \DB::transaction(function () use ($value, $attribute, &$result, $saveLog) {
430
                $model = $this->model->lockForUpdate()->find($value);
431
                if ( ! $model) 
432
                {
433
                    \ErrorHandler::notFound(class_basename($this->model) . ' with id : ' . $value);
434
                }
435
                
436
                $model->delete();
437
                $saveLog ? \Logging::saveLog('delete', class_basename($this->model), $this->getModel(), $value, $model) : false;
438
            });
439
        }
440
        else
441
        {
442
            \DB::transaction(function () use ($value, $attribute, &$result, $saveLog) {
443
                call_user_func_array("{$this->getModel()}::where", array($attribute, '=', $value))->lockForUpdate()->get()->each(function ($model){
444
                    $model->delete();
445
                    $saveLog ? \Logging::saveLog('delete', class_basename($this->model), $this->getModel(), $model->id, $model) : false;
0 ignored issues
show
Bug introduced by
The variable $saveLog does not exist. Did you forget to declare it?

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

Loading history...
446
                });
447
            });   
448
        }
449
    }
450
    
451
    /**
452
     * Fetch records from the storage based on the given
453
     * id.
454
     * 
455
     * @param  integer $id
456
     * @param  array   $relations
457
     * @param  array   $columns
458
     * @return object
459
     */
460
    public function find($id, $relations = [], $columns = array('*'))
461
    {
462
        return call_user_func_array("{$this->getModel()}::with", array($relations))->find($id, $columns);
463
    }
464
    
465
    /**
466
     * Fetch records from the storage based on the given
467
     * condition.
468
     * 
469
     * @param  array   $conditions array of conditions
470
     * @param  array   $relations
471
     * @param  string  $sortBy
472
     * @param  boolean $desc
473
     * @param  array   $columns
474
     * @return collection
475
     */
476
    public function findBy($conditions, $relations = [], $sortBy = 'created_at', $desc = 1, $columns = array('*'))
477
    {
478
        $conditions = $this->constructConditions($conditions);
479
        $sort       = $desc ? 'desc' : 'asc';
480
        return call_user_func_array("{$this->getModel()}::with",  array($relations))->whereRaw($conditions['conditionString'], $conditions['conditionValues'])->orderBy($sortBy, $sort)->get($columns);
481
    }
482
483
    /**
484
     * Fetch the first record from the storage based on the given
485
     * condition.
486
     *
487
     * @param  array   $conditions array of conditions
488
     * @param  array   $relations
489
     * @param  array   $columns
490
     * @return object
491
     */
492
    public function first($conditions, $relations = [], $columns = array('*'))
493
    {
494
        $conditions = $this->constructConditions($conditions);
495
        return call_user_func_array("{$this->getModel()}::with", array($relations))->whereRaw($conditions['conditionString'], $conditions['conditionValues'])->first($columns);  
496
    }
497
498
    /**
499
     * Build the conditions recursively for the retrieving methods.
500
     * @param  array $conditions
501
     * @return array
502
     */
503
    protected function constructConditions($conditions)
504
    {   
505
        $conditionString = '';
506
        $conditionValues = [];
507
        foreach ($conditions as $key => $value) 
508
        {
509
            if ($key == 'and') 
510
            {
511
                $conditionString  .= str_replace('{op}', 'and', $this->constructConditions($value)['conditionString']) . ' {op} ';
512
                $conditionValues   = array_merge($conditionValues, $this->constructConditions($value)['conditionValues']);
513
            }
514
            else if ($key == 'or')
515
            {
516
                $conditionString  .= str_replace('{op}', 'or', $this->constructConditions($value)['conditionString']) . ' {op} ';
517
                $conditionValues   = array_merge($conditionValues, $this->constructConditions($value)['conditionValues']);
518
            }
519
            else
520
            {
521
                $conditionString  .= $key . '=? {op} ';
522
                $conditionValues[] = $value;
523
            }
524
        }
525
        $conditionString = '(' . rtrim($conditionString, '{op} ') . ')';
526
        return ['conditionString' => $conditionString, 'conditionValues' => $conditionValues];
527
    }
528
529
    /**
530
     * Abstract method that return the necessary 
531
     * information (full model namespace)
532
     * needed to preform the previous actions.
533
     * 
534
     * @return string
535
     */
536
    abstract protected function getModel();
537
}