3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * Provides a collection of static methods for array manipulation.
32 * @copyright CiviCRM LLC (c) 2004-2018
34 class CRM_Utils_Array
{
37 * Returns $list[$key] if such element exists, or a default value otherwise.
39 * If $list is not actually an array at all, then the default value is
44 * Key value to look up in the array.
46 * Array from which to look up a value.
47 * @param mixed $default
48 * (optional) Value to return $list[$key] does not exist.
51 * Can return any type, since $list might contain anything.
53 public static function value($key, $list, $default = NULL) {
54 if (is_array($list)) {
55 return array_key_exists($key, $list) ?
$list[$key] : $default;
61 * Recursively searches an array for a key, returning the first value found.
63 * If $params[$key] does not exist and $params contains arrays, descend into
64 * each array in a depth-first manner, in array iteration order.
66 * @param array $params
67 * The array to be searched.
69 * The key to search for.
72 * The value of the key, or null if the key is not found.
74 public static function retrieveValueRecursive(&$params, $key) {
75 if (!is_array($params)) {
78 elseif ($value = CRM_Utils_Array
::value($key, $params)) {
82 foreach ($params as $subParam) {
83 if (is_array($subParam) &&
84 $value = self
::retrieveValueRecursive($subParam, $key)
94 * Wraps and slightly changes the behavior of PHP's array_search().
96 * This function reproduces the behavior of array_search() from PHP prior to
97 * version 4.2.0, which was to return NULL on failure. This function also
98 * checks that $list is an array before attempting to search it.
101 * @param mixed $value
102 * The value to search for.
104 * The array to be searched.
106 * @return int|string|null
107 * Returns the key, which could be an int or a string, or NULL on failure.
109 public static function key($value, $list) {
110 if (is_array($list)) {
111 $key = array_search($value, $list);
113 // array_search returns key if found, false otherwise
114 // it may return values like 0 or empty string which
115 // evaluates to false
116 // hence we must use identical comparison operator
117 return ($key === FALSE) ?
NULL : $key;
123 * Builds an XML fragment representing an array.
125 * Depending on the nature of the keys of the array (and its sub-arrays,
126 * if any) the XML fragment may not be valid.
129 * The array to be serialized.
131 * (optional) Indentation depth counter.
132 * @param string $seperator
133 * (optional) String to be appended after open/close tags.
136 * XML fragment representing $list.
138 public static function &xml(&$list, $depth = 1, $seperator = "\n") {
140 foreach ($list as $name => $value) {
141 $xml .= str_repeat(' ', $depth * 4);
142 if (is_array($value)) {
143 $xml .= "<{$name}>{$seperator}";
144 $xml .= self
::xml($value, $depth +
1, $seperator);
145 $xml .= str_repeat(' ', $depth * 4);
146 $xml .= "</{$name}>{$seperator}";
149 // make sure we escape value
150 $value = self
::escapeXML($value);
151 $xml .= "<{$name}>$value</{$name}>{$seperator}";
158 * Sanitizes a string for serialization in CRM_Utils_Array::xml().
160 * Replaces '&', '<', and '>' with their XML escape sequences. Replaces '^A'
163 * @param string $value
164 * String to be sanitized.
167 * Sanitized version of $value.
169 public static function escapeXML($value) {
174 $src = array('&', '<', '>', '\ 1');
175 $dst = array('&', '<', '>', ',');
178 return str_replace($src, $dst, $value);
182 * Converts a nested array to a flat array.
184 * The nested structure is preserved in the string values of the keys of the
187 * Example nested array:
212 * Corresponding flattened array:
218 * [asdf.merp] => bleep
219 * [asdf.quack.0] => 1
220 * [asdf.quack.1] => 2
221 * [asdf.quack.2] => 3
226 * Array to be flattened.
229 * @param string $prefix
230 * (optional) String to prepend to keys.
231 * @param string $seperator
232 * (optional) String that separates the concatenated keys.
234 public static function flatten(&$list, &$flat, $prefix = '', $seperator = ".") {
235 foreach ($list as $name => $value) {
236 $newPrefix = ($prefix) ?
$prefix . $seperator . $name : $name;
237 if (is_array($value)) {
238 self
::flatten($value, $flat, $newPrefix, $seperator);
241 if (!empty($value)) {
242 $flat[$newPrefix] = $value;
249 * Converts an array with path-like keys into a tree of arrays.
251 * This function is the inverse of CRM_Utils_Array::flatten().
253 * @param string $delim
256 * A one-dimensional array indexed by string keys
261 public function unflatten($delim, &$arr) {
263 foreach ($arr as $key => $value) {
264 $path = explode($delim, $key);
266 while (count($path) > 1) {
267 $key = array_shift($path);
268 if (!isset($node[$key])) {
269 $node[$key] = array();
271 $node = &$node[$key];
274 $key = array_shift($path);
275 $node[$key] = $value;
283 * If $a1[foo] and $a2[foo] both exist and are both arrays, the merge
284 * process recurses into those sub-arrays. If $a1[foo] and $a2[foo] both
285 * exist but they are not both arrays, the value from $a1 overrides the
286 * value from $a2 and the value from $a2 is discarded.
289 * First array to be merged.
291 * Second array to be merged.
296 public static function crmArrayMerge($a1, $a2) {
306 foreach ($a1 as $key => $value) {
307 if (array_key_exists($key, $a2) &&
308 is_array($a2[$key]) && is_array($a1[$key])
310 $a3[$key] = array_merge($a1[$key], $a2[$key]);
313 $a3[$key] = $a1[$key];
317 foreach ($a2 as $key => $value) {
318 if (array_key_exists($key, $a1)) {
319 // already handled in above loop
322 $a3[$key] = $a2[$key];
329 * Determines whether an array contains any sub-arrays.
332 * The array to inspect.
335 * True if $list contains at least one sub-array, false otherwise.
337 public static function isHierarchical(&$list) {
338 foreach ($list as $n => $v) {
347 * Is array A a subset of array B.
349 * @param array $subset
350 * @param array $superset
353 * TRUE if $subset is a subset of $superset
355 public static function isSubset($subset, $superset) {
356 foreach ($subset as $expected) {
357 if (!in_array($expected, $superset)) {
365 * Searches an array recursively in an optionally case-insensitive manner.
367 * @param string $value
368 * Value to search for.
369 * @param array $params
370 * Array to search within.
371 * @param bool $caseInsensitive
372 * (optional) Whether to search in a case-insensitive manner.
375 * True if $value was found, false otherwise.
377 public static function crmInArray($value, $params, $caseInsensitive = TRUE) {
378 foreach ($params as $item) {
379 if (is_array($item)) {
380 $ret = crmInArray($value, $item, $caseInsensitive);
383 $ret = ($caseInsensitive) ?
strtolower($item) == strtolower($value) : $item == $value;
393 * Convert associative array names to values and vice-versa.
395 * This function is used by by import functions and some webforms.
397 * @param array $defaults
398 * @param string $property
404 public static function lookupValue(&$defaults, $property, $lookup, $reverse) {
405 $id = $property . '_id';
407 $src = $reverse ?
$property : $id;
408 $dst = $reverse ?
$id : $property;
410 if (!array_key_exists(strtolower($src), array_change_key_case($defaults, CASE_LOWER
))) {
414 $look = $reverse ?
array_flip($lookup) : $lookup;
416 // trim lookup array, ignore . ( fix for CRM-1514 ), eg for prefix/suffix make sure Dr. and Dr both are valid
418 foreach ($look as $k => $v) {
419 $newLook[trim($k, ".")] = $v;
424 if (is_array($look)) {
425 if (!array_key_exists(trim(strtolower($defaults[strtolower($src)]), '.'), array_change_key_case($look, CASE_LOWER
))) {
430 $tempLook = array_change_key_case($look, CASE_LOWER
);
432 $defaults[$dst] = $tempLook[trim(strtolower($defaults[strtolower($src)]), '.')];
437 * Checks whether an array is empty.
439 * An array is empty if its values consist only of NULL and empty sub-arrays.
440 * Containing a non-NULL value or non-empty array makes an array non-empty.
442 * If something other than an array is passed, it is considered to be empty.
444 * If nothing is passed at all, the default value provided is empty.
446 * @param array $array
447 * (optional) Array to be checked for emptiness.
450 * True if the array is empty.
452 public static function crmIsEmptyArray($array = array()) {
453 if (!is_array($array)) {
456 foreach ($array as $element) {
457 if (is_array($element)) {
458 if (!self
::crmIsEmptyArray($element)) {
462 elseif (isset($element)) {
470 * Sorts an associative array of arrays by an attribute using strnatcmp().
472 * @param array $array
473 * Array to be sorted.
474 * @param string|array $field
475 * Name of the attribute used for sorting.
480 public static function crmArraySortByField($array, $field) {
481 $fields = (array) $field;
482 uasort($array, function ($a, $b) use ($fields) {
483 foreach ($fields as $f) {
484 $v = strnatcmp($a[$f], $b[$f]);
495 * Recursively removes duplicate values from a multi-dimensional array.
497 * @param array $array
498 * The input array possibly containing duplicate values.
501 * The input array with duplicate values removed.
503 public static function crmArrayUnique($array) {
504 $result = array_map("unserialize", array_unique(array_map("serialize", $array)));
505 foreach ($result as $key => $value) {
506 if (is_array($value)) {
507 $result[$key] = self
::crmArrayUnique($value);
514 * Sorts an array and maintains index association (with localization).
516 * Uses Collate from the PECL "intl" package, if available, for UTF-8
517 * sorting (e.g. list of countries). Otherwise calls PHP's asort().
519 * On Debian/Ubuntu: apt-get install php5-intl
521 * @param array $array
522 * (optional) Array to be sorted.
527 public static function asort($array = array()) {
528 $lcMessages = CRM_Utils_System
::getUFLocale();
530 if ($lcMessages && $lcMessages != 'en_US' && class_exists('Collator')) {
531 $collator = new Collator($lcMessages . '.utf8');
532 $collator->asort($array);
535 // This calls PHP's built-in asort().
543 * Unsets an arbitrary list of array elements from an associative array.
545 * @param array $items
546 * The array from which to remove items.
549 * When passed a string, unsets $items[$key].
550 * When passed an array of strings, unsets $items[$k] for each string $k in the array.
552 public static function remove(&$items) {
553 foreach (func_get_args() as $n => $key) {
554 // Skip argument 0 ($items) by testing $n for truth.
555 if ($n && is_array($key)) {
556 foreach ($key as $k) {
567 * Builds an array-tree which indexes the records in an array.
569 * @param string[] $keys
570 * Properties by which to index.
571 * @param object|array $records
574 * Multi-dimensional array, with one layer for each key.
576 public static function index($keys, $records) {
577 $final_key = array_pop($keys);
580 foreach ($records as $record) {
582 foreach ($keys as $key) {
583 if (is_array($record)) {
584 $keyvalue = isset($record[$key]) ?
$record[$key] : NULL;
587 $keyvalue = isset($record->{$key}) ?
$record->{$key} : NULL;
589 if (isset($node[$keyvalue]) && !is_array($node[$keyvalue])) {
590 $node[$keyvalue] = array();
592 $node = &$node[$keyvalue];
594 if (is_array($record)) {
595 $node[$record[$final_key]] = $record;
598 $node[$record->{$final_key}] = $record;
605 * Iterates over a list of records and returns the value of some property.
607 * @param string $prop
608 * Property to retrieve.
609 * @param array|object $records
613 * Keys are the original keys of $records; values are the $prop values.
615 public static function collect($prop, $records) {
617 if (is_array($records)) {
618 foreach ($records as $key => $record) {
619 if (is_object($record)) {
620 $result[$key] = $record->{$prop};
623 $result[$key] = self
::value($prop, $record);
631 * Iterates over a list of objects and executes some method on each.
634 * - This is like array_map(), except it executes the objects' method
635 * instead of a free-form callable.
636 * - This is like Array::collect(), except it uses a method
637 * instead of a property.
639 * @param string $method
640 * The method to execute.
641 * @param array|Traversable $objects
644 * An optional list of arguments to pass to the method.
647 * Keys are the original keys of $objects; values are the method results.
649 public static function collectMethod($method, $objects, $args = array()) {
651 if (is_array($objects)) {
652 foreach ($objects as $key => $object) {
653 $result[$key] = call_user_func_array(array($object, $method), $args);
660 * Trims delimiters from a string and then splits it using explode().
662 * This method works mostly like PHP's built-in explode(), except that
663 * surrounding delimiters are trimmed before explode() is called.
665 * Also, if an array or NULL is passed as the $values parameter, the value is
666 * returned unmodified rather than being passed to explode().
668 * @param array|null|string $values
669 * The input string (or an array, or NULL).
670 * @param string $delim
671 * (optional) The boundary string.
674 * An array of strings produced by explode(), or the unmodified input
677 public static function explodePadded($values, $delim = CRM_Core_DAO
::VALUE_SEPARATOR
) {
678 if ($values === NULL) {
681 // If we already have an array, no need to continue
682 if (is_array($values)) {
685 // Empty string -> empty array
686 if ($values === '') {
689 return explode($delim, trim((string) $values, $delim));
693 * Joins array elements with a string, adding surrounding delimiters.
695 * This method works mostly like PHP's built-in implode(), but the generated
696 * string is surrounded by delimiter characters. Also, if NULL is passed as
697 * the $values parameter, NULL is returned.
699 * @param mixed $values
700 * Array to be imploded. If a non-array is passed, it will be cast to an
702 * @param string $delim
703 * Delimiter to be used for implode() and which will surround the output
706 * @return string|NULL
707 * The generated string, or NULL if NULL was passed as $values parameter.
709 public static function implodePadded($values, $delim = CRM_Core_DAO
::VALUE_SEPARATOR
) {
710 if ($values === NULL) {
713 // If we already have a string, strip $delim off the ends so it doesn't get added twice
714 if (is_string($values)) {
715 $values = trim($values, $delim);
717 return $delim . implode($delim, (array) $values) . $delim;
721 * Modifies a key in an array while preserving the key order.
723 * By default when an element is added to an array, it is added to the end.
724 * This method allows for changing an existing key while preserving its
725 * position in the array.
727 * The array is both modified in-place and returned.
729 * @param array $elementArray
730 * Array to manipulate.
731 * @param string $oldKey
732 * Old key to be replaced.
733 * @param string $newKey
734 * Replacement key string.
737 * Throws a generic Exception if $oldKey is not found in $elementArray.
740 * The manipulated array.
742 public static function crmReplaceKey(&$elementArray, $oldKey, $newKey) {
743 $keys = array_keys($elementArray);
744 if (FALSE === $index = array_search($oldKey, $keys)) {
745 throw new Exception(sprintf('key "%s" does not exit', $oldKey));
747 $keys[$index] = $newKey;
748 $elementArray = array_combine($keys, array_values($elementArray));
749 return $elementArray;
753 * Searches array keys by regex, returning the value of the first match.
755 * Given a regular expression and an array, this method searches the keys
756 * of the array using the regular expression. The first match is then used
757 * to index into the array, and the associated value is retrieved and
758 * returned. If no matches are found, or if something other than an array
759 * is passed, then a default value is returned. Unless otherwise specified,
760 * the default value is NULL.
762 * @param string $regexKey
763 * The regular expression to use when searching for matching keys.
765 * The array whose keys will be searched.
766 * @param mixed $default
767 * (optional) The default value to return if the regex does not match an
768 * array key, or if something other than an array is passed.
773 public static function valueByRegexKey($regexKey, $list, $default = NULL) {
774 if (is_array($list) && $regexKey) {
775 $matches = preg_grep($regexKey, array_keys($list));
776 $key = reset($matches);
777 return ($key && array_key_exists($key, $list)) ?
$list[$key] : $default;
783 * Generates the Cartesian product of zero or more vectors.
785 * @param array $dimensions
786 * List of dimensions to multiply.
787 * Each key is a dimension name; each value is a vector.
788 * @param array $template
789 * (optional) A base set of values included in every output.
792 * Each item is a distinct combination of values from $dimensions.
794 * For example, the product of
797 * bg => {white, black}
801 * {fg => red, bg => white},
802 * {fg => red, bg => black},
803 * {fg => blue, bg => white},
804 * {fg => blue, bg => black}
807 public static function product($dimensions, $template = array()) {
808 if (empty($dimensions)) {
809 return array($template);
812 foreach ($dimensions as $key => $value) {
814 $firstValues = $value;
817 unset($dimensions[$key]);
820 foreach ($firstValues as $firstValue) {
821 foreach (self
::product($dimensions, $template) as $result) {
822 $result[$firstKey] = $firstValue;
823 $results[] = $result;
831 * Get the first element of an array.
833 * @param array $array
836 public static function first($array) {
837 foreach ($array as $value) {
844 * Extract any $keys from $array and copy to a new array.
846 * Note: If a $key does not appear in $array, then it will
847 * not appear in the result.
849 * @param array $array
851 * List of keys to copy.
854 public static function subset($array, $keys) {
856 foreach ($keys as $key) {
857 if (isset($array[$key])) {
858 $result[$key] = $array[$key];
865 * Transform an associative array of key=>value pairs into a non-associative array of arrays.
866 * This is necessary to preserve sort order when sending an array through json_encode.
868 * @param array $associative
869 * @param string $keyName
870 * @param string $valueName
873 public static function makeNonAssociative($associative, $keyName = 'key', $valueName = 'value') {
875 foreach ($associative as $key => $val) {
876 $output[] = array($keyName => $key, $valueName => $val);
882 * Diff multidimensional arrays
883 * (array_diff does not support multidimensional array)
885 * @param array $array1
886 * @param array $array2
889 public static function multiArrayDiff($array1, $array2) {
890 $arrayDiff = array();
891 foreach ($array1 as $mKey => $mValue) {
892 if (array_key_exists($mKey, $array2)) {
893 if (is_array($mValue)) {
894 $recursiveDiff = self
::multiArrayDiff($mValue, $array2[$mKey]);
895 if (count($recursiveDiff)) {
896 $arrayDiff[$mKey] = $recursiveDiff;
900 if ($mValue != $array2[$mKey]) {
901 $arrayDiff[$mKey] = $mValue;
906 $arrayDiff[$mKey] = $mValue;
913 * Given a 2-dimensional matrix, create a new matrix with a restricted list of columns.
915 * @param array $matrix
916 * All matrix data, as a list of rows.
917 * @param array $columns
918 * List of column names.
921 public static function filterColumns($matrix, $columns) {
923 foreach ($matrix as $pos => $oldRow) {
925 foreach ($columns as $column) {
926 $newRow[$column] = CRM_Utils_Array
::value($column, $oldRow);
928 $newRows[$pos] = $newRow;
934 * Rewrite the keys in an array.
936 * @param array $array
937 * @param string|callable $indexBy
938 * Either the value to key by, or a function($key, $value) that returns the new key.
941 public static function rekey($array, $indexBy) {
943 foreach ($array as $key => $value) {
944 $newKey = is_callable($indexBy) ?
$indexBy($key, $value) : $value[$indexBy];
945 $result[$newKey] = $value;
951 * Copy all properties of $other into $array (recursively).
953 * @param array|ArrayAccess $array
954 * @param array $other
956 public static function extend(&$array, $other) {
957 foreach ($other as $key => $value) {
958 if (is_array($value)) {
959 self
::extend($array[$key], $value);
962 $array[$key] = $value;
968 * Get a single value from an array-tre.
971 * Ex: array('foo'=>array('bar'=>123)).
972 * @param array $pathParts
973 * Ex: array('foo',bar').
977 public static function pathGet($arr, $pathParts) {
979 foreach ($pathParts as $part) {
980 if (!isset($r[$part])) {
989 * Set a single value in an array tree.
992 * Ex: array('foo'=>array('bar'=>123)).
993 * @param array $pathParts
994 * Ex: array('foo',bar').
998 public static function pathSet(&$arr, $pathParts, $value) {
1000 $last = array_pop($pathParts);
1001 foreach ($pathParts as $part) {
1002 if (!isset($r[$part])) {
1003 $r[$part] = array();
1011 * Convert a simple dictionary into separate key+value records.
1013 * @param array $array
1014 * Ex: array('foo' => 'bar').
1015 * @param string $keyField
1017 * @param string $valueField
1021 * 0 => array('key' => 'foo', 'value' => 'bar')
1024 public static function toKeyValueRows($array, $keyField = 'key', $valueField = 'value') {
1026 foreach ($array as $key => $value) {
1029 $valueField => $value,
1036 * Convert array where key(s) holds the actual value and value(s) as 1 into array of actual values
1037 * Ex: array('foobar' => 1, 4 => 1) formatted into array('foobar', 4)
1039 * @deprecated use convertCheckboxInputToArray instead (after testing)
1040 * https://github.com/civicrm/civicrm-core/pull/8169
1042 * @param array $array
1044 public static function formatArrayKeys(&$array) {
1045 if (!is_array($array)) {
1048 $keys = array_keys($array, 1);
1049 if (count($keys) > 1 ||
1050 (count($keys) == 1 &&
1051 (current($keys) > 1 ||
1052 is_string(current($keys)) ||
1053 (current($keys) == 1 && $array[1] == 1) // handle (0 => 4), (1 => 1)
1062 * Convert the data format coming in from checkboxes to an array of values.
1064 * The input format from check boxes looks like
1065 * array('value1' => 1, 'value2' => 1). This function converts those values to
1066 * array(''value1', 'value2).
1068 * The function will only alter the array if all values are equal to 1.
1070 * @param array $input
1074 public static function convertCheckboxFormatToArray($input) {
1075 if (isset($input[0])) {
1078 $keys = array_keys($input, 1);
1079 if ((count($keys) == count($input))) {
1086 * Ensure that array is encoded in utf8 format.
1088 * @param array $array
1090 * @return array $array utf8-encoded.
1092 public static function encode_items($array) {
1093 foreach ($array as $key => $value) {
1094 if (is_array($value)) {
1095 $array[$key] = self
::encode_items($value);
1097 elseif (is_string($value)) {
1098 $array[$key] = mb_convert_encoding($value, mb_detect_encoding($value, mb_detect_order(), TRUE), 'UTF-8');
1101 $array[$key] = $value;
1108 * Build tree of elements.
1110 * @param array $elements
1111 * @param int|null $parentId
1115 public static function buildTree($elements, $parentId = NULL) {
1118 foreach ($elements as $element) {
1119 if ($element['parent_id'] == $parentId) {
1120 $children = self
::buildTree($elements, $element['id']);
1122 $element['children'] = $children;
1124 $branch[] = $element;
1132 * Find search string in tree.
1134 * @param string $search
1135 * @param array $tree
1136 * @param string $field
1138 * @return array|null
1140 public static function findInTree($search, $tree, $field = 'id') {
1141 foreach ($tree as $item) {
1142 if ($item[$field] == $search) {
1145 if (!empty($item['children'])) {
1146 $found = self
::findInTree($search, $item['children']);
1156 * Check if a key isset which may be several layers deep.
1158 * This is a helper for when the calling function does not know how many layers deep the
1159 * path array is so cannot easily check.
1161 * @param array $array
1162 * @param array $path
1164 * @throws \CRM_Core_Exception
1166 public static function recursiveIsset($array, $path) {
1167 foreach ($path as $key) {
1168 if (!is_array($array) ||
!isset($array[$key])) {
1171 $array = $array[$key];
1177 * Check if a key isset which may be several layers deep.
1179 * This is a helper for when the calling function does not know how many layers deep the
1180 * path array is so cannot easily check.
1182 * @param array $array
1183 * @param array $path
1184 * An array of keys - e.g [0, 'bob', 8] where we want to check if $array[0]['bob'][8]
1185 * @param mixed $default
1186 * Value to return if not found.
1188 * @throws \CRM_Core_Exception
1190 public static function recursiveValue($array, $path, $default = NULL) {
1191 foreach ($path as $key) {
1192 if (!is_array($array) ||
!isset($array[$key])) {
1195 $array = $array[$key];
1201 * Append the value to the array using the key provided.
1203 * e.g if value is 'llama' & path is [0, 'email', 'location'] result will be
1204 * [0 => ['email' => ['location' => 'llama']]
1208 * @param array $source
1212 public static function recursiveBuild($path, $value, $source = []) {
1213 $arrayKey = array_shift($path);
1214 // Recurse through array keys
1216 if (!isset($source[$arrayKey])) {
1217 $source[$arrayKey] = [];
1219 $source[$arrayKey] = self
::recursiveBuild($path, $value, $source[$arrayKey]);
1223 $source[$arrayKey] = $value;