Completed
Push — master ( f4415a...651f7b )
by Anton
12s
created

Row   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 397
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 76.74%

Importance

Changes 0
Metric Value
dl 0
loc 397
ccs 66
cts 86
cp 0.7674
rs 10
c 0
b 0
f 0
wmc 25
lcom 1
cbo 7

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 2
A __sleep() 0 4 1
A __toString() 0 4 1
A __debugInfo() 0 8 1
A validate() 0 4 1
A assert() 0 4 1
A save() 0 18 3
A doInsert() 0 47 1
A doUpdate() 0 53 2
A delete() 0 27 1
A getPrimaryKey() 0 6 1
A refresh() 0 5 1
A afterRead() 0 3 1
A beforeSave() 0 3 1
A afterSave() 0 3 1
A beforeInsert() 0 3 1
A afterInsert() 0 3 1
A beforeUpdate() 0 3 1
A afterUpdate() 0 3 1
A beforeDelete() 0 3 1
A afterDelete() 0 3 1
1
<?php
2
/**
3
 * Bluz Framework Component
4
 *
5
 * @copyright Bluz PHP Team
6
 * @link      https://github.com/bluzphp/framework
7
 */
8
9
declare(strict_types=1);
10
11
namespace Bluz\Db;
12
13
use Bluz\Common\Container;
14
use Bluz\Db\Exception\DbException;
15
use Bluz\Db\Exception\InvalidPrimaryKeyException;
16
use Bluz\Db\Exception\TableNotFoundException;
17
18
/**
19
 * Db Table Row
20
 *
21
 * Example of Users\Row
22
 * <code>
23
 *     namespace Application\Users;
24
 *     class Row extends \Bluz\Db\Row
25
 *     {
26
 *        public function beforeInsert() : void
27
 *        {
28
 *            $this->created = gmdate('Y-m-d H:i:s');
29
 *        }
30
 *
31
 *        public function beforeUpdate() : void
32
 *        {
33
 *            $this->updated = gmdate('Y-m-d H:i:s');
34
 *        }
35
 *     }
36
 *
37
 *     $userRow = new \Application\Users\Row();
38
 *     $userRow -> login = 'username';
39
 *     $userRow -> save();
40
 * </code>
41
 *
42
 * @package  Bluz\Db
43
 * @author   Anton Shevchuk
44
 * @link     https://github.com/bluzphp/framework/wiki/Db-Row
45
 */
