Passed
Push — master ( 23d80c...f2e759 )
by SignpostMarv
04:38
created

DaftObjectExistsInDatabase()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 11
nc 2
nop 1
dl 0
loc 18
ccs 10
cts 10
cp 1
crap 2
rs 9.9
c 0
b 0
f 0
1
<?php
2
/**
3
* Base daft objects.
4
*
5
* @author SignpostMarv
6
*/
7
declare(strict_types=1);
8
9
namespace SignpostMarv\DaftObject;
10
11
use InvalidArgumentException;
12
use ParagonIE\EasyDB\EasyDB;
13
14
abstract class AbstractDaftObjectEasyDBRepository extends DaftObjectMemoryRepository
15
{
16
    const ARG_SECOND = 2;
17
18
    const BOOL_DOES_NOT_EXIST = false;
19
20
    const BOOL_TRUE_AS_INT = 1;
21
22
    const BOOL_FALSE_AS_INT = 0;
23
24
    /**
25
    * @var EasyDB
26
    */
27
    protected $db;
28
29
    /**
30
    * @param mixed ...$args
31
    */
32 14
    protected function __construct(string $type, EasyDB $db, ...$args)
33
    {
34 14
        parent::__construct($type, ...$args);
35 14
        $this->db = $db;
36 14
    }
37
38
    /**
39
    * {@inheritdoc}
40
    */
41 22
    public static function DaftObjectRepositoryByType(
42
        string $type,
43
        ...$args
44
    ) : DaftObjectRepository {
45
        /**
46
        * @var EasyDB|null
47
        */
48 22
        $db = array_shift($args) ?: null;
49
50 22
        $a = is_a($type, DaftObjectCreatedByArray::class, true);
51
        if (
52 22
            false === $a ||
53 22
            false === is_a($type, DefinesOwnIdPropertiesInterface::class, true)
54
        ) {
55 6
            throw new DaftObjectRepositoryTypeByClassMethodAndTypeException(
56 6
                1,
57 6
                static::class,
58 6
                __FUNCTION__,
59 6
                ($a ? DefinesOwnIdPropertiesInterface::class : DaftObjectCreatedByArray::class),
60 6
                $type
61
            );
62
        }
63
64 16
        $db = self::DaftObjectRepositoryArgsEasyDbActuallyRequired(
65 16
            $db,
66 16
            self::ARG_SECOND,
67 16
            __FUNCTION__
68
        );
69
70 14
        return new static($type, $db, ...$args);
71
    }
72
73
    /**
74
    * {@inheritdoc}
75
    */
76 14
    public static function DaftObjectRepositoryByDaftObject(
77
        DefinesOwnIdPropertiesInterface $object,
78
        ...$args
79
    ) : DaftObjectRepository {
80
        /**
81
        * @var EasyDB|null
82
        */
83 14
        $db = array_shift($args) ?: null;
84
85 14
        $db = self::DaftObjectRepositoryArgsEasyDbActuallyRequired(
86 14
            $db,
87 14
            self::ARG_SECOND,
88 14
            __FUNCTION__
89
        );
90
91 14
        return static::DaftObjectRepositoryByType(get_class($object), $db, ...$args);
92
    }
93
94
    /**
95
    * @param mixed $id
96
    */
97 6
    public function RemoveDaftObjectById($id) : void
98
    {
99 6
        $id = array_values(is_array($id) ? $id : [$id]);
100
101 6
        $idkv = self::DaftObjectIdPropertiesFromType($this->type, $id);
102
103 6
        $this->db->delete($this->DaftObjectDatabaseTable(), $this->ModifyTypesForDatabase($idkv));
104
105 6
        $this->ForgetDaftObjectById($id);
106 6
    }
107
108 6
    public function RememberDaftObjectData(
109
        DefinesOwnIdPropertiesInterface $object,
110
        bool $assumeDoesNotExist = false
111
    ) : void {
112 6
        $id = [];
113
114 6
        foreach ($object::DaftObjectIdProperties() as $prop) {
115
            /**
116
            * @var scalar|array|object|null
117
            */
118 6
            $propVal = $object->$prop;
119
120 6
            $id[$prop] = $propVal;
121
        }
122
123
        $this->db->tryFlatTransaction(function () use ($id, $object, $assumeDoesNotExist) : void {
124
            $exists =
125 6
                $assumeDoesNotExist
126 2
                    ? self::BOOL_DOES_NOT_EXIST
127 6
                    : $this->DaftObjectExistsInDatabase($id);
128 6
            $values = $this->RememberDaftObjectDataValues($object, $exists);
129 6
            $this->RememberDaftObjectDataUpdate($exists, $id, $values);
130 6
        });
131 6
    }
132
133
    /**
134
    * @param mixed $id
135
    *
136
    * @return array<string, mixed>
137
    */
138 8
    protected static function DaftObjectIdPropertiesFromType(string $type, $id) : array
139
    {
140 8
        if ( ! is_a($type, DefinesOwnIdPropertiesInterface::class, true)) {
141 2
            throw new InvalidArgumentException(
142
                'Argument 1 passed to ' .
143
                __METHOD__ .
144
                ' must be an implementation of ' .
145
                DefinesOwnIdPropertiesInterface::class .
146
                ', ' .
147 2
                $type .
148 2
                ' given!'
149
            );
150
        }
151
152
        /**
153
        * @var array<int, string>
154
        */
155 6
        $idProps = array_values($type::DaftObjectIdProperties());
156
157 6
        if (is_scalar($id) && 1 === count($idProps)) {
158 2
            $id = [$id];
159
        }
160
161
        /**
162
        * @var array<string, mixed>
163
        */
164 6
        $idkv = [];
165
166 6
        if (is_array($id)) {
167 6
            foreach ($idProps as $i => $prop) {
168
                /**
169
                * @var scalar|array|object|null
170
                */
171 6
                $propVal = $id[$i];
172
173 6
                $idkv[$prop] = $propVal;
174
            }
175
        }
176
177 6
        return $idkv;
178
    }
179
180 16
    protected static function DaftObjectRepositoryArgsEasyDbActuallyRequired(
181
        ? EasyDB $db,
182
        int $arg,
183
        string $function
184
    ) : EasyDB {
185 16
        if (false === ($db instanceof EasyDB)) {
186 2
            throw new DatabaseConnectionNotSpecifiedException(
187 2
                $arg,
188 2
                static::class,
189 2
                $function,
190 2
                EasyDB::class,
191 2
                'null'
192
            );
193
        }
194
195 14
        return $db;
196
    }
197
198
    /**
199
    * @return array<string, mixed>
200
    */
201 6
    protected function ModifyTypesForDatabase(array $values) : array
202
    {
203
        /**
204
        * @var array<string, mixed>
205
        */
206 6
        $out = array_map(
207
            /**
208
            * @param mixed $val
209
            *
210
            * @return mixed
211
            */
212
            function ($val) {
213
                return
214 6
                    is_bool($val)
215
                        ? (
216
                            $val
217
                                ? self::BOOL_TRUE_AS_INT
218
                                : self::BOOL_FALSE_AS_INT
219
                        )
220 6
                        : $val;
221 6
            },
222 6
            $values
223
        );
224
225 6
        return $out;
226
    }
227
228
    abstract protected function DaftObjectDatabaseTable() : string;
229
230
    /**
231
    * @return string[]
232
    */
233 6
    protected function RememberDaftObjectDataCols(DaftObject $object, bool $exists) : array
234
    {
235 6
        $cols = $object::DaftObjectExportableProperties();
236
237 6
        if ($exists) {
238 4
            $changed = $object->ChangedProperties();
239
            $cols = array_filter($cols, function (string $prop) use ($changed) : bool {
240 4
                return TypeParanoia::MaybeInArray($prop, $changed);
241 4
            });
242
        }
243
244 6
        return $cols;
245
    }
246
247
    /**
248
    * @return array<string, mixed>
249
    */
250 6
    protected function RememberDaftObjectDataValues(DaftObject $object, bool $exists) : array
251
    {
252
        /**
253
        * @var array<string, mixed>
254
        */
255 6
        $values = [];
256 6
        $cols = $this->RememberDaftObjectDataCols($object, $exists);
257
258 6
        foreach ($cols as $col) {
259
            /**
260
            * @var scalar|array|object|null
261
            */
262 6
            $colVal = $object->$col;
263
264 6
            $values[$col] = $colVal;
265
        }
266
267 6
        return $this->ModifyTypesForDatabase($values);
268
    }
269
270 6
    protected function RememberDaftObjectDataUpdate(bool $exists, array $id, array $values) : void
271
    {
272 6
        if (count($values) > 0) {
273 6
            if (false === $exists) {
274 6
                $this->db->insert($this->DaftObjectDatabaseTable(), $values);
275
            } else {
276 4
                $this->db->update($this->DaftObjectDatabaseTable(), $values, $id);
277
            }
278
        }
279 6
    }
280
281
    /**
282
    * @param mixed $id
283
    */
284 6
    protected function RecallDaftObjectFromData($id) : ? DaftObject
285
    {
286 6
        $idkv = self::DaftObjectIdPropertiesFromType($this->type, $id);
287
288 6
        return $this->RecallDaftObjectFromQuery($idkv);
289
    }
290
291
    /**
292
    * @param array<string, mixed> $idkv
293
    */
294 8
    protected function RecallDaftObjectFromQuery(array $idkv) : ? DefinesOwnIdPropertiesInterface
295
    {
296 8
        $type = $this->type;
297
298 8
        if ( ! is_a($type, DefinesOwnIdPropertiesInterface::class, true)) {
299 2
            throw new InvalidArgumentException(
300 2
                static::class . '::$type must be an implementation of ' .
301 2
                DefinesOwnIdPropertiesInterface::class .
302 2
                ', ' .
303 2
                $type .
304 2
                ' given!'
305
            );
306
        }
307
308 6
        if (true === $this->DaftObjectExistsInDatabase($idkv)) {
309
            /**
310
            * @var DefinesOwnIdPropertiesInterface
311
            */
312 6
            $out = new $type($this->RecallDaftObjectDataFromQuery($idkv));
313
314 6
            return $out;
315
        }
316
317 4
        return null;
318
    }
319
320
    /**
321
    * @param array<string, mixed> $idkv
322
    */
323 6
    protected function RecallDaftObjectDataFromQuery(array $idkv) : array
324
    {
325
        /**
326
        * @var array[]
327
        */
328 6
        $data = $this->db->safeQuery(
329
            (
330
                'SELECT * FROM ' .
331 6
                $this->db->escapeIdentifier($this->DaftObjectDatabaseTable()) .
332 6
                ' WHERE ' .
333 6
                implode(' AND ', array_map(
334
                    function (string $col) : string {
335 6
                        return $this->db->escapeIdentifier($col) . ' = ?';
336 6
                    },
337 6
                    array_keys($idkv)
338
                )) .
339 6
                ' LIMIT 1'
340
            ),
341 6
            array_values($idkv)
342
        );
343
344 6
        return $data[0];
345
    }
346
347
    /**
348
    * @param array<string, mixed> $id
349
    */
350 6
    protected function DaftObjectExistsInDatabase(array $id) : bool
351
    {
352 6
        $where = [];
353
354 6
        foreach (array_keys($id) as $col) {
355 6
            $where[] = $this->db->escapeIdentifier($col) . ' = ?';
356
        }
357
358
        return
359 6
            $this->db->single(
360
                (
361
                    'SELECT COUNT(*) FROM ' .
362 6
                    $this->db->escapeIdentifier($this->DaftObjectDatabaseTable()) .
363 6
                    ' WHERE ' .
364 6
                    implode(' AND ', $where)
365
                ),
366 6
                array_values($id)
367 6
            ) >= 1;
368
    }
369
}
370