Test Failed
Push — master ( 852abc...2f35aa )
by SignpostMarv
02:24
created

fore()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 5
ccs 0
cts 2
cp 0
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
    public function ModifyDaftNestedObjectTreeInsert(
18
        DaftNestedWriteableObject $newLeaf,
19
        DaftNestedWriteableObject $referenceLeaf,
20
        bool $before = false,
21
        bool $above = null
22
    ) : DaftNestedWriteableObject {
23
        if($newLeaf->GetId() === $referenceLeaf->GetId()) {
24
            throw new InvalidArgumentException('Cannot modify leaf relative to itself!');
25
        }
26
27
        if (true === $above) {
28
            $newLeaf->AlterDaftNestedObjectParentId(
29
                $referenceLeaf->ObtainDaftNestedObjectParentId()
30
            );
31
            $referenceLeaf->AlterDaftNestedObjectParentId($newLeaf->GetId());
32
        } elseif (false === $above) {
33
            $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->GetId());
34
        } else {
35 2
            $siblings = array_values(array_filter(
36
                $this->RecallDaftNestedObjectTreeWithId(
37
                    $referenceLeaf->ObtainDaftNestedObjectParentId(),
38
                    false,
39 2
                    0
40
                ),
41
                function (DaftNestedWriteableObject $leaf) use ($newLeaf) : bool {
42
                    return $leaf->GetId() !== $newLeaf->GetId();
43
                }
44
            ));
45
            $siblingIds = [];
46 10
            $siblingSort = [];
47
            $j = count($siblings);
48
49
            foreach ($siblings as $leaf) {
50 10
                $siblingIds[] = $leaf->GetId();
51
                $siblingSort[] = $leaf->GetIntNestedSortOrder();
52
            }
53 4
54
            $pos = array_search($referenceLeaf->GetId(), $siblingIds, true);
55
56
            if (false === $pos) {
57 4
                throw new RuntimeException('Reference leaf not found in siblings tree!');
58
            } else {
59 4
                for ($i = 0; $i < $j; $i += 1) {
60
                    $siblings[$i]->SetIntNestedSortOrder(
61 4
                        $siblingSort[$i] +
62
                        (($before ? ($i < $pos) : ($i <= $pos)) ? -1 : 1)
63
                    );
64
                    $this->StoreThenRetrieveFreshCopy($siblings[$i]);
65 4
                }
66
                $newLeaf->SetIntNestedSortOrder($siblingSort[$pos]);
67
68
                $this->StoreThenRetrieveFreshCopy($newLeaf);
69
            }
70
        }
71
72 6
        $this->RebuildTreeInefficiently();
73
74
        $newLeaf = $this->RecallDaftObject($newLeaf->GetId());
75
76
        if ( ! ($newLeaf instanceof DaftNestedWriteableObject)) {
77 6
            throw new RuntimeException('Could not retrieve leaf from tree after rebuilding!');
78 6
        }
79
80
        return $newLeaf;
81
    }
