Passed
Push — master ( 9bd7b5...6ff084 )
by SignpostMarv
03:03
created

StoreThenRetrieveFreshCopy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2.0078

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
ccs 7
cts 8
cp 0.875
crap 2.0078
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 BadMethodCallException;
12
use InvalidArgumentException;
13
use RuntimeException;
14
15
abstract class DaftWriteableObjectMemoryTree extends DaftObjectMemoryTree implements DaftNestedWriteableObjectTree
16
{
17 10
    public function ModifyDaftNestedObjectTreeInsert(
18
        DaftNestedWriteableObject $newLeaf,
19
        DaftNestedWriteableObject $referenceLeaf,
20
        bool $before = false,
21
        bool $above = null
22
    ) : DaftNestedWriteableObject {
23 10
        if ($newLeaf->GetId() === $referenceLeaf->GetId()) {
24
            throw new InvalidArgumentException('Cannot modify leaf relative to itself!');
25
        }
26
27 10
        if (true === $above) {
28 6
            $this->ModifyDaftNestedObjectTreeInsertAbove($newLeaf, $referenceLeaf);
29 10
        } elseif (false === $above) {
30 2
            $this->ModifyDaftNestedObjectTreeInsertBelow($newLeaf, $referenceLeaf);
31
        } else {
32 10
            $this->ModifyDaftNestedObjectTreeInsertAdjacent($newLeaf, $referenceLeaf, $before);
33
        }
34
35 10
        $this->RebuildTreeInefficiently();
36
37 10
        $newLeaf = $this->RecallDaftObject($newLeaf->GetId());
38
39 10
        if ( ! ($newLeaf instanceof DaftNestedWriteableObject)) {
40
            throw new RuntimeException('Could not retrieve leaf from tree after rebuilding!');
41
        }
42
43 10
        return $newLeaf;
44
    }
45
46 10
    public function ModifyDaftNestedObjectTreeInsertLoose(
47
        $newLeaf,
48
        $referenceLeafId,
49
        bool $before = false,
50
        bool $above = null
51
    ) : DaftNestedWriteableObject {
52 10
        if ($newLeaf === $this->GetNestedObjectTreeRootId()) {
53
            throw new InvalidArgumentException('Cannot pass root id as new leaf');
54
        }
55
56
        $newLeaf = (
57 10
            ($newLeaf instanceof DaftNestedWriteableObject)
58 10
                ? $newLeaf
59 10
                : $this->RecallDaftObject($newLeaf)
60
        );
61 10
        $referenceLeaf = $this->RecallDaftObject($referenceLeafId);
62
63 10
        if ( ! ($newLeaf instanceof DaftNestedWriteableObject)) {
64
            throw new InvalidArgumentException(
65
                'Arguemnt 1 passed to ' .
66
                __METHOD__ .
67
                ' did not resolve to a leaf node!'
68
            );
69
        }
70
71 10
        $newLeaf = $this->StoreThenRetrieveFreshCopy($newLeaf);
72
73
        if (
74 10
            ($newLeaf instanceof DaftNestedWriteableObject) &&
75 10
            ($referenceLeaf instanceof DaftNestedWriteableObject)
76
        ) {
77 4
            return $this->ModifyDaftNestedObjectTreeInsert(
78 4
                $newLeaf,
79 4
                $referenceLeaf,
80 4
                $before,
81 4
                $above
82
            );
83 10
        } elseif ($referenceLeafId !== $this->GetNestedObjectTreeRootId()) {
84
            throw new InvalidArgumentException(
85
                'Arguemnt 2 passed to ' .
86
                __METHOD__ .
87
                ' did not resolve to a leaf node!'
88
            );
89
        }
90
91 10
        $tree = array_filter(
92 10
            $this->RecallDaftNestedObjectFullTree(0),
93
            function (DaftNestedWriteableObject $leaf) use ($newLeaf) : bool {
94 10
                return $leaf->GetId() !== $newLeaf->GetId();
95 10
            }
96
        );
97
98 10
        if (0 === count($tree)) {
99 10
            $newLeaf->SetIntNestedLeft(0);
100 10
            $newLeaf->SetIntNestedRight(1);
101 10
            $newLeaf->SetIntNestedLevel(0);
102 10
            $newLeaf->AlterDaftNestedObjectParentId($this->GetNestedObjectTreeRootId());
103
104 10
            return $this->StoreThenRetrieveFreshCopy($newLeaf);
105
        }
106
107
        /**
108
        * @var DaftNestedWriteableObject $referenceLeaf
109
        */
110 10
        $referenceLeaf = $before ? current($tree) : end($tree);
111
112 10
        return $this->ModifyDaftNestedObjectTreeInsert(
113 10
            $newLeaf,
114 10
            $referenceLeaf,
115 10
            $before,
116 10
            $above
117
        );
118
    }
119
120 2
    public function ModifyDaftNestedObjectTreeRemoveWithObject(
121
        DaftNestedWriteableObject $root,
122
        ? DaftNestedWriteableObject $replacementRoot
123
    ) : int {
124
        if (
125 2
            $this->CountDaftNestedObjectTreeWithObject($root, false, null) > 0 &&
126 2
            is_null($replacementRoot)
127
        ) {
128
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
129
        }
130
131 2
        $root = $this->StoreThenRetrieveFreshCopy($root);
132
133 2
        if ( ! is_null($replacementRoot)) {
134
            $replacementRoot = $this->StoreThenRetrieveFreshCopy($replacementRoot);
135
136
            /**
137
            * @var DaftNestedWriteableObject $alter
138
            */
139
            foreach ($this->RecallDaftNestedObjectTreeWithObject($root, false, 0) as $alter) {
140
                $alter->AlterDaftNestedObjectParentId($root->ObtainDaftNestedObjectParentId());
141
                $alter = $this->StoreThenRetrieveFreshCopy($alter);
142
                if ($alter->GetId() === $replacementRoot->GetId()) {
143
                    $replacementRoot = $alter;
144
                }
145
            }
146
        }
147
148 2
        $this->RemoveDaftObject($root);
149
150 2
        $this->RebuildTreeInefficiently();
151
152 2
        return $this->CountDaftNestedObjectFullTree();
153
    }
154
155
    /**
156
    * {@inheritdoc}
157
    */
158 2
    public function ModifyDaftNestedObjectTreeRemoveWithId($root, $replacementRoot) : int
159
    {
160 2
        $rootObject = $this->RecallDaftObject($root);
161
162 2
        if ( ! ($rootObject instanceof DaftNestedWriteableObject)) {
163 2
            return $this->CountDaftNestedObjectFullTree();
164
        }
165
166
        if (
167 2
            ! is_null($replacementRoot) &&
168 2
            $replacementRoot !== $this->GetNestedObjectTreeRootId()
169
        ) {
170
            $replacementRootObject = $this->RecallDaftObject($replacementRoot);
171
172
            if ( ! ($replacementRootObject instanceof DaftNestedWriteableObject)) {
173
                throw new InvalidArgumentException(
174
                    'Could not locate replacement root, cannot leave orphan objects!'
175
                );
176
            }
177
178
            return $this->ModifyDaftNestedObjectTreeRemoveWithObject(
179
                $rootObject,
180
                $replacementRootObject
181
            );
182
        }
183
184
        if (
185 2
            $this->CountDaftNestedObjectTreeWithObject($rootObject, false, null) > 0 &&
186 2
            is_null($replacementRoot)
187
        ) {
188
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
189
        }
190
191
        /**
192
        * @var DaftNestedWriteableObject $alter
193
        */
194 2
        foreach ($this->RecallDaftNestedObjectTreeWithObject($rootObject, false, null) as $alter) {
195
            $alter = $this->StoreThenRetrieveFreshCopy($alter);
196
            $alter->AlterDaftNestedObjectParentId($replacementRoot);
197
            $this->RememberDaftObject($alter);
198
        }
199
200 2
        $this->RemoveDaftObject($rootObject);
201
202 2
        $this->RebuildTreeInefficiently();
203
204 2
        return $this->CountDaftNestedObjectFullTree();
205
    }
206
207 6
    protected function ModifyDaftNestedObjectTreeInsertAbove(
208
        DaftNestedWriteableObject $newLeaf,
209
        DaftNestedWriteableObject $referenceLeaf
210
    ) : void {
211 6
        $newLeaf->AlterDaftNestedObjectParentId(
212 6
                $referenceLeaf->ObtainDaftNestedObjectParentId()
213
            );
214 6
        $referenceLeaf->AlterDaftNestedObjectParentId($newLeaf->GetId());
215
216 6
        $newLeaf = $this->StoreThenRetrieveFreshCopy($newLeaf);
0 ignored issues
show
Unused Code introduced by
The assignment to $newLeaf is dead and can be removed.
Loading history...
217 6
        $this->StoreThenRetrieveFreshCopy($referenceLeaf);
218 6
    }
219
220 2
    protected function ModifyDaftNestedObjectTreeInsertBelow(
221
        DaftNestedWriteableObject $newLeaf,
222
        DaftNestedWriteableObject $referenceLeaf
223
    ) : void {
224 2
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->GetId());
225 2
        $newLeaf = $this->StoreThenRetrieveFreshCopy($newLeaf);
0 ignored issues
show
Unused Code introduced by
The assignment to $newLeaf is dead and can be removed.
Loading history...
226 2
    }
