Completed
Push — master ( 04c69a...03a792 )
by Alberto
02:37
created

ActiveRecord   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 352
Duplicated Lines 18.18 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 19
Bugs 6 Features 3
Metric Value
c 19
b 6
f 3
dl 64
loc 352
rs 8.2608
wmc 40
lcom 2
cbo 3

21 Methods

Rating   Name   Duplication   Size   Complexity  
A resolver() 0 8 2
A hasMany() 9 9 2
A hasOne() 9 9 2
A jsonSerialize() 0 5 1
A __call() 0 7 2
A getRelationship() 0 5 2
A populate() 0 4 1
A updateAll() 8 11 3
A deleteAll() 0 8 1
A sqlItemSanitize() 0 12 4
A firstBy() 7 7 1
A first() 0 9 1
A all() 0 6 1
A doQuery() 0 9 1
A getParam() 0 6 2
A getValues() 0 5 3
A allBy() 7 7 1
A count() 8 11 3
A paginate() 0 12 3
A allBySql() 8 8 2
A firstBySql() 8 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

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

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

1
<?php
2
/**
3
 * KumbiaPHP web & app Framework.
4
 *
5
 * LICENSE
6
 *
7
 * This source file is subject to the new BSD license that is bundled
8
 * with this package in the file LICENSE.txt.
9
 * It is also available through the world-wide-web at this URL:
10
 * http://wiki.kumbiaphp.com/Licencia
11
 * If you did not receive a copy of the license and are unable to
12
 * obtain it through the world-wide-web, please send an email
13
 * to [email protected] so we can send you a copy immediately.
14
 *
15
 * @category   Kumbia
16
 *
17
 * @copyright  2005 - 2016  Kumbia Team (http://www.kumbiaphp.com)
18
 * @license    http://wiki.kumbiaphp.com/Licencia     New BSD License
19
 */
20
namespace Kumbia\ActiveRecord;
21
22
/**
23
 * Implementación de patrón ActiveRecord con ayudantes de consultas sql.
24
 */
