Passed
Push — master ( 18dd10...55c9ae )
by SignpostMarv
03:01
created

TraitWriteableTree::ThrowIfNotTree()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 3.1852

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 0
dl 0
loc 10
ccs 2
cts 6
cp 0.3333
crap 3.1852
rs 9.4285
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
        if (
51 48
            ! is_null($leaf) &&
52
            (
53 36
                ($reference instanceof DaftNestedWriteableObject) ||
54 48
                ($referenceId === $this->GetNestedObjectTreeRootId())
0 ignored issues
show
Bug introduced by
It seems like GetNestedObjectTreeRootId() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

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