Completed
Push — master ( 467d47...6f2bb4 )
by Anton
14s
created

Row::getRelations()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 8
ccs 3
cts 4
cp 0.75
crap 2.0625
rs 9.4285
c 0
b 0
f 0
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
     * @throws TableNotFoundException
111
     * @see var_dump()
112
     */
113
    public function __debugInfo()
114
    {
115
        return [
116
            'DATA::CLEAN' => $this->clean,
117
            'DATA::RAW' => $this->container,
118
            'RELATIONS' => $this->relations ?? []
119
        ];
120
    }
121
122
    /**
123
     * Validate input data
124
     *
125
     * @param  array $data
126
     *
127
     * @return bool
128
     */
129
    public function validate($data) : bool
130
    {
131
        return true;
132
    }
133
134
    /**
135
     * Assert input data
136
     *
137
     * @param  array $data
138
     *
139
     * @return void
140
     */
141
    public function assert($data) : void
142
    {
143
        return;
144
    }
145
146
    /**
147
     * Saves the properties to the database.
148
     *
149
     * This performs an intelligent insert/update, and reloads the
150
     * properties with fresh data from the table on success.
151
     *
152
     * @return mixed The primary key value(s), as an associative array if the
153
     *               key is compound, or a scalar if the key is single-column
154
     * @throws DbException
155
     * @throws InvalidPrimaryKeyException
156
     * @throws TableNotFoundException
157
     * @throws \Bluz\Common\Exception\ConfigurationException
158
     */
159 2
    public function save()
160
    {
161 2
        $this->beforeSave();
162
        /**
163
         * If the primary key is empty, this is an INSERT of a new row.
164
         * Otherwise check primary key updated or not, if it changed - INSERT
165
         * otherwise UPDATE
166
         */
167 2
        if (!count(array_filter($this->getPrimaryKey()))) {
168 1
            $result = $this->doInsert();
169 1
        } elseif (count(array_diff_assoc($this->getPrimaryKey(), $this->clean))) {
170
            $result = $this->doInsert();
171
        } else {
172 1
            $result = $this->doUpdate();
173
        }
174 2
        $this->afterSave();
175 2
        return $result;
176
    }
177
178
    /**
179
     * Insert row to Db
180
     *
181
     * @return mixed The primary key value(s), as an associative array if the
182
     *               key is compound, or a scalar if the key is single-column
183
     * @throws DbException
184
     * @throws InvalidPrimaryKeyException
185
     * @throws TableNotFoundException
186
     * @throws \Bluz\Common\Exception\ConfigurationException
187
     */
188 1
    protected function doInsert()
189
    {
190
        /**
191
         * Run pre-INSERT logic
192
         */
193 1
        $this->beforeInsert();
194
195 1
        $data = $this->toArray();
196
        /**
197
         * Execute validator logic
198
         * Can throw ValidatorException
199
         */
200 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...
201
202 1
        $table = $this->getTable();
203
204
        /**
205
         * Execute the INSERT (this may throw an exception)
206
         */
207 1
        $primaryKey = $table::insert($data);
208
209
        /**
210
         * Normalize the result to an array indexed by primary key column(s)
211
         */
212 1
        $tempPrimaryKey = $table->getPrimaryKey();
213 1
        $newPrimaryKey = [current($tempPrimaryKey) => $primaryKey];
214
215
        /**
216
         * Save the new primary key value in object. The primary key may have
217
         * been generated by a sequence or auto-increment mechanism, and this
218
         * merge should be done before the afterInsert() method is run, so the
219
         * new values are available for logging, etc.
220
         */
221 1
        $this->setFromArray($newPrimaryKey);
222
223
        /**
224
         * Run post-INSERT logic
225
         */
226 1
        $this->afterInsert();
227
228
        /**
229
         * Update the "clean" to reflect that the data has been inserted.
230
         */
231 1
        $this->clean = $this->toArray();
232
233 1
        return $newPrimaryKey;
234
    }
235
236
    /**
237
     * Update row
238
     *
239
     * @return integer The number of rows updated
240
     * @throws DbException
241
     * @throws InvalidPrimaryKeyException
242
     * @throws TableNotFoundException
243
     * @throws \Bluz\Common\Exception\ConfigurationException
244
     */
245 1
    protected function doUpdate() : int
