Passed
Push — master ( cef0fb...114e37 )
by SignpostMarv
02:39
created

TraitWriteableTree::MaybeGetLeaf()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

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