Passed
Pull Request — master (#24)
by Colin
85:06 queued 50:03
created

Data::keyToPathArray()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
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
    public function __construct(array $data = [])
38
    {
39
        $this->data = $data;
40
    }
41
42
    /**
43
     * {@inheritdoc}
44
     */
45
    public function append(string $key, $value = null): void
46
    {
47
        $currentValue =& $this->data;
48
        $keyPath = $this->keyToPathArray($key);
49
50
        if (1 == count($keyPath)) {
51
            if (!isset($currentValue[$key])) {
52
                $currentValue[$key] = [];
53
            }
54
            if (!is_array($currentValue[$key])) {
55
                // Promote this key to an array.
56
                // TODO: Is this really what we want to do?
57
                $currentValue[$key] = [$currentValue[$key]];
58
            }
59
            $currentValue[$key][] = $value;
60
61
            return;
62
        }
63
64
        $endKey = array_pop($keyPath);
65
        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
            $currentKey =& $keyPath[$i];
67
            if (! isset($currentValue[$currentKey])) {
68
                $currentValue[$currentKey] = [];
69
            }
70
            $currentValue =& $currentValue[$currentKey];
71
        }
72
73
        if (!isset($currentValue[$endKey])) {
74
            $currentValue[$endKey] = [];
75
        }
76
        if (!is_array($currentValue[$endKey])) {
77
            $currentValue[$endKey] = [$currentValue[$endKey]];
78
        }
79
        // Promote this key to an array.
80
        // TODO: Is this really what we want to do?
81
        $currentValue[$endKey][] = $value;
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public function set(string $key, $value = null): void
88
    {
89
        $currentValue =& $this->data;
90
        $keyPath = $this->keyToPathArray($key);
91
92
        if (1 == count($keyPath)) {
93
            $currentValue[$key] = $value;
94
95
            return;
96
        }
97
98
        $endKey = array_pop($keyPath);
99
        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
            $currentKey =& $keyPath[$i];
101
            if (!isset($currentValue[$currentKey])) {
102
                $currentValue[$currentKey] = [];
103
            }
104
            if (!is_array($currentValue[$currentKey])) {
105
                throw new DataException("Key path at $currentKey of $key cannot be indexed into (is not an array)");
106
            }
107
            $currentValue =& $currentValue[$currentKey];
108
        }
109
        $currentValue[$endKey] = $value;
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function remove(string $key): void
116
    {
117
        $currentValue =& $this->data;
118
        $keyPath = $this->keyToPathArray($key);
119
120
        if (1 == count($keyPath)) {
121
            unset($currentValue[$key]);
122
123
            return;
124
        }
125
126
        $endKey = array_pop($keyPath);
127
        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
            $currentKey =& $keyPath[$i];
129
            if (!isset($currentValue[$currentKey])) {
130
                return;
131
            }
132
            $currentValue =& $currentValue[$currentKey];
133
        }
134
        unset($currentValue[$endKey]);
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     *
140
     * @psalm-mutation-free
141
     */
142
    public function get(string $key, $default = null)
143
    {
144
        $currentValue = $this->data;
145
        $keyPath = $this->keyToPathArray($key);
146
147
        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
            $currentKey = $keyPath[$i];
149
            if (!isset($currentValue[$currentKey])) {
150
                return $default;
151
            }
152
            if (!is_array($currentValue)) {
153
                return $default;
154
            }
155
            $currentValue = $currentValue[$currentKey];
156
        }
157
158
        return $currentValue === null ? $default : $currentValue;
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     *
164
     * @psalm-mutation-free
165
     */
166
    public function has(string $key): bool
167
    {
168
        $currentValue = &$this->data;
169
        $keyPath = $this->keyToPathArray($key);
170
171
        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
            $currentKey = $keyPath[$i];
173
            if (
174
                !is_array($currentValue) ||
175
                !array_key_exists($currentKey, $currentValue)
176
            ) {
177
                return false;
178
            }
179
            $currentValue = &$currentValue[$currentKey];
180
        }
181
182
        return true;
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     *
188
     * @psalm-mutation-free
189
     */
190
    public function getData(string $key): DataInterface
191
    {
192
        $value = $this->get($key);
193
        if (is_array($value) && Util::isAssoc($value)) {
194
            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
    public function import(array $data, bool $clobber = true): void
204
    {
205
        $this->data = Util::mergeAssocArray($this->data, $data, $clobber);
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211
    public function importData(DataInterface $data, bool $clobber = true): void
212
    {
213
        $this->import($data->export(), $clobber);
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     *
219
     * @psalm-mutation-free
220
     */
221
    public function export(): array
222
    {
223
        return $this->data;
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229
    public function offsetExists($key)
230
    {
231
        return $this->has($key);
232
    }
233
234
    /**
235
     * {@inheritdoc}
236
     */
237
    public function offsetGet($key)
238
    {
239
        return $this->get($key);
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     *
245
     * @param string $key
246
     */
247
    public function offsetSet($key, $value)
248
    {
249
        $this->set($key, $value);
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255
    public function offsetUnset($key)
256
    {
257
        $this->remove($key);
258
    }
259
260
    /**
261
     * @return string[]
262
     *
263
     * @psalm-return non-empty-list<string>
264
     *
265
     * @psalm-pure
266
     */
267
    protected function keyToPathArray(string $path): array
268
    {
269
        if (\strlen($path) === 0) {
270
            throw new InvalidPathException('Path cannot be an empty string');
271
        }
272
273
        $path = \str_replace(self::DELIMITERS, '.', $path);
274
275
        return \explode('.', $path);
276
    }
277
}
278