227
228 10
    protected function ModifyDaftNestedObjectTreeInsertAdjacent(
229
        DaftNestedWriteableObject $newLeaf,
230
        DaftNestedWriteableObject $referenceLeaf,
231
        bool $before
232
    ) : void {
233
        /**
234
        * @var array<int, DaftNestedWriteableObject> $siblings
235
        */
236 10
        $siblings = array_values(array_filter(
237 10
                $this->RecallDaftNestedObjectTreeWithId(
238 10
                    $referenceLeaf->ObtainDaftNestedObjectParentId(),
239 10
                    false,
240
                    0
241
                ),
242
                function (DaftNestedWriteableObject $leaf) use ($newLeaf) : bool {
243 10
                    return $leaf->GetId() !== $newLeaf->GetId();
244 10
                }
245
            ));
246 10
        $siblingIds = [];
247 10
        $siblingSort = [];
248 10
        $j = count($siblings);
249
250
        /**
251
        * @var DaftNestedWriteableObject $leaf
252
        */
253 10
        foreach ($siblings as $leaf) {
254
            /**
255
            * @var scalar|scalar[] $siblingId
256
            */
257 10
            $siblingId = $leaf->GetId();
258 10
            $siblingIds[] = $siblingId;
259 10
            $siblingSort[] = $leaf->GetIntNestedSortOrder();
260
        }
261
262 10
        $pos = array_search($referenceLeaf->GetId(), $siblingIds, true);
263
264 10
        if (false === $pos) {
265
            throw new RuntimeException('Reference leaf not found in siblings tree!');
266
        }
267 10
        for ($i = 0; $i < $j; ++$i) {
268 10
            $siblings[$i]->SetIntNestedSortOrder(
269 10
                        $siblingSort[$i] +
270 10
                        (($before ? ($i < $pos) : ($i <= $pos)) ? -1 : 1)
271
                    );
272 10
            $this->StoreThenRetrieveFreshCopy($siblings[$i]);
273
        }
274 10
        $newLeaf->SetIntNestedSortOrder($siblingSort[$pos]);
275 10
        $newLeaf->AlterDaftNestedObjectParentId(
276 10
                $referenceLeaf->ObtainDaftNestedObjectParentId()
277
            );
278
279 10
        $newLeaf = $this->StoreThenRetrieveFreshCopy($newLeaf);
0 ignored issues
show
Unused Code introduced by
The assignment to $newLeaf is dead and can be removed.
Loading history...
280 10
    }
