Passed
Push — master ( 910de5...c66f0c )
by Jan-Marten
01:33
created

DataContainer::replaceByRegex()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

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