Passed
Push — master ( 646b9f...4da9b2 )
by SignpostMarv
03:41
created

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 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
    const EXCEPTION_ARGUMENT_DID_NOT_RESOLVE_TO_A_LEAF_NODE =
18
        'Argument %u passed to %s() did not resolve to a leaf node!';
19
20 60
    public function ModifyDaftNestedObjectTreeInsert(
21
        DaftNestedWriteableObject $newLeaf,
22
        DaftNestedWriteableObject $referenceLeaf,
23
        bool $before = false,
24
        bool $above = null
25
    ) : DaftNestedWriteableObject {
26 60
        if ($newLeaf->GetId() === $referenceLeaf->GetId()) {
27 12
            throw new InvalidArgumentException('Cannot modify leaf relative to itself!');
28
        }
29
30 48
        if (true === $above) {
31 36
            $this->ModifyDaftNestedObjectTreeInsertAbove($newLeaf, $referenceLeaf);
32 32
        } elseif (false === $above) {
33 6
            $this->ModifyDaftNestedObjectTreeInsertBelow($newLeaf, $referenceLeaf);
34
        } else {
35 28
            $this->ModifyDaftNestedObjectTreeInsertAdjacent($newLeaf, $referenceLeaf, $before);
36
        }
37
38 48
        return $this->RebuildAfterInsert($newLeaf);
39
    }
40
41 60
    public function ModifyDaftNestedObjectTreeInsertLoose(
42
        $leaf,
43
        $referenceId,
44
        bool $before = false,
45
        bool $above = null
46
    ) : DaftNestedWriteableObject {
47 60
        $leaf = $this->MaybeGetLeaf($leaf);
48
49 48
        $reference = $this->RecallDaftObject($referenceId);
50
51
        if (
52 48
            ! is_null($leaf) &&
53
            (
54 36
                ($reference instanceof DaftNestedWriteableObject) ||
55 48
                ($referenceId === $this->GetNestedObjectTreeRootId())
56
            )
57
        ) {
58 24
            if ($reference instanceof DaftNestedWriteableObject) {
59 18
                return $this->ModifyDaftNestedObjectTreeInsert($leaf, $reference, $before, $above);
60
            }
61
62 24
            return $this->ModifyDaftNestedObjectTreeInsertLooseIntoTree($leaf, $before, $above);
63
        }
64
65 24
        throw new InvalidArgumentException(sprintf(
66 24
            self::EXCEPTION_ARGUMENT_DID_NOT_RESOLVE_TO_A_LEAF_NODE,
67 24
            is_null($leaf) ? 1 : 2,
68 24
            __METHOD__
69
        ));
70
    }
71
72 8
    public function ModifyDaftNestedObjectTreeRemoveWithObject(
73
        DaftNestedWriteableObject $root,
74
        ? DaftNestedWriteableObject $replacementRoot
75
    ) : int {
76
        if (
77 8
            $this->CountDaftNestedObjectTreeWithObject($root, false, null) > 0 &&
78 8
            is_null($replacementRoot)
79
        ) {
80 2
            throw new BadMethodCallException('Cannot leave orphan objects in a tree');
81
        }
82
83 6
        $root = $this->StoreThenRetrieveFreshLeaf($root);
84
85 6
        if ( ! is_null($replacementRoot)) {
86 4
            $this->ModifyDaftNestedObjectTreeRemoveWithObjectPrepareRemovalAndRebuild(
87 4
                $root,
88 4
                $replacementRoot
89
            );
90
        }
91
92 6
        $this->RemoveDaftObject($root);
93
94 6
        $this->RebuildTreeInefficiently();
95
96 6
        return $this->CountDaftNestedObjectFullTree();
97
    }
98
99
    /**
100
    *  {@inheritdoc}
101
    *
102
    * @param scalar|scalar[]|null $replacementRoot
103
    */
104 12
    public function ModifyDaftNestedObjectTreeRemoveWithId($root, $replacementRoot) : int