281
282 12
    protected function RememberDaftObjectData(DefinesOwnIdPropertiesInterface $object) : void
283
    {
284 12
        static::ThrowIfNotType($object, DaftNestedWriteableObject::class, 1, __METHOD__);
285
286 12
        parent::RememberDaftObjectData($object);
287 12
    }
288
289
    /**
290
    * @param DaftObject|string $object
291
    */
292 14
    protected static function ThrowIfNotType(
293
        $object,
294
        string $type,
295
        int $argument,
296
        string $function
297
    ) : void {
298 14
        if ( ! is_a($object, DaftNestedWriteableObject::class, is_string($object))) {
299 2
            throw new DaftObjectRepositoryTypeByClassMethodAndTypeException(
300 2
                $argument,
301 2
                static::class,
302 2
                $function,
303 2
                DaftNestedWriteableObject::class,
304 2
                is_string($object) ? $object : get_class($object)
305
            );
306
        }
307
308 12
        parent::ThrowIfNotType($object, $type, $argument, $function);
309 12
    }
310
311 10
    protected function RebuildTreeInefficiently() : void
312
    {
313
        $parentIdXref = [
314 10
            (array) $this->GetNestedObjectTreeRootId(),
315
        ];
316
317
        /**
318
        * @var array<int, array<int, DaftNestedWriteableObject>> $xRefChildren
319
        */
320
        $xRefChildren = [
321 10
            [],
322
        ];
323
324
        /**
325
        * @var array<int, scalar|scalar[]> $idXref
326
        */
327 10
        $idXref = [];
328
329 10
        $tree = $this->RecallDaftNestedObjectFullTree();
330
331
        usort($tree, function (DaftNestedWriteableObject $a, DaftNestedWriteableObject $b) : int {
332 10
            return $this->CompareObjects($a, $b);
333 10
        });
334
335
        /**
336
        * @var DaftNestedWriteableObject $leaf
337
        */
338 10
        foreach ($tree as $i => $leaf) {
339 10
            $leafParentId = $leaf->ObtainDaftNestedObjectParentId();
340 10
            $pos = array_search($leafParentId, $parentIdXref, true);
341
342 10
            if (false === $pos) {
343 8
                $parentIdXref[] = $leafParentId;
344
345
                /**
346
                * @var int $pos
347
                */
348 8
                $pos = array_search($leafParentId, $parentIdXref, true);
349
350 8
                $xRefChildren[$pos] = [];
351
            }
352
353 10
            if ( ! in_array($leaf, $xRefChildren[$pos], true)) {
354 10
                $xRefChildren[$pos][] = $leaf;
355
            }
356
357 10
            if ( ! in_array($leaf->GetId(), $idXref, true)) {
358
                /**
359
                * @var scalar|scalar[] $leafId
360
                */
361 10
                $leafId = $leaf->GetId();
362 10
                $idXref[] = $leafId;
363
            }
364
365 10
            $leaf->SetIntNestedLeft(0);
366 10
            $leaf->SetIntNestedRight(0);
367 10
            $leaf->SetIntNestedLevel(0);
368
369 10
            $tree[$i] = $this->StoreThenRetrieveFreshCopy($leaf);
370
        }
371
372
        $rebuild = function (
373
            DaftNestedWriteableObject $leaf,
374
            int $level,
375
            int $n,
376
            array $parentIds,
377
            array $ids,
378
            array $children
379
        ) use (
380 10
            &$rebuild
381
        ) : int {
382
            /**
383
            * @var scalar|scalar[] $id
384
            */
385 10
            $id = $leaf->GetId();
386
387 10
            $leaf->SetIntNestedLevel($level);
388 10
            $leaf->SetIntNestedLeft($n);
389
390 10
            ++$n;
391
392
            /**
393
            * @var int|false $parentPos
394
            */
395 10
            $parentPos = array_search((array) $id, $parentIds, true);
396
397 10
            if (false !== $parentPos) {
398
                /**
399
                * @var DaftNestedWriteableObject $childLeaf
400
                */
401 8
                foreach ($children[$parentPos] as $childLeaf) {
402 8
                    $n = (int) $rebuild(
403 8
                        $childLeaf,
404 8
                        $level + 1,
405 8
                        $n,
406 8
                        $parentIds,
407 8
                        $ids,
408 8
                        $children
409
                    );
410
                }
411
            }
412
413 10
            $leaf->SetIntNestedRight($n);
414
415 10
            $this->StoreThenRetrieveFreshCopy($leaf);
416
417 10
            return $n + 1;
418 10
        };
419
420 10
        $n = 0;
421
422
        /**
423
        * @var DaftNestedWriteableObject $rootLeaf
424
        */
425 10
        foreach ($xRefChildren[0] as $rootLeaf) {
426 10
            $n = $rebuild(
427 10
                $rootLeaf,
428 10
                0,
429 10
                $n,
430 10
                $parentIdXref,
431 10
                $idXref,
432 10
                $xRefChildren
433
            );
434
        }
435 10
    }
436
437 10
    protected function StoreThenRetrieveFreshCopy(
438
        DaftNestedWriteableObject $leaf
439
    ) : DaftNestedWriteableObject {
440 10
        $this->RememberDaftObject($leaf);
441 10
        $this->ForgetDaftObject($leaf);
442 10
        $this->ForgetDaftObjectById($leaf->GetId());
443
444 10
        $fresh = $this->RecallDaftObject($leaf->GetId());
445
446 10
        if ( ! ($fresh instanceof DaftNestedWriteableObject)) {
447
            throw new RuntimeException('Was not able to obtain a fresh copy of the object!');
448
        }
449
450 10
        return $fresh;
451
    }
452
}
453