Completed
Push — master ( 579af5...b29473 )
by Oleg
07:53
created

micro/mvc/models/Model.php (9 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php /** MicroModel */
2
3
namespace Micro\mvc\models;
4
5
use Micro\base\Exception;
6
use Micro\base\IContainer;
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/lugnsk/micro
15
 * @copyright Copyright &copy; 2013 Oleg Lunegov
16
 * @license /LICENSE
17
 * @package micro
18
 * @subpackage db
19
 * @version 1.0
20
 * @since 1.0
21
 */
22
abstract class Model extends FormModel implements IModel
23
{
24
    /** @var string $primaryKey Primary key on table */
25
    public static $primaryKey = 'id';
26
    /** @var boolean $_isNewRecord is new record? */
27
    protected $_isNewRecord = false;
28
    /** @var array $cacheRelations cached loads relations */
29
    protected $cacheRelations = [];
30
31
32
    /**
33
     * Constructor for model
34
     *
35
     * @access public
36
     *
37
     * @param IContainer $container
38
     * @param boolean $new is new model?
39
     *
40
     * @result void
41
     */
42
    public function __construct(IContainer $container, $new = true)
43
    {
44
        parent::__construct($container);
45
46
        $this->_isNewRecord = $new;
47
    }
48
49
    /**
50
     * Finder by primary key
51
     *
52
     * @access public
53
     *
54
     * @param int|string $value unique value
55
     * @param IContainer $container
56
     *
57
     * @return mixed
58
     * @throws \Micro\base\Exception
59
     * @static
60
     */
61
    public static function findByPk($value, IContainer $container)
62
    {
63
        return self::findByAttributes([self::$primaryKey => $value], true, $container);
64
    }
65
66
    /**
67
     * Find models by attributes
68
     *
69
     * @access public
70
     *
71
     * @param array $attributes attributes and data for search
72
     * @param bool $single single or more
73
     * @param IContainer $container
74
     *
75
     * @return mixed
76
     * @throws \Micro\base\Exception
77
     */
78
    public static function findByAttributes(array $attributes = [], $single = false, IContainer $container)
79
    {
80
        $query = new Query($container->db);
0 ignored issues
show
Accessing db on the interface Micro\base\IContainer suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
81
        foreach ($attributes AS $key => $val) {
82
            $query->addWhere($key . ' = :' . $key);
83
        }
84
        $query->params = $attributes;
85
86
        return self::finder($query, $single);
87
    }
88
89
    /**
90
     * Finder data in DB
91
     *
92
     * @access public
93
     *
94
     * @param IQuery $query query to search
95
     * @param boolean $single is single
96
     * @param IContainer $container
97
     *
98
     * @return mixed One or more data
99
     * @throws \Micro\base\Exception
100
     * @static
101
     */
102
    public static function finder(IQuery $query = null, $single = false, IContainer $container = null)
103
    {
104
        $query = ($query instanceof Query) ? $query : new Query($container->db);
0 ignored issues
show
Accessing db on the interface Micro\base\IContainer suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
105
        $query->table = static::tableName() . ' `m`';
106
        $query->objectName = get_called_class();
107
        $query->single = $single;
108
109
        return $query->run();
110
    }
111
112
    /**
113
     * Find by model attribute values
114
     *
115
     * @access public
116
     *
117
     * @param bool $single Is a single?
118
     *
119
     * @return mixed
120
     * @throws \Micro\base\Exception
121
     */
122
    public function find($single = false)
123
    {
124
        return self::findByAttributes(Type::getVars($this), $single, $this->container);
125
    }
126
127
    /**
128
     * Get attributes defined into model
129
     *
130
     * @access public
131
     *
132
     * @return array
133
     */
134
    public function getAttributes()
135
    {
136
        $fields = [];
137
        foreach ($this->container->db->listFields(static::tableName()) AS $field) {
0 ignored issues
show
Accessing db on the interface Micro\base\IContainer suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
138
            $fields[] = $field['field'];
139
        }
140
141
        return $fields;
142
    }
143
144
    /**
145
     * Get relation data or magic properties
146
     *
147
     * @access public
148
     *
149
     * @param string $name
150
     *
151
     * @return mixed
152
     */
153
    public function __get($name)
154
    {
155
        /** @var array $relation */
156
        if ($relation = $this->relations()->get($name)) {
157
            if (empty($this->cacheRelations[$name])) {
158
                $sql = new Query($this->container->db);
0 ignored issues
show
Accessing db on the interface Micro\base\IContainer suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
159
160
                $sql->addWhere('`m`.`' . $relation['On'][1] . '`=:' . $relation['On'][0]);
161
162
                if ($relation['Where']) {
163
                    $sql->addWhere($relation['Where']);
164
                }
165
                if ($relation['Params']) {
166
                    $sql->params = $relation['Params'];
167
                }
168
                if ($relation['Limit'] > 0) {
169
                    $sql->limit = $relation['Limit'];
170
                }
171
172
                $sql->params[$relation['On'][0]] = $this->{$relation['On'][0]};
173
174
                /** @noinspection PhpUndefinedMethodInspection */
175
                $this->cacheRelations[$name] = $relation['Model']::finder($sql, !$relation['IsMany'], $this->container);
176
            }
177
178
            return $this->cacheRelations[$name];
179
        } elseif (isset($this->$name)) {
180
            return $this->$name;
181
        }
182
183
        return false;
184
    }
185
186
    /**
187
     * @inheritdoc
188
     */
189
    public function relations()
190
    {
191
        $keys = new Relations;
192
193
        // add any keys
194
        return $keys;
195
    }
196
197
    /**
198
     * Save changes
199
     *
200
     * @access public
201
     *
202
     * @param bool $validate Validated data?
203
     *
204
     * @return boolean
205
     * @throws Exception
206
     */
207
    final public function save($validate = false)
208
    {
209
        if ($validate && !$this->validate()) {
210
            return false;
211
        }
212
213
        if ($this->isNewRecord()) {
214
            return $this->create();
215
        } else {
216
            if ($this->beforeSave() && $this->update()) {
217
                $this->afterSave();
218
219
                return true;
220
            }
221
        }
222
223
        return false;
224
    }
225
226
    /**
227
     * Is new record?
228
     *
229
     * @access public
230
     * @return boolean
231
     */
232
    public function isNewRecord()
233
    {
234
        return $this->_isNewRecord;
235
    }
236
237
    /**
238
     * Create changes
239
     *
240
     * @access public
241
     * @return boolean
242
     * @throws Exception
243
     */
244
    final public function create()
245
    {
246
        if (!$this->isNewRecord()) {
247
            return false;
248
        }
249
        if ($this->beforeCreate() && $this->beforeSave()) {
250
            $id = $this->container->db->insert(static::tableName(), $this->mergeAttributesDb());
0 ignored issues
show
Accessing db on the interface Micro\base\IContainer suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
251
            if (!$id) {
252
                return false;
253
            }
254
255
            $pKey = self::$primaryKey ?: 'id';
256
            if ($this->checkAttributeExists($pKey)) {
257
                $this->$pKey = $id;
258
            }
259
260
            $this->_isNewRecord = false;
261
262
            $this->afterCreate();
263
            $this->afterSave();
264
265
            return true;
266
        }
267
268
        return false;
269
    }
270
271
    /**
272
     * @inheritdoc
273
     */
274
    public function beforeCreate()
275
    {
276
        return true;
277
    }
278
279
    /**
280
     * @inheritdoc
281
     */
282
    public function beforeSave()
283
    {
284
        return true;
285
    }
286
287
    /**
288
     * Merge local attributes and db attributes
289
     *
290
     * @access protected
291
     *
292
     * @return array
293
     * @throws \Micro\base\Exception
294
     */
295
    protected function mergeAttributesDb()
296
    {
297
        $arr = Type::getVars($this);
298
299
        $buffer = [];
300
        foreach ($this->container->db->listFields(static::tableName()) AS $row) {
0 ignored issues
show
Accessing db on the interface Micro\base\IContainer suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
301
            $buffer[] = $row['field'];
302
        }
303
304
        foreach ($arr AS $key => $val) {
305
            if (!in_array($key, $buffer, true)) {
306
                unset($arr[$key]);
307
            }
308
        }
309
310
        unset($arr['isNewRecord']);
311
312
        return $arr;
313
    }
314
315
    /**
316
     * Check attribute exists into table
317
     *
318
     * @access public
319
     *
320
     * @param string $name Attribute name
321
     *
322
     * @return array
323
     */
324
    public function checkAttributeExists($name)
325
    {
326
        if (isset($this->$name)) {
327
            return true;
328
        }
329
330
        $res = false;
331
        foreach ($this->container->db->listFields(static::tableName()) AS $row) {
0 ignored issues
show
Accessing db on the interface Micro\base\IContainer suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
332
            if ($row['field'] === $name) {
333
                $res = true;
334
                break;
335
            }
336
        }
337
338
        return $res;
339
    }
340
341
    /**
342
     * @inheritdoc
343
     */
344
    public function afterCreate()
345
    {
346
    }
347
348
    /**
349
     * @inheritdoc
350
     */
351
    public function afterSave()
352
    {
353
    }
354
355
    /**
356
     * Update changes
357
     *
358
     * @access public
359
     *
360
     * @param string $where condition for search
361
     *
362
     * @throws Exception
363
     * @return boolean
364
     */
365
    final public function update($where = null)
366
    {
367
        if ($this->isNewRecord()) {
368
            return false;
369
        }
370
        if ($this->beforeUpdate()) {
371
            if (!$where) {
372
                if (self::$primaryKey) {
373
                    $where .= '`' . self::$primaryKey . '` = :' . self::$primaryKey;
374
                } else {
375
                    throw new Exception ($this->container,
376
                        'In table ' . static::tableName() . ' option `id` not defined/not use.'
377
                    );
378
                }
379
            }
380
381
            if ($this->container->db->update(static::tableName(), $this->mergeAttributesDb(), $where)) {
0 ignored issues
show
Accessing db on the interface Micro\base\IContainer suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
382
                $this->afterUpdate();
383
384
                return true;
385
            }
386
        }
387
388
        return false;
389
    }
390
391
    /**
392
     * @inheritdoc
393
     */
394
    public function beforeUpdate()
395
    {
396
        return true;
397
    }
398
399
    /**
400
     * @inheritdoc
401
     */
402
    public function afterUpdate()
403
    {
404
    }
405
406
    /**
407
     * Delete changes
408
     *
409
     * @access public
410
     * @return boolean
411
     * @throws Exception
412
     */
413
    final public function delete()
414
    {
415
        if ($this->isNewRecord()) {
416
            return false;
417
        }
418
        if ($this->beforeDelete()) {
419
            if (!self::$primaryKey) {
420
                throw new Exception('In table ' . static::tableName() . ' option `id` not defined/not use.');
421
            }
422
423
            if (
424
            $this->container->db->delete(
0 ignored issues
show
Accessing db on the interface Micro\base\IContainer suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
425
                static::tableName(),
426
                self::$primaryKey . '=:' . self::$primaryKey, [self::$primaryKey => $this->{self::$primaryKey}]
427
            )
428
            ) {
429
                $this->afterDelete();
430
                unset($this);
431
432
                return true;
433
            }
434
        }
435
436
        return false;
437
    }
438
439
    /**
440
     * @inheritdoc
441
     */
442
    public function beforeDelete()
443
    {
444
        return true;
445
    }
446
447
    /**
448
     * @inheritdoc
449
     */
450
    public function afterDelete()
451
    {
452
    }
453
}
454