Passed
Push — master ( 1f4790...dfe042 )
by SignpostMarv
02:45
created

UpdateRemoveThenRebuild()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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