Passed
Push — develop ( 71b701...51acb7 )
by Anton
12:20 queued 10:16
created

Row::afterDelete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 0
dl 0
loc 2
ccs 1
cts 1
cp 1
crap 1
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\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
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

128
    public function validate(/** @scrutinizer ignore-unused */ $data): bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

140
    public function assert(/** @scrutinizer ignore-unused */ $data): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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
     */
157 2
    public function save()
158
    {
159 2
        $this->beforeSave();
160
        /**
161
         * If the primary key is empty, this is an INSERT of a new row.
162
         * Otherwise check primary key updated or not, if it changed - INSERT
163
         * otherwise UPDATE
164
         */
165 2
        if (!\count(\array_filter($this->getPrimaryKey()))) {
166 1
            $result = $this->doInsert();
167 1
        } elseif (\count(\array_diff_assoc($this->getPrimaryKey(), $this->clean))) {
168
            $result = $this->doInsert();
169
        } else {
170 1
            $result = $this->doUpdate();
171
        }
172 2
        $this->afterSave();
173 2
        return $result;
174
    }
175
176
    /**
177
     * Insert row to Db
178
     *
179
     * @return mixed The primary key value(s), as an associative array if the
180
     *               key is compound, or a scalar if the key is single-column
181
     * @throws InvalidPrimaryKeyException
182
     * @throws TableNotFoundException
183
     */
184 1
    protected function doInsert()
185
    {
186
        /**
187
         * Run pre-INSERT logic
188
         */
189 1
        $this->beforeInsert();
190
191 1
        $data = $this->toArray();
192
        /**
193
         * Execute validator logic
194
         * Can throw ValidatorException
195
         */
196 1
        $this->assert($data);
197
198 1
        $table = $this->getTable();
199
200
        /**
201
         * Execute the INSERT (this may throw an exception)
202
         */
203 1
        $primaryKey = $table::insert($data);
204
205
        /**
206
         * Normalize the result to an array indexed by primary key column(s)
207
         */
208 1
        $tempPrimaryKey = $table->getPrimaryKey();
209 1
        $newPrimaryKey = [current($tempPrimaryKey) => $primaryKey];
210
211
        /**
212
         * Save the new primary key value in object. The primary key may have
213
         * been generated by a sequence or auto-increment mechanism, and this
214
         * merge should be done before the afterInsert() method is run, so the
215
         * new values are available for logging, etc.
216
         */
217 1
        $this->setFromArray($newPrimaryKey);
218
219
        /**
220
         * Run post-INSERT logic
221
         */
222 1
        $this->afterInsert();
223
224
        /**
225
         * Update the "clean" to reflect that the data has been inserted.
226
         */
227 1
        $this->clean = $this->toArray();
228
229 1
        return $newPrimaryKey;
230
    }
231
232
    /**
233
     * Update row
234
     *
235
     * @return integer The number of rows updated
236
     * @throws InvalidPrimaryKeyException
237
     * @throws TableNotFoundException
238
     */
239 1
    protected function doUpdate(): int
240
    {
241
        /**
242
         * Run pre-UPDATE logic
243
         */
244 1
        $this->beforeUpdate();
245
246 1
        $data = $this->toArray();
247
248
        /**
249
         * Execute validator logic
250
         * Can throw ValidatorException
251
         */
252 1
        $this->assert($data);
253
254 1
        $primaryKey = $this->getPrimaryKey();
255
256
        /**
257
         * Compare the data to the modified fields array to discover
258
         * which columns have been changed.
259
         */
260 1
        $diffData = array_diff_assoc($data, $this->clean);
261
262 1
        $table = $this->getTable();
263
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
        $result = 0;
273 1
        if (\count($diffData) > 0) {
274 1
            $result = $table::update($diffData, $primaryKey);
275
        }
276
277
        /**
278
         * Run post-UPDATE logic.  Do this before the _refresh()
279
         * so the _afterUpdate() function can tell the difference
280
         * between changed data and clean (pre-changed) data.
281
         */
282 1
        $this->afterUpdate();
283
284
        /**
285
         * Refresh the data just in case triggers in the RDBMS changed
286
         * any columns.  Also this resets the "clean".
287
         */
288 1
        $this->clean = $this->toArray();
289
290 1
        return $result;
291
    }
292
293
    /**
294
     * Delete existing row
295
     *
296
     * @return bool Removed or not
297
     * @throws InvalidPrimaryKeyException
298
     * @throws TableNotFoundException
299
     */
300 1
    public function delete(): bool
301
    {
302
        /**
303
         * Execute pre-DELETE logic
304
         */
305 1
        $this->beforeDelete();
306
307 1
        $primaryKey = $this->getPrimaryKey();
308
309
        /**
310
         * Execute the DELETE (this may throw an exception)
311
         */
312 1
        $table = $this->getTable();
313 1
        $result = $table::delete($primaryKey);
314
315
        /**
316
         * Execute post-DELETE logic
317
         */
318 1
        $this->afterDelete();
319
320
        /**
321
         * Reset all fields to null to indicate that the row is not there
322
         */
323 1
        $this->resetArray();
324
325 1
        return $result > 0;
326
    }
327
328
    /**
329
     * Retrieves an associative array of primary keys, if it exists
330
     *
331
     * @return array
332
     * @throws InvalidPrimaryKeyException
333
     * @throws TableNotFoundException
334
     */
335 3
    protected function getPrimaryKey(): array
336
    {
337 3
        $primary = array_flip($this->getTable()->getPrimaryKey());
338
339 3
        return array_intersect_key($this->toArray(), $primary);
340
    }
341
342
    /**
343
     * Refreshes properties from the database
344
     *
345
     * @return void
346
     */
347
    public function refresh(): void
348
    {
349
        $this->setFromArray($this->clean);
350
        $this->afterRead();
351
    }
352
353
    /**
354
     * After read data from Db
355
     *
356
     * @return void
357
     */
358 17
    protected function afterRead(): void
359
    {
360 17
    }
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(): void
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(): void
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(): void
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(): void
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(): void
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(): void
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(): void
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(): void
439
    {
440 1
    }
441
}
442