105
    {
106 12
        $rootObject = $this->RecallDaftObject($root);
107
108 12
        if ($rootObject instanceof DaftNestedWriteableObject) {
109
            if (
110 12
                $this->CountDaftNestedObjectTreeWithObject($rootObject, false, null) > 0 &&
111 12
                is_null($replacementRoot)
112
            ) {
113 2
                throw new BadMethodCallException('Cannot leave orphan objects in a tree');
114
            } elseif (
115 10
                ! is_null($replacementRoot) &&
116 10
                $replacementRoot !== $this->GetNestedObjectTreeRootId()
117
            ) {
118 4
                return $this->MaybeRemoveWithPossibleObject(
119 4
                    $rootObject,
120 4
                    $this->RecallDaftObject($replacementRoot)
121
                );
122
            }
123
124
            /**
125
            * @var scalar|scalar[] $replacementRoot
126
            */
127 6
            $replacementRoot = $replacementRoot;
128
129 6
            $this->UpdateRemoveThenRebuild($rootObject, $replacementRoot);
130
        }
131
132 6
        return $this->CountDaftNestedObjectFullTree();
133
    }
134
135 50
    public function StoreThenRetrieveFreshLeaf(
136
        DaftNestedWriteableObject $leaf
137
    ) : DaftNestedWriteableObject {
138 50
        $this->RememberDaftObject($leaf);
139 50
        $this->ForgetDaftObject($leaf);
140 50
        $this->ForgetDaftObjectById($leaf->GetId());
141
142 50
        $fresh = $this->RecallDaftObject($leaf->GetId());
143
144 50
        if ( ! ($fresh instanceof DaftNestedWriteableObject)) {
145 2
            throw new RuntimeException('Was not able to obtain a fresh copy of the object!');
146
        }
147
148 48
        return $fresh;
149
    }
150
151 48
    protected function RebuildAfterInsert(
152
        DaftNestedWriteableObject $newLeaf
153
    ) : DaftNestedWriteableObject {
154 48
        $this->RebuildTreeInefficiently();
155
156 48
        $newLeaf = $this->RecallDaftObject($newLeaf->GetId());
157
158 48
        if ( ! ($newLeaf instanceof DaftNestedWriteableObject)) {
159 12
            throw new RuntimeException('Could not retrieve leaf from tree after rebuilding!');
160
        }
161
162 36
        return $newLeaf;
163
    }
164
165 4
    protected function ModifyDaftNestedObjectTreeRemoveWithObjectPrepareRemovalAndRebuild(
166
        DaftNestedWriteableObject $root,
167
        DaftNestedWriteableObject $replacementRoot
168
    ) : void {
169
        /**
170
        * @var scalar|scalar[] $replacementRootId
171
        */
172 4
        $replacementRootId = $this->StoreThenRetrieveFreshLeaf($replacementRoot)->GetId();
173
174 4
        $this->UpdateRoots($root, $replacementRootId);
175 4
    }
176
177
    /**
178
    * @param scalar|scalar[] $replacementRootId
179
    */
180 10
    protected function UpdateRoots(DaftNestedWriteableObject $root, $replacementRootId) : void
181
    {
182
        /**
183
        * @var DaftNestedWriteableObject $alter
184
        */
185 10
        foreach ($this->RecallDaftNestedObjectTreeWithObject($root, false, 1) as $alter) {
186 4
            $alter->AlterDaftNestedObjectParentId($replacementRootId);
187 4
            $this->RememberDaftObject($alter);
188
        }
189 10
    }
190
191
    /**
192
    * @param DaftNestedWriteableObject|mixed $leaf
193
    */
194 60
    protected function MaybeGetLeaf($leaf) : ? DaftNestedWriteableObject
195
    {
196 60
        if ($leaf === $this->GetNestedObjectTreeRootId()) {
197 12
            throw new InvalidArgumentException('Cannot pass root id as new leaf');
198 48
        } elseif ($leaf instanceof DaftNestedWriteableObject) {
199 24
            return $this->StoreThenRetrieveFreshLeaf($leaf);
200
        }
201
202
        /**
203
        * @var DaftNestedWriteableObject|null $out
204
        */
205 40
        $out = $this->RecallDaftObject($leaf);
206
207 40
        return $out;
208
    }
209
210 24
    protected function ModifyDaftNestedObjectTreeInsertLooseIntoTree(
211
        DaftNestedWriteableObject $leaf,
212
        bool $before,
213
        ? bool $above
214
    ) : DaftNestedWriteableObject {
215 24
        $tree = $this->RecallDaftNestedObjectFullTree(0);
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(
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);
373 48
        $rebuilder->RebuildTree();
374 48
    }
375
}
376