Model   C
last analyzed

Complexity

Total Complexity 61

Size/Duplication

Total Lines 445
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 0
loc 445
c 0
b 0
f 0
wmc 61
lcom 1
cbo 8
rs 6.018

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
A findByPk() 0 4 1
A findByAttributes() 0 10 2
A finder() 0 9 2
A find() 0 4 1
A getAttributes() 0 9 2
C __get() 0 36 8
A relations() 0 4 1
B save() 0 18 6
A isNewRecord() 0 4 1
C create() 0 27 7
A beforeCreate() 0 4 1
A beforeSave() 0 4 1
A mergeAttributesDb() 0 19 4
A checkAttributeExists() 0 16 4
A afterCreate() 0 3 1
A afterSave() 0 3 1
C update() 0 29 7
A beforeUpdate() 0 4 1
A afterUpdate() 0 3 1
B delete() 0 25 5
A beforeDelete() 0 4 1
A afterDelete() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Model 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 Model, and based on these observations, apply Extract Interface, too.

1
<?php /** MicroModel */
2
3
namespace Micro\Mvc\Models;
4
5
use Micro\Base\Exception;
6
use Micro\Db\Injector;
7
use Micro\File\Type;
8
use Micro\Form\FormModel;
9
10
/**
11
 * Model class file.
12
 *
13
 * @author Oleg Lunegov <[email protected]>
14
 * @link https://github.com/linpax/microphp-framework
15
 * @copyright Copyright (c) 2013 Oleg Lunegov
16
 * @license https://github.com/linpax/microphp-framework/blob/master/LICENSE
17
 * @package Micro
18
 * @subpackage Mvc\Models
19
 * @version 1.0
20
 * @since 1.0
21
 * @abstract
22
 */