246
    {
247
        /**
248
         * Run pre-UPDATE logic
249
         */
250 1
        $this->beforeUpdate();
251
252 1
        $data = $this->toArray();
253
254
        /**
255
         * Execute validator logic
256
         * Can throw ValidatorException
257
         */
258 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...
259
260 1
        $primaryKey = $this->getPrimaryKey();
261
262
        /**
263
         * Compare the data to the modified fields array to discover
264
         * which columns have been changed.
265
         */
266 1
        $diffData = array_diff_assoc($data, $this->clean);
267
268 1
        $table = $this->getTable();
269
270 1
        $diffData = $table::filterColumns($diffData);
271
272
        /**
273
         * Execute the UPDATE (this may throw an exception)
274
         * Do this only if data values were changed.
275
         * Use the $diffData variable, so the UPDATE statement
276
         * includes SET terms only for data values that changed.
277
         */
278 1
        $result = 0;
279 1
        if (count($diffData) > 0) {
280 1
            $result = $table::update($diffData, $primaryKey);
281
        }
282
283
        /**
284
         * Run post-UPDATE logic.  Do this before the _refresh()
285
         * so the _afterUpdate() function can tell the difference
286
         * between changed data and clean (pre-changed) data.
287
         */
288 1
        $this->afterUpdate();
289
290
        /**
291
         * Refresh the data just in case triggers in the RDBMS changed
292
         * any columns.  Also this resets the "clean".
293
         */
294 1
        $this->clean = $this->toArray();
295
296 1
        return $result;
297
    }
298
299
    /**
300
     * Delete existing row
301
     *
302
     * @return bool Removed or not
303
     * @throws \Bluz\Common\Exception\ConfigurationException
304
     * @throws DbException
305
     * @throws InvalidPrimaryKeyException
306
     * @throws TableNotFoundException
307
     */
308 1
    public function delete() : bool
309
    {
310
        /**
311
         * Execute pre-DELETE logic
312
         */
313 1
        $this->beforeDelete();
314
315 1
        $primaryKey = $this->getPrimaryKey();
316
317
        /**
318
         * Execute the DELETE (this may throw an exception)
319
         */
320 1
        $table = $this->getTable();
321 1
        $result = $table::delete($primaryKey);
322
323
        /**
324
         * Execute post-DELETE logic
325
         */
326 1
        $this->afterDelete();
327
328
        /**
329
         * Reset all fields to null to indicate that the row is not there
330
         */
331 1
        $this->resetArray();
332
333 1
        return $result > 0;
334
    }
335
336
    /**
337
     * Retrieves an associative array of primary keys, if it exists
338
     *
339
     * @return array
340
     * @throws InvalidPrimaryKeyException
341
     * @throws TableNotFoundException
342
     */
343 3
    protected function getPrimaryKey() : array
344
    {
345 3
        $primary = array_flip($this->getTable()->getPrimaryKey());
346
347 3
        return array_intersect_key($this->toArray(), $primary);
348
    }
349
350
    /**
351
     * Refreshes properties from the database
352
     *
353
     * @return void
354
     */
355
    public function refresh() : void
356
    {
357
        $this->setFromArray($this->clean);
358
        $this->afterRead();
359
    }
360
361
    /**
362
     * After read data from Db
363
     *
364
     * @return void
365
     */
366 17
    protected function afterRead() : void
367
    {
368 17
    }
369
370
    /**
371
     * Allows pre-insert and pre-update logic to be applied to row.
372
     * Subclasses may override this method
373
     *
374
     * @return void
375
     */
376
    protected function beforeSave() : void
377
    {
378
    }
379
380
    /**
381
     * Allows post-insert and post-update logic to be applied to row.
382
     * Subclasses may override this method
383
     *
384
     * @return void
385
     */
386 2
    protected function afterSave() : void
387
    {
388 2
    }
389
390
    /**
391
     * Allows pre-insert logic to be applied to row.
392
     * Subclasses may override this method
393
     *
394
     * @return void
395
     */
396 1
    protected function beforeInsert() : void
397
    {
398 1
    }
399
400
    /**
401
     * Allows post-insert logic to be applied to row.
402
     * Subclasses may override this method
403
     *
404
     * @return void
405
     */
406 1
    protected function afterInsert() : void
407
    {
408 1
    }
409
410
    /**
411
     * Allows pre-update logic to be applied to row.
412
     * Subclasses may override this method
413
     *
414
     * @return void
415
     */
416 1
    protected function beforeUpdate() : void
417
    {
418 1
    }
419
420
    /**
421
     * Allows post-update logic to be applied to row.
422
     * Subclasses may override this method
423
     *
424
     * @return void
425
     */
426 1
    protected function afterUpdate() : void
427
    {
428 1
    }
429
430
    /**
431
     * Allows pre-delete logic to be applied to row.
432
     * Subclasses may override this method
433
     *
434
     * @return void
435
     */
436 1
    protected function beforeDelete() : void
437
    {
438 1
    }
439
440
    /**
441
     * Allows post-delete logic to be applied to row.
442
     * Subclasses may override this method
443
     *
444
     * @return void
445
     */
446 1
    protected function afterDelete() : void
447
    {
448 1
    }
449
}
450