46
abstract class Row implements RowInterface, \JsonSerializable, \ArrayAccess
47
{
48
    use Container\Container;
49
    use Container\ArrayAccess;
50
    use Container\JsonSerialize;
51
    use Container\MagicAccess;
52
    use Traits\TableProperty;
53
    use Traits\RowRelations;
54
55
    /**
56
     * @var Table instance of Table class
57
     */
58
    protected $table;
59
60
    /**
61
     * This is set to a copy of $data when the data is fetched from
62
     * a database, specified as a new tuple in the constructor, or
63
     * when dirty data is posted to the database with save().
64
     *
65
     * @var array
66
     */
67
    protected $clean = [];
68
69
    /**
70
     * Create Row instance
71
     *
72
     * @param array $data
73
     */
74 17
    public function __construct(array $data = [])
75
    {
76
        // original cleaner data
77 17
        $this->clean = $this->toArray();
78
79
        // not clean data, but not modified
80 17
        if (\count($data)) {
81
            $this->setFromArray($data);
82
        }
83 17
        $this->afterRead();
84 17
    }
85
86
    /**
87
     * List of required for serialization properties
88
     *
89
     * @return string[]
90
     */
91
    public function __sleep()
92
    {
93
        return ['container', 'clean'];
94
    }
95
96
    /**
97
     * Cast to string as class name
98
     *
99
     * @return string
100
     */
101
    public function __toString()
102
    {
103
        return static::class;
104
    }
105
106
    /**
107
     * Magic method for var_dump()
108
     *
109
     * @return array
110
     * @see var_dump()
111
     */
112
    public function __debugInfo()
113
    {
114
        return [
115
            'DATA::CLEAN' => $this->clean,
116
            'DATA::RAW' => $this->container,
117
            'RELATIONS' => $this->relations ?? []
118
        ];
119
    }
120
121
    /**
122
     * Validate input data
123
     *
124
     * @param  array $data
125
     *
126
     * @return bool
127
     */
128
    public function validate($data) : bool
129
    {
130
        return true;
131
    }
132
133
    /**
134
     * Assert input data
135
     *
136
     * @param  array $data
137
     *
138
     * @return void
139
     */
140
    public function assert($data) : void
141
    {
142
        return;
143
    }
144
145
    /**
146
     * Saves the properties to the database.
147
     *
148
     * This performs an intelligent insert/update, and reloads the
149
     * properties with fresh data from the table on success.
150
     *
151
     * @return mixed The primary key value(s), as an associative array if the
152
     *               key is compound, or a scalar if the key is single-column
153
     * @throws DbException
154
     * @throws InvalidPrimaryKeyException
155
     * @throws TableNotFoundException
156
     * @throws \Bluz\Common\Exception\ConfigurationException
157
     */
158 2
    public function save()
159
    {
160 2
        $this->beforeSave();
161
        /**
162
         * If the primary key is empty, this is an INSERT of a new row.
163
         * Otherwise check primary key updated or not, if it changed - INSERT
164
         * otherwise UPDATE
165
         */
166 2
        if (!\count(\array_filter($this->getPrimaryKey()))) {
167 1
            $result = $this->doInsert();
168 1
        } elseif (\count(\array_diff_assoc($this->getPrimaryKey(), $this->clean))) {
169
            $result = $this->doInsert();
170
        } else {
171 1
            $result = $this->doUpdate();
172
        }
173 2
        $this->afterSave();
174 2
        return $result;
175
    }
176
177
    /**
178
     * Insert row to Db
179
     *
180
     * @return mixed The primary key value(s), as an associative array if the
181
     *               key is compound, or a scalar if the key is single-column
182
     * @throws InvalidPrimaryKeyException
183
     * @throws TableNotFoundException
184
     */
185 1
    protected function doInsert()
186
    {
187
        /**
188
         * Run pre-INSERT logic
189
         */
190 1
        $this->beforeInsert();
191
192 1
        $data = $this->toArray();
193
        /**
194
         * Execute validator logic
195
         * Can throw ValidatorException
196
         */
197 1
        $this->assert($data);
0 ignored issues
show
Unused Code introduced by
The call to the method Bluz\Db\Row::assert() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
198
199 1
        $table = $this->getTable();
200
201
        /**
202
         * Execute the INSERT (this may throw an exception)
203
         */
204 1
        $primaryKey = $table::insert($data);
205
206
        /**
207
         * Normalize the result to an array indexed by primary key column(s)
208
         */
209 1
        $tempPrimaryKey = $table->getPrimaryKey();
210 1
        $newPrimaryKey = [current($tempPrimaryKey) => $primaryKey];
211
212
        /**
213
         * Save the new primary key value in object. The primary key may have
214
         * been generated by a sequence or auto-increment mechanism, and this
215
         * merge should be done before the afterInsert() method is run, so the
216
         * new values are available for logging, etc.
217
         */
218 1
        $this->setFromArray($newPrimaryKey);
219
220
        /**
221
         * Run post-INSERT logic
222
         */
223 1
        $this->afterInsert();
224
225
        /**
226
         * Update the "clean" to reflect that the data has been inserted.
227
         */
228 1
        $this->clean = $this->toArray();
229
230 1
        return $newPrimaryKey;
231
    }
232
233
    /**
234
     * Update row
235
     *
236
     * @return integer The number of rows updated
237
     * @throws InvalidPrimaryKeyException
238
     * @throws TableNotFoundException
239
     */
240 1
    protected function doUpdate() : int
241
    {
242
        /**
243
         * Run pre-UPDATE logic
244
         */
245 1
        $this->beforeUpdate();
246
247 1
        $data = $this->toArray();
248
249
        /**
250
         * Execute validator logic
251
         * Can throw ValidatorException
252
         */
253 1
        $this->assert($data);
0 ignored issues
show
Unused Code introduced by
The call to the method Bluz\Db\Row::assert() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
254
255 1
        $primaryKey = $this->getPrimaryKey();
256
257
        /**
258
         * Compare the data to the modified fields array to discover
259
         * which columns have been changed.
260
         */
261 1
        $diffData = array_diff_assoc($data, $this->clean);
262
263 1
        $table = $this->getTable();
264
265 1
        $diffData = $table::filterColumns($diffData);
266
267
        /**
268
         * Execute the UPDATE (this may throw an exception)
269
         * Do this only if data values were changed.
270
         * Use the $diffData variable, so the UPDATE statement
271
         * includes SET terms only for data values that changed.
272
         */
273 1
        $result = 0;
274 1
        if (\count($diffData) > 0) {
275 1
            $result = $table::update($diffData, $primaryKey);
276
        }
277
278
        /**
279
         * Run post-UPDATE logic.  Do this before the _refresh()
280
         * so the _afterUpdate() function can tell the difference
281
         * between changed data and clean (pre-changed) data.
282
         */
283 1
        $this->afterUpdate();
284
285
        /**
286
         * Refresh the data just in case triggers in the RDBMS changed
287
         * any columns.  Also this resets the "clean".
288
         */
289 1
        $this->clean = $this->toArray();
290
291 1
        return $result;
292
    }
293
294
    /**
295
     * Delete existing row
296
     *
297
     * @return bool Removed or not
298
     * @throws InvalidPrimaryKeyException
299
     * @throws TableNotFoundException
300
     */
301 1
    public function delete() : bool
302
    {
303
        /**
304
         * Execute pre-DELETE logic
305
         */
306 1
        $this->beforeDelete();
307
308 1
        $primaryKey = $this->getPrimaryKey();
309
310
        /**
311
         * Execute the DELETE (this may throw an exception)
312
         */
313 1
        $table = $this->getTable();
314 1
        $result = $table::delete($primaryKey);
315
316
        /**
317
         * Execute post-DELETE logic
318
         */
319 1
        $this->afterDelete();
320
321
        /**
322
         * Reset all fields to null to indicate that the row is not there
323
         */
324 1
        $this->resetArray();
325
326 1
        return $result > 0;
327
    }
328
329
    /**
330
     * Retrieves an associative array of primary keys, if it exists
331
     *
332
     * @return array
333
     * @throws InvalidPrimaryKeyException
334
     * @throws TableNotFoundException
335
     */
336 3
    protected function getPrimaryKey() : array
337
    {
338 3
        $primary = array_flip($this->getTable()->getPrimaryKey());
339
340 3
        return array_intersect_key($this->toArray(), $primary);
341
    }
342
343
    /**
344
     * Refreshes properties from the database
345
     *
346
     * @return void
347
     */
348
    public function refresh() : void
349
    {
350
        $this->setFromArray($this->clean);
351
        $this->afterRead();
352
    }
353
354
    /**
355
     * After read data from Db
356
     *
357
     * @return void
358
     */
359 17
    protected function afterRead() : void
360
    {
361 17
    }
362
363
    /**
364
     * Allows pre-insert and pre-update logic to be applied to row.
365
     * Subclasses may override this method
366
     *
367
     * @return void
368
     */
369
    protected function beforeSave() : void
370
    {
371
    }
372
373
    /**
374
     * Allows post-insert and post-update logic to be applied to row.
375
     * Subclasses may override this method
376
     *
377
     * @return void
378
     */
379 2
    protected function afterSave() : void
380
    {
381 2
    }
382
383
    /**
384
     * Allows pre-insert logic to be applied to row.
385
     * Subclasses may override this method
386
     *
387
     * @return void
388
     */
389 1
    protected function beforeInsert() : void
390
    {
391 1
    }
392
393
    /**
394
     * Allows post-insert logic to be applied to row.
395
     * Subclasses may override this method
396
     *
397
     * @return void
398
     */
399 1
    protected function afterInsert() : void
400
    {
401 1
    }
402
403
    /**
404
     * Allows pre-update logic to be applied to row.
405
     * Subclasses may override this method
406
     *
407
     * @return void
408
     */
409 1
    protected function beforeUpdate() : void
410
    {
411 1
    }
412
413
    /**
414
     * Allows post-update logic to be applied to row.
415
     * Subclasses may override this method
416
     *
417
     * @return void
418
     */
419 1
    protected function afterUpdate() : void
420
    {
421 1
    }
422
423
    /**
424
     * Allows pre-delete logic to be applied to row.
425
     * Subclasses may override this method
426
     *
427
     * @return void
428
     */
429 1
    protected function beforeDelete() : void
430
    {
431 1
    }
432
433
    /**
434
     * Allows post-delete logic to be applied to row.
435
     * Subclasses may override this method
436
     *
437
     * @return void
438
     */
439 1
    protected function afterDelete() : void
440
    {
441 1
    }
442
}
443