Passed
Pull Request — master (#25)
by Colin
78:53 queued 43:54
created

Data::keyToPathArray()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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