Passed
Push — master ( 51285e...d992c0 )
by SignpostMarv
05:00
created

RecallDaftObjectFromData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 2

Importance

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