82
83
    public function ModifyDaftNestedObjectTreeInsertLoose(
84
        $newLeaf,
85
        $referenceLeafId,
86 6
        bool $before = false,
87
        bool $above = null
88
    ) : DaftNestedWriteableObject {
89
        if ($newLeaf === $this->GetNestedObjectTreeRootId()) {
90
            throw new InvalidArgumentException('Cannot pass root id as new leaf');
91 6
        }
92
        $newLeafId = $newLeaf;
0 ignored issues
show
Unused Code introduced by
The assignment to $newLeafId is dead and can be removed.
Loading history...
93
        $newLeaf = $this->StoreThenRetrieveFreshCopy(
94
            ($newLeaf instanceof DaftNestedWriteableObject)
0 ignored issues
show
Bug introduced by
It seems like $newLeaf instanceof Sign...allDaftObject($newLeaf) can also be of type null; however, parameter $leaf of SignpostMarv\DaftObject\...ThenRetrieveFreshCopy() does only seem to accept SignpostMarv\DaftObject\DaftNestedWriteableObject, maybe add an additional type check? ( Ignorable by Annotation )

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

94
            /** @scrutinizer ignore-type */ ($newLeaf instanceof DaftNestedWriteableObject)
Loading history...
95
                ? $newLeaf
96
                : $this->RecallDaftObject($newLeaf)
97 6
        );
98 4
        $referenceLeaf = $this->RecallDaftObject($referenceLeafId);
99 6
100
        if ( ! ($newLeaf instanceof DaftNestedWriteableObject)) {
0 ignored issues
show
introduced by
$newLeaf is always a sub-type of SignpostMarv\DaftObject\DaftNestedWriteableObject.
Loading history...
101 6
            throw new InvalidArgumentException(
102 6
                'Arguemnt 1 passed to ' .
103
                __METHOD__ .
104
                ' did not resolve to a leaf node!'
105
            );
106
        } elseif (
107
            ($newLeaf instanceof DaftNestedWriteableObject) &&
108 6
            ($referenceLeaf instanceof DaftNestedWriteableObject)
109 2
        ) {
110 6
            return $this->ModifyDaftNestedObjectTreeInsert(
111
                $newLeaf,
112 6
                $referenceLeaf,
113 2
                $before,
114
                $above
115
            );
116
        } elseif ($referenceLeafId !== $this->GetNestedObjectTreeRootId()) {
117 6
            throw new InvalidArgumentException(
118 6
                'Arguemnt 2 passed to ' .
119
                __METHOD__ .
120
                ' did not resolve to a leaf node!'
121
            );
122
        }
123
124
        $tree = array_filter(
125
            $this->RecallDaftNestedObjectFullTree(0),
126
            function (DaftNestedWriteableObject $leaf) use ($newLeaf) : bool {
127 6
                return $leaf->GetId() !== $newLeaf->GetId();
128 4
            }
129
        );
130
131 4
        if (0 === count($tree)) {
132
            $newLeaf->SetIntNestedLeft(0);
133
            $newLeaf->SetIntNestedRight(1);
134 4
            $newLeaf->SetIntNestedLevel(0);
135 4
            $newLeaf->AlterDaftNestedObjectParentId($this->GetNestedObjectTreeRootId());
136 4
137
            return $this->StoreThenRetrieveFreshCopy($newLeaf);
138 2
        }
139
140
        return $this->ModifyDaftNestedObjectTreeInsert(
141 4
            $newLeaf,
142
            $before ? current($tree) : end($tree),
143
            $before,
144
            null
145 4
        );
146
    }
147 4
148 2
    public function ModifyDaftNestedObjectTreeRemoveWithObject(
149 2
        DaftNestedWriteableObject $root,
150
        ? DaftNestedWriteableObject $replacementRoot
151 2
    ) : int {
152 2
        if (
153
            $this->CountDaftNestedObjectTreeWithObject($root, false, null) > 0 &&
154
            is_null($replacementRoot)
155 2
        ) {
156 2
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
157 2
        }
158 2
159 2
        $root = $this->StoreThenRetrieveFreshCopy($root);
160
161 2
        $right = $root->GetIntNestedRight();
162
        $width = ($right - $root->GetIntNestedLeft()) + 1;
163
164
        $this->ModifyDaftNestedObjectTreeForRemoval($right, $width);
165
166
        if ( ! is_null($replacementRoot)) {
167 2
            $replacementRoot = $this->StoreThenRetrieveFreshCopy($replacementRoot);
168
169 2
            /**
170
            * @var DaftNestedWriteableObject $alter
171 2
            */
172 2
            foreach ($this->RecallDaftNestedObjectTreeWithObject($root, false, 1) as $alter) {
173 2
                $alter = $this->StoreThenRetrieveFreshCopy($alter);
174 2
                $this->ModifyDaftNestedObjectTreeInsertBelow($alter, $replacementRoot);
0 ignored issues
show
Bug introduced by
The method ModifyDaftNestedObjectTreeInsertBelow() does not exist on SignpostMarv\DaftObject\...iteableObjectMemoryTree. Did you maybe mean ModifyDaftNestedObjectTreeInsert()? ( Ignorable by Annotation )

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

174
                $this->/** @scrutinizer ignore-call */ 
175
                       ModifyDaftNestedObjectTreeInsertBelow($alter, $replacementRoot);

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...
175
            }
176 2
        }
177
178
        $this->RemoveDaftObject($root);
179 2
180
        return $this->CountDaftNestedObjectFullTree();
181
    }
