Passed
Push — master ( a6a570...b51c15 )
by Aymen
02:43
created

Dotted::arrayMergeRecursiveDistinct()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 14
nc 5
nop 2
1
<?php
2
/**
3
 * This file is part of the fnayou/dotted package.
4
 *
5
 * Copyright (c) 2016. Aymen FNAYOU <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Fnayou;
12
13
/**
14
 * Class Dotted.
15
 */
16
class Dotted
17
{
18
    const SEPARATOR = '/[\.]/';
19
20
    /**
21
     * @var array
22
     */
23
    protected $values = [];
24
25
    /**
26
     * @param array $values
27
     */
28
    public function __construct(array $values = [])
29
    {
30
        $this->setValues($values);
31
    }
32
33
    /**
34
     * @param array $values
35
     *
36
     * @return \Fnayou\Dotted
37
     */
38
    public static function create(array $values = [])
39
    {
40
        return new static($values);
41
    }
42
43
    /**
44
     * @param array $values
45
     *
46
     * @return \Fnayou\Dotted
47
     */
48
    public function setValues(array $values)
49
    {
50
        $this->values = $values;
51
52
        return $this;
53
    }
54
55
    /**
56
     * @return array
57
     */
58
    public function getValues()
59
    {
60
        return $this->values;
61
    }
62
63
    /**
64
     * @param string $path
65
     * @param string $default
66
     *
67
     * @return mixed
68
     */
69
    public function get(string $path, string $default = null)
70
    {
71
        if (true === empty($path)) {
72
            throw new \InvalidArgumentException('"path" parameter cannot be empty.');
73
        }
74
75
        $keys = $this->explode($path);
76
        $values = $this->values;
77
78
        foreach ($keys as $key) {
79
            if (true === isset($values[$key])) {
80
                $values = $values[$key];
81
            } else {
82
                return $default;
83
            }
84
        }
85
86
        return $values;
87
    }
88
89
    /**
90
     * @param string $path
91
     * @param array  $values
92
     *
93
     * @return \Fnayou\Dotted
94
     */
95
    public function add(string $path, array $values)
96
    {
97
        $pathValues = (array) $this->get($path);
98
99
        $this->set($path, $this->arrayMergeRecursiveDistinct($pathValues, $values));
100
101
        return $this;
102
    }
103
104
    /**
105
     * @param string $path
106
     * @param mixed  $value
107
     *
108
     * @throws \InvalidArgumentException
109
     *
110
     * @return \Fnayou\Dotted
111
     */
112
    public function set(string $path, $value)
113
    {
114
        if (true === empty($path)) {
115
            throw new \InvalidArgumentException('"path" parameter cannot be empty.');
116
        }
117
118
        $values = &$this->values;
119
        $keys = $this->explode($path);
120
        while (0 < \count($keys)) {
121
            if (1 === \count($keys)) {
122
                if (true === \is_array($values)) {
123
                    $values[\array_shift($keys)] = $value;
124
                } else {
125
                    throw new \InvalidArgumentException(
126
                        \sprintf('cannot set value at "path" (%s) : not array.', $path)
127
                    );
128
                }
129
            } else {
130
                $key = \array_shift($keys);
131
                if (!isset($values[$key])) {
132
                    $values[$key] = [];
133
                }
134
                $values = &$values[$key];
135
            }
136
        }
137
138
        return $this;
139
    }
140
141
    /**
142
     * @param string $path
143
     *
144
     * @return bool
145
     */
146
    public function has(string $path)
147
    {
148
        $keys = $this->explode($path);
149
        $values = $this->values;
150
151
        foreach ($keys as $key) {
152
            if (true === isset($values[$key])) {
153
                $values = $values[$key];
154
                continue;
155
            }
156
157
            return false;
158
        }
159
160
        return true;
161
    }
162
163
    /**
164
     * @return array
165
     */
166
    public function flatten()
167
    {
168
        return $this->arrayFlattenRecursive($this->values, null);
169
    }
170
171
    /**
172
     * @param string $path
173
     *
174
     * @return array
175
     */
176
    protected function explode(string $path)
177
    {
178
        return \preg_split(static::SEPARATOR, $path);
179
    }
180
181
    /**
182
     * array_merge_recursive does indeed merge arrays, but it converts values with duplicate
183
     * keys to arrays rather than overwriting the value in the first array with the duplicate
184
     * value in the second array, as array_merge does. I.e., with array_merge_recursive,
185
     * this happens (documented behavior):.
186
     *
187
     * array_merge_recursive(array('key' => 'org value'), array('key' => 'new value'));
188
     *     => array('key' => array('org value', 'new value'));
189
     *
190
     * arrayMergeRecursiveDistinct does not change the datatypes of the values in the arrays.
191
     * Matching keys' values in the second array overwrite those in the first array, as is the
192
     * case with array_merge, i.e.:
193
     *
194
     * arrayMergeRecursiveDistinct(array('key' => 'org value'), array('key' => 'new value'));
195
     *     => array('key' => array('new value'));
196
     *
197
     * Parameters are passed by reference, though only for performance reasons. They're not
198
     * altered by this function.
199
     *
200
     * If key is integer, it will be merged like array_merge do:
201
     * arrayMergeRecursiveDistinct(array(0 => 'org value'), array(0 => 'new value'));
202
     *     => array(0 => 'org value', 1 => 'new value');
203
     *
204
     * @param array $array1
205
     * @param array $array2
206
     *
207
     * @return array
208
     *
209
     * @author Daniel <daniel (at) danielsmedegaardbuus (dot) dk>
210
     * @author Gabriel Sobrinho <gabriel (dot) sobrinho (at) gmail (dot) com>
211
     * @author Anton Medvedev <anton (at) elfet (dot) ru>
212
     * @author Aymen Fnayou <fnayou (dot) aymen (at) gmail (dot) com>
213
     */
214
    protected function arrayMergeRecursiveDistinct(array &$array1, array &$array2)
215
    {
216
        $result = $array1;
217
218
        foreach ($array2 as $key => &$value) {
219
            if (true === \is_array($value) && true === isset($result[$key]) && true === \is_array($result[$key])) {
220
                if (true === \is_int($key)) {
221
                    $result[] = $this->arrayMergeRecursiveDistinct($result[$key], $value);
222
223
                    continue;
224
                }
225
226
                $result[$key] = $this->arrayMergeRecursiveDistinct($result[$key], $value);
227
228
                continue;
229
            }
230
231
            if (true === \is_int($key)) {
232
                $result[] = $value;
233
234
                continue;
235
            }
236
237
            $result[$key] = $value;
238
        }
239
240
        return $result;
241
    }
242
243
    /**
244
     * @param array       $array
245
     * @param string|null $parent
246
     *
247
     * @return array
248
     */
249
    protected function arrayFlattenRecursive(array $array, string $parent = null)
250
    {
251
        $result = [];
252
        foreach ($array as $key => $value) {
253
            if (true === \is_array($value)) {
254
                $result = \array_merge($result, $this->arrayFlattenRecursive($value, $key));
255
256
                continue;
257
            }
258
259
            if (null === $parent) {
260
                $result[$key] = $value;
261
262
                continue;
263
            }
264
265
            $result[$parent.'.'.$key] = $value;
266
        }
267
268
        return $result;
269
    }
270
}
271