Test Failed
Push — master ( 44fbaf...51285e )
by SignpostMarv
09:44
created

AbstractDaftObjectEasyDBRepository::RecallDaftObjectDataFromQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

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