Passed
Push — master ( 3b67a8...ff814d )
by SignpostMarv
04:59
created

RememberDaftObjectData()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

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