Mercurial > hg > isophonics-drupal-site
comparison core/lib/Drupal/Component/Utility/NestedArray.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\Component\Utility; | |
4 | |
5 /** | |
6 * Provides helpers to perform operations on nested arrays and array keys of variable depth. | |
7 * | |
8 * @ingroup utility | |
9 */ | |
10 class NestedArray { | |
11 | |
12 /** | |
13 * Retrieves a value from a nested array with variable depth. | |
14 * | |
15 * This helper function should be used when the depth of the array element | |
16 * being retrieved may vary (that is, the number of parent keys is variable). | |
17 * It is primarily used for form structures and renderable arrays. | |
18 * | |
19 * Without this helper function the only way to get a nested array value with | |
20 * variable depth in one line would be using eval(), which should be avoided: | |
21 * @code | |
22 * // Do not do this! Avoid eval(). | |
23 * // May also throw a PHP notice, if the variable array keys do not exist. | |
24 * eval('$value = $array[\'' . implode("']['", $parents) . "'];"); | |
25 * @endcode | |
26 * | |
27 * Instead, use this helper function: | |
28 * @code | |
29 * $value = NestedArray::getValue($form, $parents); | |
30 * @endcode | |
31 * | |
32 * A return value of NULL is ambiguous, and can mean either that the requested | |
33 * key does not exist, or that the actual value is NULL. If it is required to | |
34 * know whether the nested array key actually exists, pass a third argument | |
35 * that is altered by reference: | |
36 * @code | |
37 * $key_exists = NULL; | |
38 * $value = NestedArray::getValue($form, $parents, $key_exists); | |
39 * if ($key_exists) { | |
40 * // Do something with $value. | |
41 * } | |
42 * @endcode | |
43 * | |
44 * However if the number of array parent keys is static, the value should | |
45 * always be retrieved directly rather than calling this function. | |
46 * For instance: | |
47 * @code | |
48 * $value = $form['signature_settings']['signature']; | |
49 * @endcode | |
50 * | |
51 * @param array $array | |
52 * The array from which to get the value. | |
53 * @param array $parents | |
54 * An array of parent keys of the value, starting with the outermost key. | |
55 * @param bool $key_exists | |
56 * (optional) If given, an already defined variable that is altered by | |
57 * reference. | |
58 * | |
59 * @return mixed | |
60 * The requested nested value. Possibly NULL if the value is NULL or not all | |
61 * nested parent keys exist. $key_exists is altered by reference and is a | |
62 * Boolean that indicates whether all nested parent keys exist (TRUE) or not | |
63 * (FALSE). This allows to distinguish between the two possibilities when | |
64 * NULL is returned. | |
65 * | |
66 * @see NestedArray::setValue() | |
67 * @see NestedArray::unsetValue() | |
68 */ | |
69 public static function &getValue(array &$array, array $parents, &$key_exists = NULL) { | |
70 $ref = &$array; | |
71 foreach ($parents as $parent) { | |
72 if (is_array($ref) && array_key_exists($parent, $ref)) { | |
73 $ref = &$ref[$parent]; | |
74 } | |
75 else { | |
76 $key_exists = FALSE; | |
77 $null = NULL; | |
78 return $null; | |
79 } | |
80 } | |
81 $key_exists = TRUE; | |
82 return $ref; | |
83 } | |
84 | |
85 /** | |
86 * Sets a value in a nested array with variable depth. | |
87 * | |
88 * This helper function should be used when the depth of the array element you | |
89 * are changing may vary (that is, the number of parent keys is variable). It | |
90 * is primarily used for form structures and renderable arrays. | |
91 * | |
92 * Example: | |
93 * @code | |
94 * // Assume you have a 'signature' element somewhere in a form. It might be: | |
95 * $form['signature_settings']['signature'] = array( | |
96 * '#type' => 'text_format', | |
97 * '#title' => t('Signature'), | |
98 * ); | |
99 * // Or, it might be further nested: | |
100 * $form['signature_settings']['user']['signature'] = array( | |
101 * '#type' => 'text_format', | |
102 * '#title' => t('Signature'), | |
103 * ); | |
104 * @endcode | |
105 * | |
106 * To deal with the situation, the code needs to figure out the route to the | |
107 * element, given an array of parents that is either | |
108 * @code array('signature_settings', 'signature') @endcode | |
109 * in the first case or | |
110 * @code array('signature_settings', 'user', 'signature') @endcode | |
111 * in the second case. | |
112 * | |
113 * Without this helper function the only way to set the signature element in | |
114 * one line would be using eval(), which should be avoided: | |
115 * @code | |
116 * // Do not do this! Avoid eval(). | |
117 * eval('$form[\'' . implode("']['", $parents) . '\'] = $element;'); | |
118 * @endcode | |
119 * | |
120 * Instead, use this helper function: | |
121 * @code | |
122 * NestedArray::setValue($form, $parents, $element); | |
123 * @endcode | |
124 * | |
125 * However if the number of array parent keys is static, the value should | |
126 * always be set directly rather than calling this function. For instance, | |
127 * for the first example we could just do: | |
128 * @code | |
129 * $form['signature_settings']['signature'] = $element; | |
130 * @endcode | |
131 * | |
132 * @param array $array | |
133 * A reference to the array to modify. | |
134 * @param array $parents | |
135 * An array of parent keys, starting with the outermost key. | |
136 * @param mixed $value | |
137 * The value to set. | |
138 * @param bool $force | |
139 * (optional) If TRUE, the value is forced into the structure even if it | |
140 * requires the deletion of an already existing non-array parent value. If | |
141 * FALSE, PHP throws an error if trying to add into a value that is not an | |
142 * array. Defaults to FALSE. | |
143 * | |
144 * @see NestedArray::unsetValue() | |
145 * @see NestedArray::getValue() | |
146 */ | |
147 public static function setValue(array &$array, array $parents, $value, $force = FALSE) { | |
148 $ref = &$array; | |
149 foreach ($parents as $parent) { | |
150 // PHP auto-creates container arrays and NULL entries without error if $ref | |
151 // is NULL, but throws an error if $ref is set, but not an array. | |
152 if ($force && isset($ref) && !is_array($ref)) { | |
153 $ref = []; | |
154 } | |
155 $ref = &$ref[$parent]; | |
156 } | |
157 $ref = $value; | |
158 } | |
159 | |
160 /** | |
161 * Unsets a value in a nested array with variable depth. | |
162 * | |
163 * This helper function should be used when the depth of the array element you | |
164 * are changing may vary (that is, the number of parent keys is variable). It | |
165 * is primarily used for form structures and renderable arrays. | |
166 * | |
167 * Example: | |
168 * @code | |
169 * // Assume you have a 'signature' element somewhere in a form. It might be: | |
170 * $form['signature_settings']['signature'] = array( | |
171 * '#type' => 'text_format', | |
172 * '#title' => t('Signature'), | |
173 * ); | |
174 * // Or, it might be further nested: | |
175 * $form['signature_settings']['user']['signature'] = array( | |
176 * '#type' => 'text_format', | |
177 * '#title' => t('Signature'), | |
178 * ); | |
179 * @endcode | |
180 * | |
181 * To deal with the situation, the code needs to figure out the route to the | |
182 * element, given an array of parents that is either | |
183 * @code array('signature_settings', 'signature') @endcode | |
184 * in the first case or | |
185 * @code array('signature_settings', 'user', 'signature') @endcode | |
186 * in the second case. | |
187 * | |
188 * Without this helper function the only way to unset the signature element in | |
189 * one line would be using eval(), which should be avoided: | |
190 * @code | |
191 * // Do not do this! Avoid eval(). | |
192 * eval('unset($form[\'' . implode("']['", $parents) . '\']);'); | |
193 * @endcode | |
194 * | |
195 * Instead, use this helper function: | |
196 * @code | |
197 * NestedArray::unset_nested_value($form, $parents, $element); | |
198 * @endcode | |
199 * | |
200 * However if the number of array parent keys is static, the value should | |
201 * always be set directly rather than calling this function. For instance, for | |
202 * the first example we could just do: | |
203 * @code | |
204 * unset($form['signature_settings']['signature']); | |
205 * @endcode | |
206 * | |
207 * @param array $array | |
208 * A reference to the array to modify. | |
209 * @param array $parents | |
210 * An array of parent keys, starting with the outermost key and including | |
211 * the key to be unset. | |
212 * @param bool $key_existed | |
213 * (optional) If given, an already defined variable that is altered by | |
214 * reference. | |
215 * | |
216 * @see NestedArray::setValue() | |
217 * @see NestedArray::getValue() | |
218 */ | |
219 public static function unsetValue(array &$array, array $parents, &$key_existed = NULL) { | |
220 $unset_key = array_pop($parents); | |
221 $ref = &self::getValue($array, $parents, $key_existed); | |
222 if ($key_existed && is_array($ref) && array_key_exists($unset_key, $ref)) { | |
223 $key_existed = TRUE; | |
224 unset($ref[$unset_key]); | |
225 } | |
226 else { | |
227 $key_existed = FALSE; | |
228 } | |
229 } | |
230 | |
231 /** | |
232 * Determines whether a nested array contains the requested keys. | |
233 * | |
234 * This helper function should be used when the depth of the array element to | |
235 * be checked may vary (that is, the number of parent keys is variable). See | |
236 * NestedArray::setValue() for details. It is primarily used for form | |
237 * structures and renderable arrays. | |
238 * | |
239 * If it is required to also get the value of the checked nested key, use | |
240 * NestedArray::getValue() instead. | |
241 * | |
242 * If the number of array parent keys is static, this helper function is | |
243 * unnecessary and the following code can be used instead: | |
244 * @code | |
245 * $value_exists = isset($form['signature_settings']['signature']); | |
246 * $key_exists = array_key_exists('signature', $form['signature_settings']); | |
247 * @endcode | |
248 * | |
249 * @param array $array | |
250 * The array with the value to check for. | |
251 * @param array $parents | |
252 * An array of parent keys of the value, starting with the outermost key. | |
253 * | |
254 * @return bool | |
255 * TRUE if all the parent keys exist, FALSE otherwise. | |
256 * | |
257 * @see NestedArray::getValue() | |
258 */ | |
259 public static function keyExists(array $array, array $parents) { | |
260 // Although this function is similar to PHP's array_key_exists(), its | |
261 // arguments should be consistent with getValue(). | |
262 $key_exists = NULL; | |
263 self::getValue($array, $parents, $key_exists); | |
264 return $key_exists; | |
265 } | |
266 | |
267 /** | |
268 * Merges multiple arrays, recursively, and returns the merged array. | |
269 * | |
270 * This function is similar to PHP's array_merge_recursive() function, but it | |
271 * handles non-array values differently. When merging values that are not both | |
272 * arrays, the latter value replaces the former rather than merging with it. | |
273 * | |
274 * Example: | |
275 * @code | |
276 * $link_options_1 = array('fragment' => 'x', 'attributes' => array('title' => t('X'), 'class' => array('a', 'b'))); | |
277 * $link_options_2 = array('fragment' => 'y', 'attributes' => array('title' => t('Y'), 'class' => array('c', 'd'))); | |
278 * | |
279 * // This results in array('fragment' => array('x', 'y'), 'attributes' => array('title' => array(t('X'), t('Y')), 'class' => array('a', 'b', 'c', 'd'))). | |
280 * $incorrect = array_merge_recursive($link_options_1, $link_options_2); | |
281 * | |
282 * // This results in array('fragment' => 'y', 'attributes' => array('title' => t('Y'), 'class' => array('a', 'b', 'c', 'd'))). | |
283 * $correct = NestedArray::mergeDeep($link_options_1, $link_options_2); | |
284 * @endcode | |
285 * | |
286 * @param array ... | |
287 * Arrays to merge. | |
288 * | |
289 * @return array | |
290 * The merged array. | |
291 * | |
292 * @see NestedArray::mergeDeepArray() | |
293 */ | |
294 public static function mergeDeep() { | |
295 return self::mergeDeepArray(func_get_args()); | |
296 } | |
297 | |
298 /** | |
299 * Merges multiple arrays, recursively, and returns the merged array. | |
300 * | |
301 * This function is equivalent to NestedArray::mergeDeep(), except the | |
302 * input arrays are passed as a single array parameter rather than a variable | |
303 * parameter list. | |
304 * | |
305 * The following are equivalent: | |
306 * - NestedArray::mergeDeep($a, $b); | |
307 * - NestedArray::mergeDeepArray(array($a, $b)); | |
308 * | |
309 * The following are also equivalent: | |
310 * - call_user_func_array('NestedArray::mergeDeep', $arrays_to_merge); | |
311 * - NestedArray::mergeDeepArray($arrays_to_merge); | |
312 * | |
313 * @param array $arrays | |
314 * An arrays of arrays to merge. | |
315 * @param bool $preserve_integer_keys | |
316 * (optional) If given, integer keys will be preserved and merged instead of | |
317 * appended. Defaults to FALSE. | |
318 * | |
319 * @return array | |
320 * The merged array. | |
321 * | |
322 * @see NestedArray::mergeDeep() | |
323 */ | |
324 public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FALSE) { | |
325 $result = []; | |
326 foreach ($arrays as $array) { | |
327 foreach ($array as $key => $value) { | |
328 // Renumber integer keys as array_merge_recursive() does unless | |
329 // $preserve_integer_keys is set to TRUE. Note that PHP automatically | |
330 // converts array keys that are integer strings (e.g., '1') to integers. | |
331 if (is_int($key) && !$preserve_integer_keys) { | |
332 $result[] = $value; | |
333 } | |
334 // Recurse when both values are arrays. | |
335 elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) { | |
336 $result[$key] = self::mergeDeepArray([$result[$key], $value], $preserve_integer_keys); | |
337 } | |
338 // Otherwise, use the latter value, overriding any previous value. | |
339 else { | |
340 $result[$key] = $value; | |
341 } | |
342 } | |
343 } | |
344 return $result; | |
345 } | |
346 | |
347 /** | |
348 * Filters a nested array recursively. | |
349 * | |
350 * @param array $array | |
351 * The filtered nested array. | |
352 * @param callable|null $callable | |
353 * The callable to apply for filtering. | |
354 * | |
355 * @return array | |
356 * The filtered array. | |
357 */ | |
358 public static function filter(array $array, callable $callable = NULL) { | |
359 $array = is_callable($callable) ? array_filter($array, $callable) : array_filter($array); | |
360 foreach ($array as &$element) { | |
361 if (is_array($element)) { | |
362 $element = static::filter($element, $callable); | |
363 } | |
364 } | |
365 | |
366 return $array; | |
367 } | |
368 | |
369 } |