Completed
Pull Request — master (#409)
by Anton
05:45
created

Row::__toString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
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\InvalidPrimaryKeyException;
15
use Bluz\Db\Exception\RelationNotFoundException;
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()
27
 *        {
28
 *            $this->created = gmdate('Y-m-d H:i:s');
29
 *        }
30
 *
31
 *        public function beforeUpdate()
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
class Row implements \JsonSerializable, \ArrayAccess
47
{
48
    use Container\Container;
49
    use Container\ArrayAccess;
50
    use Container\JsonSerialize;
51
    use Container\MagicAccess;
52
53
    /**
54
     * @var Table instance of Table class
55
     */
56
    protected $table;
57
58
    /**
59
     * @var string name of Table class
60
     */
61
    protected $tableClass;
62
63
    /**
64
     * This is set to a copy of $data when the data is fetched from
65
     * a database, specified as a new tuple in the constructor, or
66
     * when dirty data is posted to the database with save().
67
     *
68
     * @var array
69
     */
70
    protected $clean = [];
71
72
    /**
73
     * @var array relations rows
74
     */
75
    protected $relations = [];
76
77
    /**
78
     * Create Row instance
79
     *
80
     * @param array $data
81
     */
82 14
    public function __construct($data = [])
83
    {
84
        // original cleaner data
85 14
        $this->clean = $this->toArray();
86
87
        // not clean data, but not modified
88 14
        if (sizeof($data)) {
89
            $this->setFromArray($data);
90
        }
91 14
        $this->afterRead();
92 14
    }
93
94
    /**
95
     * List of required for serialization properties
96
     *
97
     * @return string[]
98
     */
99
    public function __sleep()
100
    {
101
        return ['container', 'clean'];
102
    }
103
104
    /**
105
     * Cast to string as class name
106
     *
107
     * @return string
108
     */
109
    public function __toString()
110
    {
111
        return static::class;
112
    }
113
114
    /**
115
     * Magic method for var_dump()
116
     *
117
     * @return array
118
     * @see var_dump()
119
     */
120
    public function __debugInfo()
121
    {
122
        return [
123
            'TABLE' => $this->getTable()->getName(),
124
            'DATA::CLEAN' => $this->clean,
125
            'DATA::RAW' => $this->container,
126
            'RELATIONS' => $this->relations
127
        ];
128
    }
129
130
    /**
131
     * Validate input data
132
     *
133
     * @param  array|object $data
134
     * @return bool
135
     */
136
    public function validate($data)
137
    {
138
        return true;
139
    }
140
141
    /**
142
     * Assert input data
143
     *
144
     * @param  array|object $data
145
     * @return bool
146
     */
147
    public function assert($data)
148
    {
149
        return true;
150
    }
151
152
    /**
153
     * Saves the properties to the database.
154
     *
155
     * This performs an intelligent insert/update, and reloads the
156
     * properties with fresh data from the table on success.
157
     *
158
     * @return mixed The primary key value(s), as an associative array if the
159
     *               key is compound, or a scalar if the key is single-column
160
     */
161 2
    public function save()
162
    {
163 2
        $this->beforeSave();
164
        /**
165
         * If the primary key is empty, this is an INSERT of a new row.
166
         * Otherwise check primary key updated or not, if it changed - INSERT
167
         * otherwise UPDATE
168
         */
169 2
        if (!sizeof(array_filter($this->getPrimaryKey()))) {
170 1
            $result = $this->doInsert();
171 1
        } elseif (sizeof(array_diff_assoc($this->getPrimaryKey(), $this->clean))) {
172
            $result = $this->doInsert();
173
        } else {
174 1
            $result = $this->doUpdate();
175
        }
176 2
        $this->afterSave();
177 2
        return $result;
178
    }
179
180
    /**
181
     * Insert row to Db
182
     *
183
     * @return mixed The primary key value(s), as an associative array if the
184
     *               key is compound, or a scalar if the key is single-column
185
     */
186 1
    protected function doInsert()
187
    {
188
        /**
189
         * Run pre-INSERT logic
190
         */
191 1
        $this->beforeInsert();
192
193 1
        $data = $this->toArray();
194
195
        /**
196
         * Execute validator logic
197
         * Can throw ValidatorException
198
         */
199 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...
200
201 1
        $table = $this->getTable();
202
203
        /**
204
         * Execute the INSERT (this may throw an exception)
205
         */
206 1
        $primaryKey = $table::insert($data);
207
208
        /**
209
         * Normalize the result to an array indexed by primary key column(s)
210
         */
211 1
        $tempPrimaryKey = $table->getPrimaryKey();
212 1
        $newPrimaryKey = [current($tempPrimaryKey) => $primaryKey];
213
214
        /**
215
         * Save the new primary key value in object. The primary key may have
216
         * been generated by a sequence or auto-increment mechanism, and this
217
         * merge should be done before the afterInsert() method is run, so the
218
         * new values are available for logging, etc.
219
         */
220 1
        $this->setFromArray($newPrimaryKey);
221
222
        /**
223
         * Run post-INSERT logic
224
         */
225 1
        $this->afterInsert();
226
227
        /**
228
         * Update the "clean" to reflect that the data has been inserted.
229
         */
230 1
        $this->clean = $this->toArray();
231
232 1
        return $newPrimaryKey;
233
    }
234
235
    /**
236
     * Update row
237
     *
238
     * @return integer The number of rows updated
239
     */
240 1
    protected function doUpdate()
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 1
        $diffData = $table::filterColumns($diffData);
265
266
        /**
267
         * Execute the UPDATE (this may throw an exception)
268
         * Do this only if data values were changed.
269
         * Use the $diffData variable, so the UPDATE statement
270
         * includes SET terms only for data values that changed.
271
         */
272 1
        if (sizeof($diffData) > 0) {
273 1
            $result = $table::update($diffData, $primaryKey);
274
        } else {
275
            $result = 0;
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 integer The number of deleted rows
298
     */
299 1
    public function delete()
300
    {
301
        /**
302
         * Execute pre-DELETE logic
303
         */
304 1
        $this->beforeDelete();
305
306 1
        $primaryKey = $this->getPrimaryKey();
307
308
        /**
309
         * Execute the DELETE (this may throw an exception)
310
         */
311 1
        $table = $this->getTable();
312 1
        $result = $table::delete($primaryKey);
313
314
        /**
315
         * Execute post-DELETE logic
316
         */
317 1
        $this->afterDelete();
318
319
        /**
320
         * Reset all fields to null to indicate that the row is not there
321
         */
322 1
        $this->resetArray();
323
324 1
        return $result;
325
    }
326
327
    /**
328
     * Retrieves an associative array of primary keys, if it exists
329
     *
330
     * @throws InvalidPrimaryKeyException
331
     * @return array
332
     */
333 3
    protected function getPrimaryKey()
334
    {
335 3
        $primary = array_flip($this->getTable()->getPrimaryKey());
336
337 3
        $array = array_intersect_key($this->toArray(), $primary);
338
339 3
        return $array;
340
    }
341
342
    /**
343
     * Refreshes properties from the database
344
     *
345
     * @return void
346
     */
347
    public function refresh()
348
    {
349
        $this->setFromArray($this->clean);
350
        $this->afterRead();
351
    }
352
353
    /**
354
     * After read data from Db
355
     *
356
     * @return void
357
     */
358 14
    protected function afterRead()
359
    {
360 14
    }
361
362
    /**
363
     * Allows pre-insert and pre-update logic to be applied to row.
364
     * Subclasses may override this method
365
     *
366
     * @return void
367
     */
368
    protected function beforeSave()
369
    {
370
    }
371
372
    /**
373
     * Allows post-insert and post-update logic to be applied to row.
374
     * Subclasses may override this method
375
     *
376
     * @return void
377
     */
378 2
    protected function afterSave()
379
    {
380 2
    }
381
382
    /**
383
     * Allows pre-insert logic to be applied to row.
384
     * Subclasses may override this method
385
     *
386
     * @return void
387
     */
388 1
    protected function beforeInsert()
389
    {
390 1
    }
391
392
    /**
393
     * Allows post-insert logic to be applied to row.
394
     * Subclasses may override this method
395
     *
396
     * @return void
397
     */
398 1
    protected function afterInsert()
399
    {
400 1
    }
401
402
    /**
403
     * Allows pre-update logic to be applied to row.
404
     * Subclasses may override this method
405
     *
406
     * @return void
407
     */
408 1
    protected function beforeUpdate()
409
    {
410 1
    }
411
412
    /**
413
     * Allows post-update logic to be applied to row.
414
     * Subclasses may override this method
415
     *
416
     * @return void
417
     */
418 1
    protected function afterUpdate()
419
    {
420 1
    }
421
422
    /**
423
     * Allows pre-delete logic to be applied to row.
424
     * Subclasses may override this method
425
     *
426
     * @return void
427
     */
428 1
    protected function beforeDelete()
429
    {
430 1
    }
431
432
    /**
433
     * Allows post-delete logic to be applied to row.
434
     * Subclasses may override this method
435
     *
436
     * @return void
437
     */
438 1
    protected function afterDelete()
439
    {
440 1
    }
441
442
    /**
443
     * Setup Table instance
444
     *
445
     * @param  Table $table
446
     * @return self
447
     */
448 3
    public function setTable(Table $table)
449
    {
450 3
        $this->table = $table;
451 3
        return $this;
452
    }
453
454
    /**
455
     * Returns the table object, or null if this is disconnected row
456
     *
457
     * @throws TableNotFoundException
458
     * @return Table
459
     */
460 3
    public function getTable()
461
    {
462 3
        if ($this->table instanceof Table) {
463
            return $this->table;
464
        }
465
466 3
        if ($this->tableClass) {
467 3
            $tableClass = $this->tableClass;
468
        } else {
469
            // try to guess table class
470
            $rowClass = get_class($this);
471
            /**
472
             * @var string $tableClass is child of \Bluz\Db\Table
473
             */
474
            $tableClass = substr($rowClass, 0, strrpos($rowClass, '\\', 1) + 1) . 'Table';
475
        }
476
477
        // check class initialization
478 3
        if (!class_exists($tableClass) || !is_subclass_of($tableClass, '\\Bluz\\Db\\Table')) {
479 1
            throw new TableNotFoundException("`Table` class is not exists or not initialized");
480
        }
481
482
        /**
483
         * @var Table $tableClass
484
         */
485 2
        $table = $tableClass::getInstance();
486
487 2
        $this->setTable($table);
488
489 2
        return $table;
490
    }
491
492
    /**
493
     * Set relation
494
     *
495
     * @param  Row $row
496
     * @return Row
497
     */
498
    public function setRelation(Row $row)
499
    {
500
        $modelName = $row->getTable()->getModel();
501
        $this->relations[$modelName] = [$row];
502
        return $this;
503
    }
504
505
    /**
506
     * Get relation by model name
507
     *
508
     * @param  string $modelName
509
     * @return Row|false
510
     * @throws RelationNotFoundException
511
     */
512 1
    public function getRelation($modelName)
513
    {
514 1
        $relations = $this->getRelations($modelName);
515
        if (!empty($relations)) {
516
            return current($relations);
517
        } else {
518
            return false;
519
        }
520
    }
521
522
    /**
523
     * Get relations by model name
524
     *
525
     * @param  string $modelName
526
     * @return array
527
     * @throws RelationNotFoundException
528
     */
529 1
    public function getRelations($modelName)
530
    {
531 1
        if (!isset($this->relations[$modelName])) {
532 1
            $this->relations[$modelName] = Relations::findRelation($this, $modelName);
533
        }
534
535
        return $this->relations[$modelName];
536
    }
537
}
538