Passed
Push — master ( 88bba2...d6ea6c )
by SignpostMarv
03:11
created

ModifyDaftNestedObjectTreeInsertAbove()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 1
rs 9.6666
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);
0 ignored issues
show
Bug introduced by
It seems like RecallDaftObject() 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

46
        /** @scrutinizer ignore-call */ 
47
        $reference = $this->RecallDaftObject($referenceId);
Loading history...
47
48
        if (
49 48
            ! is_null($leaf) &&
50
            (
51 36
                ($reference instanceof DaftNestedWriteableObject) ||
52 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

52
                ($referenceId === $this->/** @scrutinizer ignore-call */ GetNestedObjectTreeRootId())
Loading history...
53
            )
54
        ) {
55 24
            if ($reference instanceof DaftNestedWriteableObject) {
56 18
                return $this->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
57
            }
58
59 24
            return $this->ModifyDaftNestedObjectTreeInsertLooseIntoTree($leaf, $before, $above);
60
        }
61
62 24
        throw new InvalidArgumentException(sprintf(
63 24
            'Argument %u passed to %s() did not resolve to a leaf node!',
64 24
            is_null($leaf) ? 1 : 2,
65 24
            __METHOD__
66
        ));
67
    }
68
69 8
    public function ModifyDaftNestedObjectTreeRemoveWithObject(
70
        DaftNestedWriteableObject $root,
71
        ? DaftNestedWriteableObject $replacementRoot
72
    ) : int {
73
        if (
74 8
            $this->CountDaftNestedObjectTreeWithObject($root, false, null) > 0 &&
0 ignored issues
show
Bug introduced by
It seems like CountDaftNestedObjectTreeWithObject() 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

74
            $this->/** @scrutinizer ignore-call */ 
75
                   CountDaftNestedObjectTreeWithObject($root, false, null) > 0 &&
Loading history...
75 8
            is_null($replacementRoot)
76
        ) {
77 2
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
78
        }
79
80 6
        $root = $this->StoreThenRetrieveFreshLeaf($root);
81
82 6
        if ( ! is_null($replacementRoot)) {
83 4
            $this->ModifyDaftNestedObjectTreeRemoveWithObjectPrepareRemovalAndRebuild(
84 4
                $root,
85 4
                $replacementRoot
86
            );
87
        }
88
89 6
        $this->RemoveDaftObject($root);
0 ignored issues
show
Bug introduced by
It seems like RemoveDaftObject() 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

89
        $this->/** @scrutinizer ignore-call */ 
90
               RemoveDaftObject($root);
Loading history...
90
91 6
        $this->RebuildTreeInefficiently();
92
93 6
        return $this->CountDaftNestedObjectFullTree();
0 ignored issues
show
Bug introduced by
It seems like CountDaftNestedObjectFullTree() 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

93
        return $this->/** @scrutinizer ignore-call */ CountDaftNestedObjectFullTree();
Loading history...
94
    }
95
96
    /**
97
    *  {@inheritdoc}
98
    *
99
    * @param scalar|scalar[]|null $replacementRoot
100
    */
101 12
    public function ModifyDaftNestedObjectTreeRemoveWithId($root, $replacementRoot) : int
102
    {
103 12
        $rootObject = $this->RecallDaftObject($root);
104
105 12
        if ($rootObject instanceof DaftNestedWriteableObject) {
106
            if (
107 12
                $this->CountDaftNestedObjectTreeWithObject($rootObject, false, null) > 0 &&
108 12
                is_null($replacementRoot)
109
            ) {
110 2
                throw new BadMethodCallException('Cannot leave orphan objects in a tree');
111
            } elseif (
112 10
                ! is_null($replacementRoot) &&
113 10
                $replacementRoot !== $this->GetNestedObjectTreeRootId()
114
            ) {
115 4
                return $this->MaybeRemoveWithPossibleObject(
116 4
                    $rootObject,
117 4
                    $this->RecallDaftObject($replacementRoot)
118
                );
119
            }
120
121
            /**
122
            * @var scalar|scalar[] $replacementRoot
123
            */
124 6
            $replacementRoot = $replacementRoot;
125
126 6
            $this->UpdateRemoveThenRebuild($rootObject, $replacementRoot);
127
        }
128
129 6
        return $this->CountDaftNestedObjectFullTree();
130
    }
