Passed
Push — master ( 55c9ae...f0f6ce )
by SignpostMarv
02:57
created

TraitWriteableTree::StoreThenRetrieveFreshLeaf()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 14
ccs 8
cts 8
cp 1
crap 2
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
        /**
51
        * @var DaftNestedWriteableObjectTree $this
52
        */
53 48
        $tree = $this;
0 ignored issues
show
Unused Code introduced by
The assignment to $tree is dead and can be removed.
Loading history...
54
55
        if (
56 48
            ! is_null($leaf) &&
57
            (
58 36
                ($reference instanceof DaftNestedWriteableObject) ||
59 48
                ($referenceId === $this->GetNestedObjectTreeRootId())
60
            )
61
        ) {
62 24
            if ($reference instanceof DaftNestedWriteableObject) {
63 18
                return $this->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
64
            }
65
66 24
            return $this->ModifyDaftNestedObjectTreeInsertLooseIntoTree($leaf, $before, $above);
0 ignored issues
show
Bug introduced by
The method ModifyDaftNestedObjectTreeInsertLooseIntoTree() does not exist on SignpostMarv\DaftObject\...stedWriteableObjectTree. Did you maybe mean ModifyDaftNestedObjectTreeInsertLoose()? ( Ignorable by Annotation )

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

66
            return $this->/** @scrutinizer ignore-call */ ModifyDaftNestedObjectTreeInsertLooseIntoTree($leaf, $before, $above);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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 12
        if ($rootObject instanceof DaftNestedWriteableObject) {
115
            if (
116 12
                $this->CountDaftNestedObjectTreeWithObject($rootObject, false, null) > 0 &&
117 12
                is_null($replacementRoot)
118
            ) {
119 2
                throw new BadMethodCallException('Cannot leave orphan objects in a tree');
120
            } elseif (
121 10
                ! is_null($replacementRoot) &&
122 10
                $replacementRoot !== $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

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