Passed
Push — master ( 5b2ba6...cbe18c )
by Beau
01:51
created

Data::keyToPathArray()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is a part of dflydev/dot-access-data.
5
 *
6
 * (c) Dragonfly Development Inc.
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Dflydev\DotAccessData;
13
14
use ArrayAccess;
15
use Dflydev\DotAccessData\Exception\DataException;
16
use Dflydev\DotAccessData\Exception\InvalidPathException;
17
18
/**
19
 * @implements ArrayAccess<string, mixed>
20
 */
21
class Data implements DataInterface, ArrayAccess
22
{
23
    private const DELIMITERS = ['.', '/'];
24
25
    /**
26
     * Internal representation of data data
27
     *
28
     * @var array<string, mixed>
29
     */
30
    protected $data;
31
32
    /**
33
     * Constructor
34
     *
35
     * @param array<string, mixed> $data
36
     */
37 42
    public function __construct(array $data = [])
38
    {
39 42
        $this->data = $data;
40 42
    }
41
42
    /**
43
     * {@inheritdoc}
44
     */
45 3
    public function append(string $key, $value = null): void
46
    {
47 3
        $currentValue =& $this->data;
48 3
        $keyPath = $this->keyToPathArray($key);
49
50 3
        if (1 == count($keyPath)) {
51 3
            if (!isset($currentValue[$key])) {
52 3
                $currentValue[$key] = [];
53
            }
54 3
            if (!is_array($currentValue[$key])) {
55
                // Promote this key to an array.
56
                // TODO: Is this really what we want to do?
57 3
                $currentValue[$key] = [$currentValue[$key]];
58
            }
59 3
            $currentValue[$key][] = $value;
60
61 3
            return;
62
        }
63
64 3
        $endKey = array_pop($keyPath);
65 3
        for ($i = 0; $i < count($keyPath); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
66 3
            $currentKey =& $keyPath[$i];
67 3
            if (! isset($currentValue[$currentKey])) {
68 3
                $currentValue[$currentKey] = [];
69
            }
70 3
            $currentValue =& $currentValue[$currentKey];
71
        }
72
73 3
        if (!isset($currentValue[$endKey])) {
74 3
            $currentValue[$endKey] = [];
75
        }
76 3
        if (!is_array($currentValue[$endKey])) {
77 3
            $currentValue[$endKey] = [$currentValue[$endKey]];
78
        }
79
        // Promote this key to an array.
80
        // TODO: Is this really what we want to do?
81 3
        $currentValue[$endKey][] = $value;
82 3
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87 9
    public function set(string $key, $value = null): void
88
    {
89 9
        $currentValue =& $this->data;
90 9
        $keyPath = $this->keyToPathArray($key);
91
92 9
        if (1 == count($keyPath)) {
93 6
            $currentValue[$key] = $value;
94
95 6
            return;
96
        }
97
98 9
        $endKey = array_pop($keyPath);
99 9
        for ($i = 0; $i < count($keyPath); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
100 9
            $currentKey =& $keyPath[$i];
101 9
            if (!isset($currentValue[$currentKey])) {
102 9
                $currentValue[$currentKey] = [];
103
            }
104 9
            if (!is_array($currentValue[$currentKey])) {
105 3
                throw new DataException("Key path at $currentKey of $key cannot be indexed into (is not an array)");
106
            }
107 9
            $currentValue =& $currentValue[$currentKey];
108
        }
109 9
        $currentValue[$endKey] = $value;
110 9
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115 6
    public function remove(string $key): void
116
    {
117 6
        $currentValue =& $this->data;
118 6
        $keyPath = $this->keyToPathArray($key);
119
120 6
        if (1 == count($keyPath)) {
121 6
            unset($currentValue[$key]);
122
123 6
            return;
124
        }
125
126 6
        $endKey = array_pop($keyPath);
127 6
        for ($i = 0; $i < count($keyPath); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
128 6
            $currentKey =& $keyPath[$i];
129 6
            if (!isset($currentValue[$currentKey])) {
130 6
                return;
131
            }
132 6
            $currentValue =& $currentValue[$currentKey];
133
        }
134 6
        unset($currentValue[$endKey]);
135 6
    }
136
137
    /**
138
     * {@inheritdoc}
139
     *
140
     * @psalm-mutation-free
141
     */
142 30
    public function get(string $key, $default = null)
143
    {
144 30
        $currentValue = $this->data;
145 30
        $keyPath = $this->keyToPathArray($key);
146
147 30
        for ($i = 0; $i < count($keyPath); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
148 30
            $currentKey = $keyPath[$i];
149 30
            if (!isset($currentValue[$currentKey])) {
150 27
                return $default;
151
            }
152 30
            if (!is_array($currentValue)) {
153
                return $default;
154
            }
155 30
            $currentValue = $currentValue[$currentKey];
156
        }
157
158 30
        return $currentValue === null ? $default : $currentValue;
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     *
164
     * @psalm-mutation-free
165
     */
166 6
    public function has(string $key): bool
167
    {
168 6
        $currentValue = &$this->data;
169 6
        $keyPath = $this->keyToPathArray($key);
170
171 6
        for ($i = 0; $i < count($keyPath); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
172 6
            $currentKey = $keyPath[$i];
173
            if (
174 6
                !is_array($currentValue) ||
175 6
                !array_key_exists($currentKey, $currentValue)
176
            ) {
177 6
                return false;
178
            }
179 6
            $currentValue = &$currentValue[$currentKey];
180
        }
181
182 6
        return true;
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     *
188
     * @psalm-mutation-free
189
     */
190 6
    public function getData(string $key): DataInterface
191
    {
192 6
        $value = $this->get($key);
193 6
        if (is_array($value) && Util::isAssoc($value)) {
194 6
            return new Data($value);
195
        }
196
197
        throw new DataException("Value at '$key' could not be represented as a DataInterface");
198
    }
199
200
    /**
201
     * {@inheritdoc}
202
     */
203 6
    public function import(array $data, bool $clobber = true): void
204
    {
205 6
        $this->data = Util::mergeAssocArray($this->data, $data, $clobber);
206 6
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211 3
    public function importData(DataInterface $data, bool $clobber = true): void
212
    {
213 3
        $this->import($data->export(), $clobber);
214 3
    }
215
216
    /**
217
     * {@inheritdoc}
218
     *
219
     * @psalm-mutation-free
220
     */
221 6
    public function export(): array
222
    {
223 6
        return $this->data;
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229 3
    public function offsetExists($key)
230
    {
231 3
        return $this->has($key);
232
    }
233
234
    /**
235
     * {@inheritdoc}
236
     */
237 9
    public function offsetGet($key)
238
    {
239 9
        return $this->get($key);
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     *
245
     * @param string $key
246
     */
247 3
    public function offsetSet($key, $value)
248
    {
249 3
        $this->set($key, $value);
250 3
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255 3
    public function offsetUnset($key)
256
    {
257 3
        $this->remove($key);
258 3
    }
259
260
    /**
261
     * @return string[]
262
     *
263
     * @psalm-return non-empty-list<string>
264
     *
265
     * @psalm-pure
266
     */
267 39
    protected function keyToPathArray(string $path): array
268
    {
269 39
        if (\strlen($path) === 0) {
270 30
            throw new InvalidPathException('Path cannot be an empty string');
271
        }
272
273 39
        $path = \str_replace(self::DELIMITERS, '.', $path);
274
275 39
        return \explode('.', $path);
276
    }
277
}
278