25
class ActiveRecord extends LiteRecord implements \JsonSerializable
26
{
27
28
    const BELONG_TO = 1;
29
    const HAS_MANY  = 2;
30
    const HAS_ONE   = 3;
31
32
    /**
33
     * Describe the relationships
34
     * @var array
35
     */
36
    static protected $_rs = [];
37
38
    /**
39
     * Store all information about relationship for populate methods
40
     * @var array
41
     */
42
    protected $_populate = [];
43
44
    static public function resolver($rs, $obj){
45
        $model = $rs->model;
46
        if($rs->type === self::HAS_MANY){
47
            return $model::allBy($rs->via, $obj->pk());
48
        }else{
49
            return $model::first($rs->via, $obj->pk());
50
        }
51
    }
52
53 View Code Duplication
    static public function hasMany($name, $class, $via = NULL){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
54
        $str = strtolower($name);
55
        $name = static::getTable();
56
        static::$_rs[$str] = (object)[
57
            'model' => $class,
58
            'type'  => self::HAS_MANY,
59
            'via'   => $via ? $via : "{$name}_id"
60
        ];
61
    }
62
63 View Code Duplication
    static public function hasOne($name, $class, $via = NULL){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
64
        $str = strtolower($name);
65
        $name = static::getTable();
66
        static::$_rs[$str] = (object)[
67
            'model' => $class,
68
            'type'  => self::HAS_ONE,
69
            'via'   => $via ? $via : "{$name}_id"
70
        ];
71
    }
72
73
    public function jsonSerialize(){
74
       $var = get_object_vars($this);
75
       unset($var['_populate']);
76
       return array_merge($var, $this->_populate);
77
    }
78
79
80
    public function __call($name, $arguments){
81
        //it's a relationship
82
        if (strncmp($name, 'get', 3) === 0){
83
            $rel =  strtolower(substr ($name, 3));
84
            return static::resolver(static::getRelationship($rel), $this);
85
        }
86
    }
87
88
    static protected function getRelationship($rel){
89
        if(!isset(static::$_rs[$rel]))
90
            throw new \RuntimeException("Invalid relationship '$rel'", 500);
91
        return static::$_rs[$rel];
92
    }
93
94
    public function populate($rel){
95
        $rs = static::getRelationship($rel);
96
        $this->_populate[$rel] =  static::resolver($rs, $this);
97
    }
98
99
    /**
100
     * Actualizar registros.
101
     *
102
     * @param array  $fields
103
     * @param string $where  condiciones
104
     * @param array  $values valores para condiciones
105
     *
106
     * @return int numero de registros actualizados
107
     */
108 View Code Duplication
    public static function updateAll(array $fields, $where = null, array $values = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
109
    {
110
        if ($values !== null && !is_array($values)) {
111
            $values = \array_slice(\func_get_args(), 2);
112
        }
113
        $sql = QueryGenerator::updateAll(\get_called_class(), $fields, $values, $where);
114
        $sth = self::prepare($sql);
115
        $sth->execute($values);
116
117
        return $sth->rowCount();
118
    }
119
120
    /**
121
     * Eliminar registro.
122
     *
123
     * @param string        $where  condiciones
124
     * @param array |string $values valores
125
     *
126
     * @return int numero de registros eliminados
127
     */
128
    public static function deleteAll($where = null, $values = null)
129
    {
130
        $source = static::getSource();
131
        $sql = QueryGenerator::deleteAll($source, $where);
132
        $sth = self::query($sql, $values);
133
134
        return $sth->rowCount();
135
    }
136
137
    /**
138
     * Elimina caracteres que podrian ayudar a ejecutar
139
     * un ataque de Inyeccion SQL.
140
     *
141
     * @param string $sqlItem
142
     *
143
     * @return string
144
     * @throw KumbiaException
145
     */
146
    public static function sqlItemSanitize($sqlItem)
147
    {
148
        $sqlItem = \trim($sqlItem);
149
        if ($sqlItem !== '' && $sqlItem !== null) {
150
            $sql_temp = \preg_replace('/\s+/', '', $sqlItem);
151
            if (!\preg_match('/^[a-zA-Z0-9_\.]+$/', $sql_temp)) {
152
                throw new \RuntimeException('Se esta tratando de ejecutar una operacion maliciosa!');
153
            }
154
        }
155
156
        return $sqlItem;
157
    }
158
159
    /**
160
     * Obtener la primera coincidencia por el campo indicado.
161
     *
162
     * @param string $field  campo
163
     * @param string $value  valor
164
     * @param array  $params parametros adicionales
165
     *                       order: criterio de ordenamiento
166
     *                       fields: lista de campos
167
     *                       join: joins de tablas
168
     *                       group: agrupar campos
169
     *                       having: condiciones de grupo
170
     *                       offset: valor offset
171
     *
172
     * @return ActiveRecord
173
     */
174 View Code Duplication
    public static function firstBy($field, $value, $params = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
175
    {
176
        $field = self::sqlItemSanitize($field);
177
        $params['where'] = "$field = ?";
178
179
        return self::first($params, $value);
180
    }
181
182
    /**
183
     * Obtener la primera coincidencia de las condiciones indicadas.
184
     *
185
     * @param array  $params parametros de bus
186
     * @param string $field  campo
0 ignored issues
show
Bug introduced by
There is no parameter named $field. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
187
     * @param string $value  valor
0 ignored issues
show
Documentation introduced by
There is no parameter named $value. Did you maybe mean $values?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
188
     * @param array  $params parametros adicionales
189
     *                       order: criterio de ordenamiento
190
     *                       fields: lista de campos
191
     *                       group: agrupar campos
192
     *                       join: joins de tablas
193
     *                       having: condiciones de grupo
194
     *                       offset: valor offset queda
195
     * @param array  $values valores de busqueda
196
     *
197
     * @return ActiveRecord
198
     */
199
    public static function first($params = [], $values = [])
200
    {
201
        $args = func_get_args();
202
        /*Reescribe el limit*/
203
        $args[0]['limit'] = 1;
204
        $res = self::doQuery($args);
205
206
        return $res->fetch();
207
    }
208
209
    /**
210
     * Obtener todos los registros.
211
     *
212
     * @param array $params
213
     *                      where: condiciones where
214
     *                      order: criterio de ordenamiento
215
     *                      fields: lista de campos
216
     *                      join: joins de tablas
217
     *                      group: agrupar campos
218
     *                      having: condiciones de grupo
219
     *                      limit: valor limit
220
     *                      offset: valor offset
221
     * @param array $values valores de busqueda
222
     *
223
     * @return \PDOStatement
224
     */
225
    public static function all($params = [], $values = [])
226
    {
227
        $res = self::doQuery(func_get_args());
228
229
        return $res->fetchAll();
230
    }
231
232
    /**
233
     * Do a query.
234
     *
235
     * @param array $array params of query
236
     *
237
     * @return \PDOStatement|false
238
     */
239
    protected static function doQuery(array $array)
240
    {
241
        $params = self::getParam($array);
242
        $values = self::getValues($array);
243
        $sql = QueryGenerator::select(static::getSource(), static::getDriver(), $params);
244
        $sth = static::query($sql, $values);
245
246
        return $sth;
247
    }
248
249
    /**
250
     * Retorna los parametros para el doQuery.
251
     *
252
     * @param array $array
253
     *
254
     * @return array
255
     */
256
    protected static function getParam(array &$array)
257
    {
258
        $val = array_shift($array);
259
260
        return is_null($val) ?  [] : $val;
261
    }
262
263
    /**
264
     * Retorna los values para el doQuery.
265
     *
266
     * @param array $array
267
     *
268
     * @return array
269
     */
270
    protected static function getValues(array $array)
271
    {
272
        return isset($array[0]) ?
273
            is_array($array[0]) ? $array[0] : [$array[0]]: $array;
274
    }
275
276
    /**
277
     * Obtener todas las coincidencias por el campo indicado.
278
     *
279
     * @param string $field  campo
280
     * @param string $value  valor
281
     * @param array  $params
282
     *                       order: criterio de ordenamiento
283
     *                       fields: lista de campos
284
     *                       join: joins de tablas
285
     *                       group: agrupar campos
286
     *                       having: condiciones de grupo
287
     *                       limit: valor limit
288
     *                       offset: valor offset
289
     *
290
     * @return \PDOStatement
291
     */
292 View Code Duplication
    public static function allBy($field, $value, $params = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
293
    {
294
        $field = self::sqlItemSanitize($field);
295
        $params['where'] = "$field = ?";
296
297
        return self::all($params, $value);
298
    }
299
300
    /**
301
     * Cuenta los registros que coincidan con las condiciones indicadas.
302
     *
303
     * @param string $where  condiciones
304
     * @param array  $values valores
305
     *
306
     * @return int
307
     */
308 View Code Duplication
    public static function count($where = null, $values = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
309
    {
310
        $source = static::getSource();
311
        $sql = QueryGenerator::count($source, $where);
312
        if ($values !== null && !is_array($values)) {
313
            $values = \array_slice(\func_get_args(), 1);
314
        }
315
        $sth = static::query($sql, $values);
316
317
        return $sth->fetch()->count;
318
    }
319
320
    /**
321
     * Paginar.
322
     *
323
     * @param array $params
324
     * @param int   $page    numero de pagina
325
     * @param int   $perPage cantidad de items por pagina
326
     * @param array $values  valores
327
     *
328
     * @return Paginator
329
     */
330
    public static function paginate(array $params, $page, $perPage, $values = null)
331
    {
332
        unset($params['limit'], $params['offset']);
333
        $sql = QueryGenerator::select(static::getSource(), static::getDriver(), $params);
334
335
        // Valores para consulta
336
        if ($values !== null && !\is_array($values)) {
337
            $values = \array_slice(func_get_args(), 3);
338
        }
339
340
        return new Paginator(\get_called_class(), $sql, (int) $page, (int) $perPage, $values);
341
    }
342
343
    /**
344
     * Obtiene todos los registros de la consulta sql.
345
     *
346
     * @param string         $sql
347
     * @param string | array $values
348
     *
349
     * @return array
350
     */
351 View Code Duplication
    public static function allBySql($sql, $values = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
352
    {
353
        if (!is_array($values)) {
354
            $values = \array_slice(\func_get_args(), 1);
355
        }
356
357
        return parent::all($sql, $values);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (all() instead of allBySql()). Are you sure this is correct? If so, you might want to change this to $this->all().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
358
    }
359
360
    /**
361
     * Obtiene el primer registro de la consulta sql.
362
     *
363
     * @param string         $sql
364
     * @param string | array $values
365
     *
366
     * @return array
367
     */
368 View Code Duplication
    public static function firstBySql($sql, $values = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
369
    {
370
        if (!is_array($values)) {
371
            $values = \array_slice(\func_get_args(), 1);
372
        }
373
374
        return parent::first($sql, $values);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (first() instead of firstBySql()). Are you sure this is correct? If so, you might want to change this to $this->first().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
375
    }
376
}
377