182
183
    /**
184
    * {@inheritdoc}
185
    */
186
    public function ModifyDaftNestedObjectTreeRemoveWithId($root, $replacementRoot) : int
187 2
    {
188
        $rootObject = $this->RecallDaftObject($root);
189 2
190
        if ( ! ($rootObject instanceof DaftNestedWriteableObject)) {
191 2
            return $this->CountDaftNestedObjectFullTree();
192 2
        }
193
194
        if (
195 2
            ! is_null($replacementRoot) &&
196
            $replacementRoot !== $this->GetNestedObjectTreeRootId()
197 2
        ) {
198
            $replacementRootObject = $this->RecallDaftObject($replacementRoot);
199 2
200
            if ( ! ($replacementRootObject instanceof DaftNestedWriteableObject)) {
201
                throw new InvalidArgumentException(
202
                    'Could not locate replacement root, cannot leave orphan objects!'
203 2
                );
204
            }
205
206 4
            return $this->ModifyDaftNestedObjectTreeRemoveWithObject(
207
                $rootObject,
208
                $replacementRootObject
209
            );
210 4
        }
211
212
        if (
213
            $this->CountDaftNestedObjectTreeWithObject($rootObject, false, null) > 0 &&
214
            is_null($replacementRoot)
215
        ) {
216
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
217 6
        }
218
219
        /**
220
        * @var DaftNestedWriteableObject $alter
221 6
        */
222 2
        foreach ($this->RecallDaftNestedObjectTreeWithObject($rootObject, false, null) as $alter) {
223
            $alter = $this->StoreThenRetrieveFreshCopy($alter);
224
            $alter->AlterDaftNestedObjectParentId($replacementRoot);
225
            $this->RememberDaftObject($alter);
226
        }
227
228 4
        $right = $rootObject->GetIntNestedRight();
229
        $width = ($right - $rootObject->GetIntNestedLeft()) + 1;
230
231
        $this->ModifyDaftNestedObjectTreeForRemoval($right, $width);
232
233 4
        $this->RemoveDaftObject($rootObject);
234
235 4
        return $this->CountDaftNestedObjectFullTree();
236
    }
237
238
    protected function RememberDaftObjectData(DefinesOwnIdPropertiesInterface $object) : void
239
    {
240
        static::ThrowIfNotType($object, DaftNestedWriteableObject::class, 1, __METHOD__);
241 4
242
        parent::RememberDaftObjectData($object);
243
    }
244
245
    /**
246
    * @param DaftObject|string $object
247
    */
248
    protected static function ThrowIfNotType(
249 4
        $object,
250
        string $type,
251
        int $argument,
252 2
        string $function
253
    ) : void {
254
        if ( ! is_a($object, DaftNestedWriteableObject::class, is_string($object))) {
255
            throw new DaftObjectRepositoryTypeByClassMethodAndTypeException(
256
                $argument,
257 2
                static::class,
258 2
                $function,
259
                DaftNestedWriteableObject::class,
260
                is_string($object) ? $object : get_class($object)
261
            );
262
        }
263 2
264
        parent::ThrowIfNotType($object, $type, $argument, $function);
265 2
    }
266 2
267
    protected function RebuildTreeInefficiently() : void
