Issues (1)

src/DataContainer.php (1 issue)

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
Deprecated Code introduced by
The interface Mediact\DataContainer\It...eDataContainerInterface has been deprecated: DataContainerInterface is iterable. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

16
class DataContainer implements /** @scrutinizer ignore-deprecated */ IterableDataContainerInterface

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.

Loading history...
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