Dot::remove()   B
last analyzed

Complexity

Conditions 7
Paths 6

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 17
rs 8.2222
cc 7
eloc 12
nc 6
nop 2
1
<?php
2
namespace nochso\Omni;
3
4
/**
5
 * Dot allows easy access to multi-dimensional arrays using dot notation.
6
 */
7
final class Dot {
8
	/**
9
	 * Get the value of the element at the given dot key path.
10
	 *
11
	 * @param array      $array   Multi-dimensional array to access.
12
	 * @param string     $path    Dot-notation key path. e.g. `parent.child`
13
	 * @param null|mixed $default Default value to return
14
	 *
15
	 * @return mixed
16
	 */
17
	public static function get(array $array, $path, $default = null) {
18
		$getter = function ($arrayCarry, $key) use ($default) {
19
			if (!is_array($arrayCarry) || !isset($arrayCarry[$key])) {
20
				return $default;
21
			}
22
			return $arrayCarry[$key];
23
		};
24
		$keys = self::extractKeys($path);
25
		return array_reduce($keys, $getter, $array);
26
	}
27
28
	/**
29
	 * Has returns true if an element exists at the given dot key path.
30
	 *
31
	 * @param array  $array Multi-dimensionsal array to search.
32
	 * @param string $path  Dot-notation key path to search.
33
	 *
34
	 * @return bool
35
	 */
36
	public static function has(array $array, $path) {
37
		$unique = new \stdClass();
38
		$value = self::get($array, $path, $unique);
39
		return $value !== $unique;
40
	}
41
42
	/**
43
	 * Set a value at a certain path by creating missing elements and overwriting non-array values.
44
	 *
45
	 * If any of the visited elements is not an array, it will be replaced with an array.
46
	 *
47
	 * This will overwrite existing non-array values.
48
	 *
49
	 * @param array  $array
50
	 * @param string $path
51
	 * @param mixed  $value
52
	 */
53
	public static function set(array &$array, $path, $value) {
54
		self::setHelper($array, $path, $value, false);
55
	}
56
57
	/**
58
	 * trySet sets a value at a certain path, expecting arrays or missing elements along the way.
59
	 *
60
	 * If any of the visited elements is not an array, a \RuntimeException is thrown.
61
	 *
62
	 * Use this if you want to avoid overwriting existing non-array values.
63
	 *
64
	 * @param array  $array
65
	 * @param string $path
66
	 * @param mixed  $value
67
	 *
68
	 * @throws \RuntimeException
69
	 */
70
	public static function trySet(array &$array, $path, $value) {
71
		self::setHelper($array, $path, $value, true);
72
	}
73
74
	/**
75
	 * Remove an element if it exists.
76
	 *
77
	 * @param array  $array
78
	 * @param string $path
79
	 */
80
	public static function remove(array &$array, $path) {
81
		$keys = self::extractKeys($path);
82
		$keysWithoutLast = array_slice($keys, 0, -1);
83
		$keyCount = count($keysWithoutLast);
84
		$lastKey = $keys[$keyCount];
85
		$node =& $array;
86
		// Abort when key is missing earlier than expected
87
		for ($i = 0; $i < $keyCount && is_array($node) && isset($node[$keys[$i]]); ++$i) {
88
			$node =& $node[$keys[$i]];
89
		}
90
		if ($i < $keyCount) {
91
			return;
92
		}
93
		if (is_array($node) && isset($node[$lastKey])) {
94
			unset($node[$lastKey]);
95
		}
96
	}
97
98
	/**
99
	 * Flatten the array into a single dimension array with dot paths as keys.
100
	 *
101
	 * @param array       $array
102
	 * @param null|string $parent
103
	 *
104
	 * @return array
105
	 */
106
	public static function flatten(array $array, $parent = null) {
107
		$flat = [];
108
		foreach ($array as $key => $value) {
109
			$keypath = self::escapeKey($key);
110
			if ($parent !== null) {
111
				$keypath = $parent . '.' . $keypath;
112
			}
113
			if (is_array($value)) {
114
				$flat = array_merge(self::flatten($value, $keypath), $flat);
115
			} else {
116
				$flat[$keypath] = $value;
117
			}
118
		}
119
		return $flat;
120
	}
121
122
	/**
123
	 * escapeKey escapes an individual part of a key.
124
	 *
125
	 * @param string $key
126
	 *
127
	 * @return string
128
	 */
129
	private static function escapeKey($key) {
130
		$re = '/([\\.\\\\])/';
131
		$subst = '\\\$1';
132
		return preg_replace($re, $subst, $key);
133
	}
134
135
	/**
136
	 * @param string $path
137
	 *
138
	 * @return array
139
	 */
140
	private static function extractKeys($path) {
141
		$keys = [];
142
		if (!preg_match_all('/(?:\\\\.|[^\\.\\\\]++)+/', $path, $matches)) {
143
			return [$path];
144
		}
145
		foreach ($matches[0] as $match) {
146
			$keys[] = str_replace(['\.', '\\\\'], ['.', '\\'], $match);
147
		}
148
		return $keys;
149
	}
150
151
	private static function setHelper(array &$array, $path, $value, $strict = true) {
152
		$keys = self::extractKeys($path);
153
		$lastKey = $keys[count($keys) - 1];
154
		$keysWithoutLast = array_slice($keys, 0, -1);
155
		$node =& $array;
156
		foreach ($keysWithoutLast as $key) {
157
			self::prepareNode($node, $key, $path, $strict);
158
			$node =& $node[$key];
159
		}
160
		$node[$lastKey] = $value;
161
	}
162
163
	private static function prepareNode(array &$node, $key, $path, $strict) {
164
		if (!isset($node[$key]) || !$strict && !is_array($node[$key])) {
165
			$node[$key] = [];
166
		}
167
		if ($strict && !is_array($node[$key])) {
168
			throw new \RuntimeException(
169
				sprintf(
170
					"Can not set value at path '%s' because the element at key '%s' is not an array. Found value of type '%s' instead.",
171
					$path,
172
					$key,
173
					gettype($node[$key])
174
				)
175
			);
176
		}
177
	}
178
}
179