23
abstract class Model extends FormModel implements IModel
24
{
25
    /** @var string $primaryKey Primary key on table */
26
    public static $primaryKey = 'id';
27
    /** @var string $tableName Table name */
28
    public static $tableName;
29
30
    /** @var boolean $_isNewRecord Is new record? */
31
    protected $_isNewRecord = false;
32
    /** @var array $cacheRelations Cached loads relations */
33
    protected $cacheRelations = [];
34
35
36
    /**
37
     * Constructor for model
38
     *
39
     * @access public
40
     *
41
     * @param boolean $new is new model?
42
     *
43
     * @result void
44
     * @throws Exception
45
     */
46
    public function __construct($new = true)
47
    {
48
        parent::__construct();
49
50
        if (!static::$tableName) {
51
            throw new Exception('Table name not set in `'.__CLASS__.'`` model.');
52
        }
53
54
        $this->_isNewRecord = $new;
55
    }
56
57
    /**
58
     * Finder by primary key
59
     *
60
     * @access public
61
     *
62
     * @param int|string $value unique value
63
     *
64
     * @return mixed
65
     * @throws \Micro\base\Exception
66
     * @static
67
     */
68
    public static function findByPk($value)
69
    {
70
        return self::findByAttributes([self::$primaryKey => $value], true);
71
    }
72
73
    /**
74
     * Find models by attributes
75
     *
76
     * @access public
77
     *
78
     * @param array $attributes attributes and data for search
79
     * @param bool $single single or more
80
     *
81
     * @return mixed
82
     * @throws \Micro\base\Exception
83
     */
84
    public static function findByAttributes(array $attributes = [], $single = false)
85
    {
86
        $query = new Query((new Injector)->getDriver());
87
        foreach ($attributes AS $key => $val) {
88
            $query->addWhere($key.' = :'.$key);
89
        }
90
        $query->params = $attributes;
91
92
        return self::finder($query, $single);
93
    }
94
95
    /**
96
     * Finder data in DB
97
     *
98
     * @access public
99
     *
100
     * @param IQuery $query query to search
101
     * @param boolean $single is single
102
     *
103
     * @return mixed One or more data
104
     * @throws \Micro\base\Exception
105
     * @static
106
     */
107
    public static function finder(IQuery $query = null, $single = false)
108
    {
109
        $query = ($query instanceof Query) ? $query : new Query((new Injector)->getDriver());
110
        $query->table = static::$tableName . ' m';
111
        $query->objectName = get_called_class();
112
        $query->single = $single;
113
114
        return $query->run();
115
    }
116
117
    /**
118
     * Find by model attribute values
119
     *
120
     * @access public
121
     *
122
     * @param bool $single Is a single?
123
     *
124
     * @return mixed
125
     * @throws \Micro\base\Exception
126
     */
127
    public function find($single = false)
128
    {
129
        return self::findByAttributes(Type::getVars($this), $single);
130
    }
131
132
    /**
133
     * Get attributes defined into model
134
     *
135
     * @access public
136
     *
137
     * @return array
138
     * @throws Exception
139
     */
140
    public function getAttributes()
141
    {
142
        $fields = [];
143
        foreach ((new Injector)->getDriver()->listFields(static::$tableName) AS $field) {
144
            $fields[] = $field['field'];
145
        }
146
147
        return $fields;
148
    }
149
150
    /**
151
     * Get relation data or magic properties
152
     *
153
     * @access public
154
     *
155
     * @param string $name
156
     *
157
     * @return mixed
158
     * @throws Exception
159
     */
160
    public function __get($name)
161
    {
162
        /** @var array $relation */
163
        if ($relation = $this->relations()->get($name)) {
164
            if (empty($this->cacheRelations[$name])) {
165
                $sql = new Query((new Injector)->getDriver());
166
167
                if ((new Injector)->getDriver()->getDriverType() === 'pgsql') {
168
                    $sql->addWhere('"m"."' . $relation['On'][1] . '"=:' . $relation['On'][0]);
169
                } else {
170
                    $sql->addWhere('`m`.`' . $relation['On'][1] . '`=:' . $relation['On'][0]);
171
                }
172
173
                if ($relation['Where']) {
174
                    $sql->addWhere($relation['Where']);
175
                }
176
                if ($relation['Params']) {
177
                    $sql->params = $relation['Params'];
178
                }
179
                if ($relation['Limit'] > 0) {
180
                    $sql->limit = $relation['Limit'];
181
                }
182
183
                $sql->params[$relation['On'][0]] = $this->{$relation['On'][0]};
184
185
                /** @noinspection PhpUndefinedMethodInspection */
186
                $this->cacheRelations[$name] = $relation['Model']::finder($sql, !$relation['IsMany']);
187
            }
188
189
            return $this->cacheRelations[$name];
190
        } elseif (isset($this->$name)) {
191
            return $this->$name;
192
        }
193
194
        return false;
195
    }
196
197
    /**
198
     * @inheritdoc
199
     */
200
    public function relations()
201
    {
202
        return new Relations;
203
    }
204
205
    /**
206
     * Save changes
207
     *
208
     * @access public
209
     *
210
     * @param bool $validate Validated data?
211
     *
212
     * @return boolean
213
     * @throws Exception
214
     */
215
    final public function save($validate = false)
216
    {
217
        if ($validate && !$this->validate()) {
218
            return false;
219
        }
220
221
        if ($this->isNewRecord()) {
222
            return $this->create();
223
        } else {
224
            if ($this->beforeSave() && $this->update()) {
225
                $this->afterSave();
226
227
                return true;
228
            }
229
        }
230
231
        return false;
232
    }
233
234
    /**
235
     * Is new record?
236
     *
237
     * @access public
238
     * @return boolean
239
     */
240
    public function isNewRecord()
241
    {
242
        return $this->_isNewRecord;
243
    }
244
245
    /**
246
     * Create changes
247
     *
248
     * @access public
249
     * @return boolean
250
     * @throws Exception
251
     */
252
    final public function create()
253
    {
254
        if (!$this->isNewRecord()) {
255
            return false;
256
        }
257
        if ($this->beforeCreate() && $this->beforeSave()) {
258
            $id = (new Injector)->getDriver()->insert(static::$tableName,
259
                $this->mergeAttributesDb());
260
            if (!$id) {
261
                return false;
262
            }
263
264
            $pKey = self::$primaryKey ?: 'id';
265
            if ($this->checkAttributeExists($pKey)) {
266
                $this->$pKey = $id;
267
            }
268
269
            $this->_isNewRecord = false;
270
271
            $this->afterCreate();
272
            $this->afterSave();
273
274
            return true;
275
        }
276
277
        return false;
278
    }
279
280
    /**
281
     * @inheritdoc
282
     */
283
    public function beforeCreate()
284
    {
285
        return true;
286
    }
287
288
    /**
289
     * @inheritdoc
290
     */
291
    public function beforeSave()
292
    {
293
        return true;
294
    }
295
296
    /**
297
     * Merge local attributes and db attributes
298
     *
299
     * @access protected
300
     *
301
     * @return array
302
     * @throws \Micro\base\Exception
303
     */
304
    protected function mergeAttributesDb()
305
    {
306
        $arr = Type::getVars($this);
307
308
        $buffer = [];
309
        foreach ((new Injector)->getDriver()->listFields(static::$tableName) AS $row) {
310
            $buffer[] = $row['field'];
311
        }
312
313
        foreach ($arr AS $key => $val) {
314
            if (!in_array($key, $buffer, true)) {
315
                unset($arr[$key]);
316
            }
317
        }
318
319
        unset($arr['isNewRecord']);
320
321
        return $arr;
322
    }
323
324
    /**
325
     * Check attribute exists into table
326
     *
327
     * @access public
328
     *
329
     * @param string $name Attribute name
330
     *
331
     * @return boolean
332
     * @throws Exception
333
     */
334
    public function checkAttributeExists($name)
335
    {
336
        if (isset($this->$name)) {
337
            return true;
338
        }
339
340
        $res = false;
341
        foreach ((new Injector)->getDriver()->listFields(static::$tableName) AS $row) {
342
            if ($row['field'] === $name) {
343
                $res = true;
344
                break;
345
            }
346
        }
347
348
        return $res;
349
    }
350
351
    /**
352
     * @inheritdoc
353
     */
354
    public function afterCreate()
355
    {
356
    }
357
358
    /**
359
     * @inheritdoc
360
     */
361
    public function afterSave()
362
    {
363
    }
364
365
    /**
366
     * Update changes
367
     *
368
     * @access public
369
     *
370
     * @param string $where condition for search
371
     *
372
     * @throws Exception
373
     * @return boolean
374
     */
375
    final public function update($where = null)
376
    {
377
        if ($this->isNewRecord()) {
378
            return false;
379
        }
380
        if ($this->beforeUpdate()) {
381
            if (!$where) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $where of type string|null is loosely compared to false; 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...
382
                if (self::$primaryKey) {
383
                    if ((new Injector)->getDriver()->getDriverType() === 'pgsql') {
384
                        $where .= '"' . self::$primaryKey . '" = :' . self::$primaryKey;
385
                    } else {
386
                        $where .= '`' . self::$primaryKey . '` = :' . self::$primaryKey;
387
                    }
388
389
                } else {
390
                    throw new Exception('In table '.static::$tableName.' option `'.self::$primaryKey.'` not defined/not use.'
391
                    );
392
                }
393
            }
394
395
            if ((new Injector)->getDriver()->update(static::$tableName, $this->mergeAttributesDb(), $where)) {
396
                $this->afterUpdate();
397
398
                return true;
399
            }
400
        }
401
402
        return false;
403
    }