131
132 50
    public function StoreThenRetrieveFreshLeaf(
133
        DaftNestedWriteableObject $leaf
134
    ) : DaftNestedWriteableObject {
135 50
        $this->RememberDaftObject($leaf);
0 ignored issues
show
Bug introduced by
The method RememberDaftObject() does not exist on SignpostMarv\DaftObject\TraitWriteableTree. Did you maybe mean RememberDaftObjectData()? ( Ignorable by Annotation )

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

135
        $this->/** @scrutinizer ignore-call */ 
136
               RememberDaftObject($leaf);

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...
136 50
        $this->ForgetDaftObject($leaf);
0 ignored issues
show
Bug introduced by
It seems like ForgetDaftObject() 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

136
        $this->/** @scrutinizer ignore-call */ 
137
               ForgetDaftObject($leaf);
Loading history...
137 50
        $this->ForgetDaftObjectById($leaf->GetId());
0 ignored issues
show
Bug introduced by
It seems like ForgetDaftObjectById() 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

137
        $this->/** @scrutinizer ignore-call */ 
138
               ForgetDaftObjectById($leaf->GetId());
Loading history...
138
139 50
        $fresh = $this->RecallDaftObject($leaf->GetId());
140
141 50
        if ( ! ($fresh instanceof DaftNestedWriteableObject)) {
142 2
            throw new RuntimeException('Was not able to obtain a fresh copy of the object!');
143
        }
144
145 48
        return $fresh;
146
    }
147
148 48
    protected function RebuildAfterInsert(
149
        DaftNestedWriteableObject $newLeaf
150
    ) : DaftNestedWriteableObject {
151 48
        $this->RebuildTreeInefficiently();
152
153 48
        $newLeaf = $this->RecallDaftObject($newLeaf->GetId());
154
155 48
        if ( ! ($newLeaf instanceof DaftNestedWriteableObject)) {
156 12
            throw new RuntimeException('Could not retrieve leaf from tree after rebuilding!');
157
        }
158
159 36
        return $newLeaf;
160
    }
161
162 4
    protected function ModifyDaftNestedObjectTreeRemoveWithObjectPrepareRemovalAndRebuild(
163
        DaftNestedWriteableObject $root,
164
        DaftNestedWriteableObject $replacementRoot
165
    ) : void {
166
        /**
167
        * @var scalar|scalar[] $replacementRootId
168
        */
169 4
        $replacementRootId = $this->StoreThenRetrieveFreshLeaf($replacementRoot)->GetId();
170
171 4
        $this->UpdateRoots($root, $replacementRootId);
172 4
    }
173
174
    /**
175
    * @param scalar|scalar[] $replacementRootId
176
    */
177 10
    protected function UpdateRoots(DaftNestedWriteableObject $root, $replacementRootId) : void
