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
|
|||
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
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
}
![]() |
|||
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
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
}
![]() |
|||
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
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
}
![]() |
|||
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
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
}
![]() |
|||
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 |
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: