1 | <?php |
||
2 | |||
3 | /** |
||
4 | * Copyright MediaCT. All rights reserved. |
||
5 | * https://www.mediact.nl |
||
6 | */ |
||
7 | |||
8 | namespace Mediact\DataContainer; |
||
9 | |||
10 | use ArrayIterator; |
||
11 | use Traversable; |
||
12 | |||
13 | /** |
||
14 | * Contains any data which can be accessed using dot-notation. |
||
15 | */ |
||
16 | class DataContainer implements IterableDataContainerInterface |
||
0 ignored issues
–
show
|
|||
17 | { |
||
18 | use ReplaceByPatternTrait; |
||
19 | use PathParserTrait; |
||
20 | use KeyQuoterTrait; |
||
21 | |||
22 | /** @var array */ |
||
23 | private $data; |
||
24 | |||
25 | /** |
||
26 | * Constructor. |
||
27 | * |
||
28 | * @param array $data |
||
29 | */ |
||
30 | 3 | final public function __construct(iterable $data = []) |
|
31 | { |
||
32 | 3 | $this->data = $data instanceof Traversable |
|
33 | 1 | ? iterator_to_array($data) |
|
34 | 2 | : $data; |
|
35 | 3 | } |
|
36 | |||
37 | /** |
||
38 | * Check whether a path exists. |
||
39 | * |
||
40 | * @param string $path |
||
41 | * |
||
42 | * @return bool |
||
43 | */ |
||
44 | 7 | public function has(string $path): bool |
|
45 | { |
||
46 | 7 | $check = clone $this; |
|
47 | 7 | return $this->get($path, $check) !== $check; |
|
48 | } |
||
49 | |||
50 | /** |
||
51 | * Get a value of a path. |
||
52 | * |
||
53 | * @param string $path |
||
54 | * @param mixed $default |
||
55 | * |
||
56 | * @return mixed |
||
57 | */ |
||
58 | 7 | public function get(string $path, $default = null) |
|
59 | { |
||
60 | 7 | return array_reduce( |
|
61 | 7 | $this->parsePath($path), |
|
62 | function ($data, $key) use ($default) { |
||
63 | 7 | return is_array($data) && array_key_exists($key, $data) |
|
64 | 5 | ? $data[$key] |
|
65 | 7 | : $default; |
|
66 | 7 | }, |
|
67 | 7 | $this->data |
|
68 | ); |
||
69 | } |
||
70 | |||
71 | /** |
||
72 | * Get the contained array. |
||
73 | * |
||
74 | * @return array |
||
75 | */ |
||
76 | 2 | public function all(): array |
|
77 | { |
||
78 | 2 | return $this->data; |
|
79 | } |
||
80 | |||
81 | /** |
||
82 | * Set a value on a path. |
||
83 | * |
||
84 | * @param string $path |
||
85 | * @param mixed $value |
||
86 | * |
||
87 | * @return void |
||
88 | */ |
||
89 | 6 | public function set(string $path, $value = null) |
|
90 | { |
||
91 | 6 | $keys = $this->parsePath($path); |
|
92 | 6 | $last = array_pop($keys); |
|
93 | 6 | $node =& $this->getNodeReference($keys); |
|
94 | |||
95 | 6 | if (strlen($last) > 0) { |
|
96 | 5 | $node[$last] = $value; |
|
97 | } else { |
||
98 | 1 | $node = $value; |
|
99 | } |
||
100 | 6 | } |
|
101 | |||
102 | /** |
||
103 | * Remove a path if it exists. |
||
104 | * |
||
105 | * @param string $pattern |
||
106 | * |
||
107 | * @return void |
||
108 | */ |
||
109 | 5 | public function remove(string $pattern) |
|
110 | { |
||
111 | 5 | foreach ($this->glob($pattern) as $path) { |
|
112 | 4 | $keys = $this->parsePath($path); |
|
113 | 4 | $last = array_pop($keys); |
|
114 | 4 | $node =& $this->getNodeReference($keys); |
|
115 | 4 | unset($node[$last]); |
|
116 | } |
||
117 | 5 | } |
|
118 | |||
119 | /** |
||
120 | * Find paths that match a pattern. |
||
121 | * |
||
122 | * @param string $pattern |
||
123 | * |
||
124 | * @return string[] |
||
125 | */ |
||
126 | 8 | public function glob(string $pattern): array |
|
127 | { |
||
128 | 8 | return $pattern === '' |
|
129 | 1 | ? [$pattern] |
|
130 | 7 | : $this->findArrayPathsByPatterns( |
|
131 | 7 | $this->data, |
|
132 | 7 | $this->parsePath($pattern), |
|
133 | 8 | '' |
|
134 | ); |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Find paths that match a pattern an their replacements. |
||
139 | * |
||
140 | * @param string $pattern |
||
141 | * @param string $replacement |
||
142 | * |
||
143 | * @return string[] |
||
144 | */ |
||
145 | 6 | public function expand(string $pattern, string $replacement): array |
|
146 | { |
||
147 | 6 | $matches = $this->glob($pattern); |
|
148 | |||
149 | 6 | return array_combine( |
|
150 | 6 | $matches, |
|
151 | 6 | array_map( |
|
152 | function ($match) use ($pattern, $replacement) { |
||
153 | 6 | return $this->replaceByPattern( |
|
154 | 6 | $pattern, |
|
155 | 6 | $match, |
|
156 | 6 | $replacement, |
|
157 | 6 | static::SEPARATOR |
|
158 | ); |
||
159 | 6 | }, |
|
160 | 6 | $matches |
|
161 | ) |
||
162 | ); |
||
163 | } |
||
164 | |||
165 | /** |
||
166 | * Branch into a list of data containers. |
||
167 | * |
||
168 | * @param string $pattern |
||
169 | * |
||
170 | * @return DataContainerInterface[] |
||
171 | */ |
||
172 | 4 | public function branch(string $pattern): array |
|
173 | { |
||
174 | 4 | return array_map( |
|
175 | function (array $data): DataContainerInterface { |
||
176 | 4 | return new static($data); |
|
177 | 4 | }, |
|
178 | 4 | array_map( |
|
179 | function (string $path): array { |
||
180 | 4 | return (array) $this->get($path, []); |
|
181 | 4 | }, |
|
182 | 4 | $this->glob($pattern) |
|
183 | ) |
||
184 | ); |
||
185 | } |
||
186 | |||
187 | /** |
||
188 | * Get a node from the container. |
||
189 | * |
||
190 | * @param string $path |
||
191 | * |
||
192 | * @return DataContainerInterface |
||
193 | */ |
||
194 | 4 | public function node(string $path): DataContainerInterface |
|
195 | { |
||
196 | 4 | $data = $this->get($path, []); |
|
197 | 4 | return new static( |
|
198 | 4 | is_array($data) |
|
199 | 3 | ? $data |
|
200 | 4 | : [] |
|
201 | ); |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Copy paths matching a pattern to another path. |
||
206 | * |
||
207 | * @param string $pattern |
||
208 | * @param string $replacement |
||
209 | * |
||
210 | * @return void |
||
211 | */ |
||
212 | 4 | public function copy(string $pattern, string $replacement) |
|
213 | { |
||
214 | 4 | $expanded = $this->expand($pattern, $replacement); |
|
215 | 4 | foreach ($expanded as $source => $destination) { |
|
216 | 4 | $this->set($destination, $this->get($source)); |
|
217 | } |
||
218 | 4 | } |
|
219 | |||
220 | /** |
||
221 | * Move paths matching a pattern to another path. |
||
222 | * |
||
223 | * @param string $pattern |
||
224 | * @param string $replacement |
||
225 | * |
||
226 | * @return void |
||
227 | */ |
||
228 | 4 | public function move(string $pattern, string $replacement) |
|
229 | { |
||
230 | 4 | $expanded = $this->expand($pattern, $replacement); |
|
231 | 4 | foreach ($expanded as $source => $destination) { |
|
232 | 4 | if ($source !== $destination) { |
|
233 | 4 | $this->set($destination, $this->get($source)); |
|
234 | 4 | if (strpos($destination, $source . static::SEPARATOR) !== 0) { |
|
235 | 4 | $this->remove($source); |
|
236 | } |
||
237 | } |
||
238 | } |
||
239 | 4 | } |
|
240 | |||
241 | /** |
||
242 | * Get reference to a data node, create it if it does not exist. |
||
243 | * |
||
244 | * @param array $keys |
||
245 | * |
||
246 | * @return array |
||
247 | */ |
||
248 | 10 | private function &getNodeReference(array $keys): array |
|
249 | { |
||
250 | 10 | $current =& $this->data; |
|
251 | |||
252 | 10 | while (!empty($keys)) { |
|
253 | 6 | $key = array_shift($keys); |
|
254 | if ( |
||
255 | 6 | !array_key_exists($key, $current) |
|
256 | 6 | || !is_array($current[$key]) |
|
257 | ) { |
||
258 | 2 | $current[$key] = []; |
|
259 | } |
||
260 | |||
261 | 6 | $current =& $current[$key]; |
|
262 | } |
||
263 | |||
264 | 10 | return $current; |
|
265 | } |
||
266 | |||
267 | /** |
||
268 | * Find paths in an array by an array of patterns. |
||
269 | * |
||
270 | * @param array $data |
||
271 | * @param string[] $patterns |
||
272 | * @param string $prefix |
||
273 | * |
||
274 | * @return array |
||
275 | */ |
||
276 | 7 | private function findArrayPathsByPatterns( |
|
277 | array $data, |
||
278 | array $patterns, |
||
279 | string $prefix |
||
280 | ): array { |
||
281 | 7 | $pattern = array_shift($patterns); |
|
282 | 7 | $matchingKeys = array_filter( |
|
283 | 7 | array_keys($data), |
|
284 | function ($key) use ($pattern) { |
||
285 | 7 | return fnmatch($pattern, $key, FNM_NOESCAPE); |
|
286 | 7 | } |
|
287 | ); |
||
288 | |||
289 | 7 | $paths = []; |
|
290 | 7 | foreach ($matchingKeys as $key) { |
|
291 | 7 | $path = $prefix . $this->quoteKey($key); |
|
292 | 7 | if (count($patterns) === 0) { |
|
293 | 7 | $paths[] = $path; |
|
294 | 7 | continue; |
|
295 | } |
||
296 | |||
297 | 5 | if (is_array($data[$key])) { |
|
298 | 5 | $paths = array_merge( |
|
299 | 5 | $paths, |
|
300 | 5 | $this->findArrayPathsByPatterns( |
|
301 | 5 | $data[$key], |
|
302 | 5 | $patterns, |
|
303 | 5 | $path . static::SEPARATOR |
|
304 | ) |
||
305 | ); |
||
306 | } |
||
307 | } |
||
308 | |||
309 | 7 | return $paths; |
|
310 | } |
||
311 | |||
312 | /** |
||
313 | * Retrieve an external iterator. |
||
314 | * |
||
315 | * @return Traversable |
||
316 | */ |
||
317 | 1 | public function getIterator(): Traversable |
|
318 | { |
||
319 | 1 | return new ArrayIterator($this->all()); |
|
320 | } |
||
321 | } |
||
322 |
This interface has been deprecated. The supplier of the interface has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the interface will be removed and what other interface to use instead.