178
    {
179
        /**
180
        * @var DaftNestedWriteableObject $alter
181
        */
182 10
        foreach ($this->RecallDaftNestedObjectTreeWithObject($root, false, 1) as $alter) {
0 ignored issues
show
Bug introduced by
It seems like RecallDaftNestedObjectTreeWithObject() 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

182
        foreach ($this->/** @scrutinizer ignore-call */ RecallDaftNestedObjectTreeWithObject($root, false, 1) as $alter) {
Loading history...
183 4
            if ($alter instanceof DaftNestedWriteableObject) {
184 4
                $alter->AlterDaftNestedObjectParentId($replacementRootId);
185 4
                $this->RememberDaftObject($alter);
186
            }
187
        }
188 10
    }
189
190
    /**
191
    * @param DaftNestedWriteableObject|mixed $leaf
192
    */
193 60
    protected function MaybeGetLeaf($leaf) : ? DaftNestedWriteableObject
194
    {
195 60
        if ($leaf === $this->GetNestedObjectTreeRootId()) {
196 12
            throw new InvalidArgumentException('Cannot pass root id as new leaf');
197 48
        } elseif ($leaf instanceof DaftNestedWriteableObject) {
198 24
            return $this->StoreThenRetrieveFreshLeaf($leaf);
199
        }
200
201 40
        $out = $this->RecallDaftObject($leaf);
202
203 40
        if ($out instanceof DaftNestedWriteableObject) {
204 28
            return $out;
205
        }
206
207 12
        return null;
208
    }
209
210 24
    protected function ModifyDaftNestedObjectTreeInsertLooseIntoTree(
211
        DaftNestedWriteableObject $leaf,
212
        bool $before,
213
        ? bool $above
214
    ) : DaftNestedWriteableObject {
215 24
        $tree = $this->RecallDaftNestedObjectFullTree(0);
0 ignored issues
show
Bug introduced by
It seems like RecallDaftNestedObjectFullTree() 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

215
        /** @scrutinizer ignore-call */ 
216
        $tree = $this->RecallDaftNestedObjectFullTree(0);
Loading history...
216
        $tree = array_filter($tree, function (DaftNestedWriteableObject $e) use ($leaf) : bool {
217 24
            return $e->GetId() !== $leaf->GetId();
218 24
        });
219
220 24
        if (0 === count($tree)) {
221 24
            $leaf->SetIntNestedLeft(0);
222 24
            $leaf->SetIntNestedRight(1);
223 24
            $leaf->SetIntNestedLevel(0);
224 24
            $leaf->AlterDaftNestedObjectParentId($this->GetNestedObjectTreeRootId());
225
226 24
            return $this->StoreThenRetrieveFreshLeaf($leaf);
227
        }
228
229
        /**
230
        * @var DaftNestedWriteableObject $reference
231
        */
232 24
        $reference = $before ? current($tree) : end($tree);
233
234 24
        return $this->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
235
    }
236
237 4
    protected function MaybeRemoveWithPossibleObject(
238
        DaftNestedWriteableObject $rootObject,
239
        ? DaftObject $replacementRootObject
240
    ) : int {
241 4
        if ( ! ($replacementRootObject instanceof DaftNestedWriteableObject)) {
242 2
            throw new InvalidArgumentException(
243 2
                'Could not locate replacement root, cannot leave orphan objects!'
244
            );
245
        }
246
247 2
        return $this->ModifyDaftNestedObjectTreeRemoveWithObject(
248 2
            $rootObject,
249 2
            $replacementRootObject
250
        );
251
    }
252
253
    /**
254
    * @param scalar|scalar[] $replacementRoot
255
    */
256 6
    protected function UpdateRemoveThenRebuild(
257
        DaftNestedWriteableObject $rootObject,
258
        $replacementRoot
259
    ) : void {
260 6
        $this->UpdateRoots($rootObject, $replacementRoot);
261
262 6
        $this->RemoveDaftObject($rootObject);
263
264 6
        $this->RebuildTreeInefficiently();
265 6
    }
266
267 36
    protected function ModifyDaftNestedObjectTreeInsertAbove(
268
        DaftNestedWriteableObject $newLeaf,
269
        DaftNestedWriteableObject $referenceLeaf
270
    ) : void {
271 36
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->ObtainDaftNestedObjectParentId());
272 36
        $referenceLeaf->AlterDaftNestedObjectParentId($newLeaf->GetId());
273
274 36
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
275 36
        $this->StoreThenRetrieveFreshLeaf($referenceLeaf);
276 36
    }
277
278 6
    protected function ModifyDaftNestedObjectTreeInsertBelow(
279
        DaftNestedWriteableObject $newLeaf,
280
        DaftNestedWriteableObject $referenceLeaf
281
    ) : void {
282 6
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->GetId());
283 6
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
284 6
    }
285
286 40
    protected function ModifyDaftNestedObjectTreeInsertAdjacent(
287
        DaftNestedWriteableObject $newLeaf,
288
        DaftNestedWriteableObject $referenceLeaf,
289
        bool $before
290
    ) : void {
291
        /**
292
        * @var array<int, DaftNestedWriteableObject> $siblings
293
        */
294 40
        $siblings = array_values(array_filter(
295 40
            $this->RecallDaftNestedObjectTreeWithId(
0 ignored issues
show
Bug introduced by
It seems like RecallDaftNestedObjectTreeWithId() 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

295
            $this->/** @scrutinizer ignore-call */ 
296
                   RecallDaftNestedObjectTreeWithId(
Loading history...
296 40
                $referenceLeaf->ObtainDaftNestedObjectParentId(),
297 40
                false,
298
                0
299
            ),
300
            function (DaftNestedWriteableObject $leaf) use ($newLeaf) : bool {
301 28
                return $leaf->GetId() !== $newLeaf->GetId();
302 40
            }
303
        ));
304
305 40
        $siblingIds = [];
306 40
        $siblingSort = [];
307 40
        $j = count($siblings);
308
309
        /**
310
        * @var DaftNestedWriteableObject $leaf
311
        */
312 40
        foreach ($siblings as $leaf) {
313
            /**
314
            * @var scalar|scalar[] $siblingId
315
            */
316 28
            $siblingId = $leaf->GetId();
317 28
            $siblingIds[] = $siblingId;
318 28
            $siblingSort[] = $leaf->GetIntNestedSortOrder();
319
        }
320
321 40
        $pos = array_search($referenceLeaf->GetId(), $siblingIds, true);
322
323 40
        if (false === $pos) {
324 12
            throw new RuntimeException('Reference leaf not found in siblings tree!');
325
        }
326
327 28
        for ($i = 0; $i < $j; ++$i) {
328 28
            $siblings[$i]->SetIntNestedSortOrder(
329 28
                $siblingSort[$i] +
330 28
                (($before ? ($i < $pos) : ($i <= $pos)) ? -1 : 1)
331
            );
332 28
            $this->StoreThenRetrieveFreshLeaf($siblings[$i]);
333
        }
334
335 28
        $newLeaf->SetIntNestedSortOrder($siblingSort[$pos]);
336 28
        $newLeaf->AlterDaftNestedObjectParentId($referenceLeaf->ObtainDaftNestedObjectParentId());
337
338 28
        $this->StoreThenRetrieveFreshLeaf($newLeaf);
339 28
    }
340
341 88
    protected function RememberDaftObjectData(DefinesOwnIdPropertiesInterface $object) : void
342
    {
343 88
        static::ThrowIfNotType($object, DaftNestedWriteableObject::class, 1, __METHOD__);
344
345 88
        parent::RememberDaftObjectData($object);
346 88
    }
347
348
    /**
349
    * @param DaftObject|string $object
350
    */
351 102
    protected static function ThrowIfNotType(
352
        $object,
353
        string $type,
354
        int $argument,
355
        string $function
356
    ) : void {
357 102
        if ( ! is_a($object, DaftNestedWriteableObject::class, is_string($object))) {
358 2
            throw new DaftObjectRepositoryTypeByClassMethodAndTypeException(
359 2
                $argument,
360 2
                static::class,
361 2
                $function,
362 2
                DaftNestedWriteableObject::class,
363 2
                is_string($object) ? $object : get_class($object)
364
            );
365
        }
366
367 100
        parent::ThrowIfNotType($object, $type, $argument, $function);
368 100
    }
369
370 48
    protected function RebuildTreeInefficiently() : void
371
    {
372 48
        $rebuilder = new InefficientDaftNestedRebuild($this);
0 ignored issues
show
Bug introduced by
$this of type SignpostMarv\DaftObject\TraitWriteableTree is incompatible with the type SignpostMarv\DaftObject\...stedWriteableObjectTree expected by parameter $tree of SignpostMarv\DaftObject\...dRebuild::__construct(). ( Ignorable by Annotation )

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

372
        $rebuilder = new InefficientDaftNestedRebuild(/** @scrutinizer ignore-type */ $this);
Loading history...
373 48
        $rebuilder->RebuildTree();
374 48
    }
375
}
376