Failed Conditions
Pull Request — main (#3639)
by Rafael
35:01
created

ArrayAccessor::getDeepElementWithLoop()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 19
ccs 10
cts 10
cp 1
rs 9.9666
c 0
b 0
f 0
cc 4
nc 6
nop 1
crap 4
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace ApacheSolrForTypo3\Solr\System\Util;
19
20
/**
21
 * Class ArrayAccessor
22
 *
23
 * LowLevel class to access nested associative arrays with
24
 * a path.
25
 *
26
 * Example:
27
 *
28
 * $data = [];
29
 * $data['foo']['bar'] = 'bla';
30
 *
31
 * $accessor = new ArrayAccessor($data);
32
 * $value = $accessor->get('foo.bar');
33
 *
34
 * echo $value;
35
 *
36
 * the example above will output "bla"
37
 */
38
class ArrayAccessor
39
{
40
    protected ?array $data = [];
41
42
    protected string $pathSeparator = ':';
43
44
    protected bool $includePathSeparatorInKeys = false;
45
46 513
    public function __construct(
47
        array $data = [],
48
        string $pathSeparator = ':',
49
        bool $includePathSeparatorInKeys = false
50
    ) {
51 513
        $this->data = $data;
52 513
        $this->pathSeparator = $pathSeparator;
53 513
        $this->includePathSeparatorInKeys = $includePathSeparatorInKeys;
54
    }
55
56 45
    public function setData(array $data): void
57
    {
58 45
        $this->data = $data;
59
    }
60
61 64
    public function getData(): array
62
    {
63 64
        return $this->data;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->data could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
64
    }
65
66 443
    public function get(string $path, mixed $defaultIfEmpty = null): mixed
67
    {
68 443
        $pathArray = $this->getPathAsArray($path);
69 443
        $pathSegmentCount = count($pathArray);
70
71
        // direct access for small paths
72
        switch ($pathSegmentCount) {
73 443
            case 1:
74 18
                return $this->data[$pathArray[0]] ?? $defaultIfEmpty;
75 443
            case 2:
76 111
                return $this->data[$pathArray[0]][$pathArray[1]] ?? $defaultIfEmpty;
77 390
            case 3:
78 67
                return $this->data[$pathArray[0]][$pathArray[1]][$pathArray[2]] ?? $defaultIfEmpty;
79
            default:
80
                // when we have a longer path we use a loop to get the element
81 369
                $loopResult = $this->getDeepElementWithLoop($pathArray);
82 369
                return $loopResult ?? $defaultIfEmpty;
83
        }
84
    }
85
86 369
    protected function getDeepElementWithLoop(array $pathArray): mixed
87
    {
88 369
        $currentElement = $this->data;
89 369
        foreach ($pathArray as $key => $pathSegment) {
90
            // if the current path segment was not found we can stop searching
91 369
            if (!isset($currentElement[$pathSegment])) {
92 343
                break;
93
            }
94 354
            $currentElement = $currentElement[$pathSegment];
95 354
            unset($pathArray[$key]);
96
        }
97
98
        // if segments are left the item does not exist
99 369
        if (count($pathArray) > 0) {
100 343
            return null;
101
        }
102
103
        // if no items left, we've found the last element
104 270
        return $currentElement;
105
    }
106
107 56
    public function has(string $path): bool
108
    {
109 56
        return $this->get($path) !== null;
110
    }
111
112 81
    public function set(string $path, mixed $value): void
113
    {
114 81
        $pathArray = $this->getPathAsArray($path);
115 81
        $pathSegmentCount = count($pathArray);
116
117
        switch ($pathSegmentCount) {
118
            // direct access for small paths
119 81
            case 1:
120 1
                $this->data[$pathArray[0]] = $value;
121 1
                return;
122 81
            case 2:
123 75
                $this->data[$pathArray[0]][$pathArray[1]] = $value;
124 75
                return;
125
            default:
126 6
                $this->setDeepElementWithLoop($pathArray, $value);
127
        }
128
    }
129
130 6
    protected function setDeepElementWithLoop(array $pathArray, mixed $value): void
131
    {
132 6
        $currentElement = &$this->data;
133 6
        foreach ($pathArray as $key => $pathSegment) {
134 6
            if (!isset($currentElement[$pathSegment])) {
135 6
                $currentElement[$pathSegment] = [];
136
            }
137
138 6
            unset($pathArray[$key]);
139
            // if segments are left the item does not exist
140 6
            if (count($pathArray) === 0) {
141 6
                $currentElement[$pathSegment] = $value;
142 6
                return;
143
            }
144
145 6
            $currentElement = &$currentElement[$pathSegment];
146
        }
147
    }
148
149 37
    public function reset(string $path): void
150
    {
151 37
        $pathArray = $this->getPathAsArray($path);
152 37
        $pathSegmentCount = count($pathArray);
153
154
        switch ($pathSegmentCount) {
155
            // direct access for small paths
156 37
            case 1:
157
                unset($this->data[$pathArray[0]]);
158
                return;
159 37
            case 2:
160 32
                unset($this->data[$pathArray[0]][$pathArray[1]]);
161 32
                return;
162
            default:
163 5
                $this->resetDeepElementWithLoop($pathArray);
164
        }
165
    }
166
167 5
    protected function resetDeepElementWithLoop(array $pathArray): void
168
    {
169 5
        $currentElement = &$this->data;
170
171 5
        foreach ($pathArray as $key => $pathSegment) {
172 5
            unset($pathArray[$key]);
173
            // if segments are left the item does not exist
174 5
            if (count($pathArray) === 0) {
175
                /** @noinspection PhpUndefinedVariableInspection */
176 5
                unset($currentElement[$pathSegment]);
177
                // when the element is empty after unsetting the path segment, we can remove it completely
178 5
                if (empty($currentElement)) {
179 5
                    unset($currentElement);
180
                }
181 5
            } elseif (isset($currentElement) && isset($currentElement[$pathSegment])) {
182 5
                $currentElement = &$currentElement[$pathSegment];
183
            }
184
        }
185
    }
186
187 446
    protected function getPathAsArray(string $path): array
188
    {
189 446
        if (!$this->includePathSeparatorInKeys) {
190 117
            return explode($this->pathSeparator, $path);
191
        }
192
193 382
        $substitutedPath = str_replace($this->pathSeparator, $this->pathSeparator . '@@@', trim($path));
194 382
        return array_filter(explode('@@@', $substitutedPath));
195
    }
196
}
197