268 2
    {
269
        $parentIdXref = [
270 2
            (array) $this->GetNestedObjectTreeRootId(),
271
        ];
272
        $xRefChildren = [
273
            [],
274
        ];
275
        $idXref = [];
276
277
        $level = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $level is dead and can be removed.
Loading history...
278
279
        $tree = $this->RecallDaftNestedObjectFullTree();
280
281
        usort($tree, function (DaftNestedWriteableObject $a, DaftNestedWriteableObject $b) : int {
282 2
            return $this->CompareObjects($a, $b);
283
        });
284 2
285
        foreach ($tree as $leaf) {
286
            $leafParentId = $leaf->ObtainDaftNestedObjectParentId();
287
            $pos = array_search($leafParentId, $parentIdXref, true);
288
289
            if (false === $pos) {
290 2
                $parentIdXref[] = $leafParentId;
291
292 2
                /**
293
                * @var int $pos
294 2
                */
295 2
                $pos = array_search($leafParentId, $parentIdXref, true);
296
297
                $xRefChildren[$pos] = [];
298
            }
299 2
300 2
            if ( ! in_array($leaf, $xRefChildren[$pos], true)) {
301
                $xRefChildren[$pos][] = $leaf;
302
            }
303
304
            if ( ! in_array($leaf->GetId(), $idXref, true)) {
305
                $idXref[] = $leaf->GetId();
306
            }
307
        }
308
309
        $rebuild = function (
310
            DaftNestedWriteableObject $leaf,
311
            int $level,
312
            int $n,
313
            array $parentIds,
314
            array $ids,
315
            array $children
316
        ) use (
317 2
            &$rebuild
318 2
        ) : int {
319
            $id = $leaf->GetId();
320
321
            $pos = (int) array_search($id, $ids, true);
0 ignored issues
show
Unused Code introduced by
The assignment to $pos is dead and can be removed.
Loading history...
322
323
            $leaf->SetIntNestedLevel($level);
324
            $leaf->SetIntNestedLeft($n);
325
326 2
            ++$n;
327
328
            $parentPos = array_search($id, $parentIds, true);
329
330
            if (false !== $parentPos) {
331
                foreach ($children[$parentPos] as $childLeaf) {
332 2
                    $n = $rebuild(
333 2
                        $childLeaf,
334
                        $level + 1,
335 2
                        $n,
336
                        $parentIds,
337 2
                        $ids,
338
                        $children
339 2
                    );
340
                }
341
            }
342 14
343
            $leaf->SetIntNestedRight($n);
344 14
345
            $this->StoreThenRetrieveFreshCopy($leaf);
346 14
347 14
            return $n + 1;
348
        };
349
350
        $n = 0;
351
352 16
        foreach ($xRefChildren[0] as $rootLeaf) {
353
            $n = $rebuild(
354
                $rootLeaf,
355
                0,
356
                $n,
357
                $parentIdXref,
358 16
                $idXref,
359 2
                $xRefChildren
360 2
            );
361 2
        }
362 2
    }
363 2
364 2
    protected function ModifyDaftNestedObjectTreeForRemoval(int $right, int $width) : void
365
    {
366
        /**
367
        * @var DaftNestedWriteableObject $alter
368 14
        */
369 14
        foreach ($this->RecallDaftNestedObjectFullTree() as $alter) {
370
            $alter = $this->StoreThenRetrieveFreshCopy($alter);
371 8
372
            $alterLeft = $alter->GetIntNestedLeft();
373
            $alterRight = $alter->GetIntNestedRight();
374
            $changed = false;
375
376
            if ($alterRight > $right) {
377 8
                $alter->SetIntNestedRight($alterRight - $width);
378 8
                $changed = true;
379
            }
380 8
            if ($alterLeft > $right) {
381 8
                $alter->SetIntNestedLeft($alterLeft - $width);
382 8
                $changed = true;
383 8
            }
384 8
385
            if ($changed) {
386 8
                $this->RememberDaftObject($alter);
387 8
            }
388 8
        }
389
    }
390
391
    protected function StoreThenRetrieveFreshCopy(
392
        DaftNestedWriteableObject $leaf
393
    ) : DaftNestedWriteableObject {
394 8
        $this->RememberDaftObject($leaf);
395
        $this->ForgetDaftObject($leaf);
396 8
        $this->ForgetDaftObjectById($leaf->GetId());
397 8
398 8
        $fresh = $this->RecallDaftObject($leaf->GetId());
399 8
400
        if ( ! ($fresh instanceof DaftNestedWriteableObject)) {
401 8
            throw new RuntimeException('Was not able to obtain a fresh copy of the object!');
402
        }
403 8
404 8
        return $fresh;
405 8
    }
406
}
407