404
405
    /**
406
     * @inheritdoc
407
     */
408
    public function beforeUpdate()
409
    {
410
        return true;
411
    }
412
413
    /**
414
     * @inheritdoc
415
     */
416
    public function afterUpdate()
417
    {
418
    }
419
420
    /**
421
     * Delete changes
422
     *
423
     * @access public
424
     * @return boolean
425
     * @throws Exception
426
     */
427
    final public function delete()
428
    {
429
        if ($this->isNewRecord()) {
430
            return false;
431
        }
432
        if ($this->beforeDelete()) {
433
            if (!self::$primaryKey) {
434
                throw new Exception('In table '.static::$tableName.' option `'.self::$primaryKey.'` not defined/not use.');
435
            }
436
437
            if (
438
            (new Injector)->getDriver()->delete(
439
                static::$tableName,
440
                self::$primaryKey.'=:'.self::$primaryKey, [self::$primaryKey => $this->{self::$primaryKey}]
441
            )
442
            ) {
443
                $this->afterDelete();
444
                unset($this);
445
446
                return true;
447
            }
448
        }
449
450
        return false;
451
    }
452
453
    /**
454
     * @inheritdoc
455
     */
456
    public function beforeDelete()
457
    {
458
        return true;
459
    }
460
461
    /**
462
     * @inheritdoc
463
     */
464
    public function afterDelete()
465
    {
466
    }
467
}
468