diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index b667d89..f609de8 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -6,6 +6,7 @@ use Symfony\Component\ClassLoader\ApcUniversalClassLoader; use Drupal\Core\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpFoundation\Request; use Drupal\Core\Language\Language; +use Drupal\Core\Utility\NestedArray; /** * @file @@ -2089,28 +2090,7 @@ function drupal_array_merge_deep() { * @see drupal_array_merge_deep() */ function drupal_array_merge_deep_array($arrays) { - $result = array(); - - foreach ($arrays as $array) { - foreach ($array as $key => $value) { - // Renumber integer keys as array_merge_recursive() does. Note that PHP - // automatically converts array keys that are integer strings (e.g., '1') - // to integers. - if (is_integer($key)) { - $result[] = $value; - } - // Recurse when both values are arrays. - elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) { - $result[$key] = drupal_array_merge_deep_array(array($result[$key], $value)); - } - // Otherwise, use the latter value, overriding any previous value. - else { - $result[$key] = $value; - } - } - } - - return $result; + return NestedArray::mergeDeepArray($arrays); } /** diff --git a/core/includes/common.inc b/core/includes/common.inc index f2a4003..ee9620b 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -3,6 +3,7 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Drupal\Core\Database\Database; +use Drupal\Core\Utility\NestedArray; /** * @file @@ -6373,19 +6374,7 @@ function element_set_attributes(array &$element, array $map) { * @see drupal_array_unset_nested_value() */ function &drupal_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) { - $ref = &$array; - foreach ($parents as $parent) { - if (is_array($ref) && array_key_exists($parent, $ref)) { - $ref = &$ref[$parent]; - } - else { - $key_exists = FALSE; - $null = NULL; - return $null; - } - } - $key_exists = TRUE; - return $ref; + return NestedArray::getValue($array, $parents, $key_exists); } /** @@ -6450,16 +6439,7 @@ function &drupal_array_get_nested_value(array &$array, array $parents, &$key_exi * @see drupal_array_get_nested_value() */ function drupal_array_set_nested_value(array &$array, array $parents, $value, $force = FALSE) { - $ref = &$array; - foreach ($parents as $parent) { - // PHP auto-creates container arrays and NULL entries without error if $ref - // is NULL, but throws an error if $ref is set, but not an array. - if ($force && isset($ref) && !is_array($ref)) { - $ref = array(); - } - $ref = &$ref[$parent]; - } - $ref = $value; + NestedArray::setValue($array, $parents, $value, $force); } /** @@ -6521,15 +6501,7 @@ function drupal_array_set_nested_value(array &$array, array $parents, $value, $f * @see drupal_array_get_nested_value() */ function drupal_array_unset_nested_value(array &$array, array $parents, &$key_existed = NULL) { - $unset_key = array_pop($parents); - $ref = &drupal_array_get_nested_value($array, $parents, $key_existed); - if ($key_existed && is_array($ref) && array_key_exists($unset_key, $ref)) { - $key_existed = TRUE; - unset($ref[$unset_key]); - } - else { - $key_existed = FALSE; - } + NestedArray::unsetValue($array, $parents, $key_existed); } /** @@ -6561,11 +6533,7 @@ function drupal_array_unset_nested_value(array &$array, array $parents, &$key_ex * @see drupal_array_get_nested_value() */ function drupal_array_nested_key_exists(array $array, array $parents) { - // Although this function is similar to PHP's array_key_exists(), its - // arguments should be consistent with drupal_array_get_nested_value(). - $key_exists = NULL; - drupal_array_get_nested_value($array, $parents, $key_exists); - return $key_exists; + return NestedArray::keyExists($array, $parents); } /** diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index a749a4b..1cd8209 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Config; +use Drupal\Core\Utility\NestedArray; + /** * Defines the default configuration object. */ @@ -105,7 +107,7 @@ class Config { $name = $this->getName(); if (isset($conf[$name])) { - $merged_data = drupal_array_merge_deep($this->data, $conf[$name]); + $merged_data = NestedArray::mergeDeepArray(array($this->data, $conf[$name])); } else { $merged_data = $this->data; @@ -121,7 +123,7 @@ class Config { } else { $key_exists = NULL; - $value = drupal_array_get_nested_value($merged_data, $parts, $key_exists); + $value = NestedArray::getValue($merged_data, $parts, $key_exists); return $key_exists ? $value : NULL; } } @@ -157,7 +159,7 @@ class Config { $this->data[$key] = $value; } else { - drupal_array_set_nested_value($this->data, $parts, $value); + NestedArray::setValue($this->data, $parts, $value); } return $this; } @@ -213,7 +215,7 @@ class Config { unset($this->data[$key]); } else { - drupal_array_unset_nested_value($this->data, $parts); + NestedArray::unsetValue($this->data, $parts); } return $this; } diff --git a/core/lib/Drupal/Core/Utility/NestedArray.php b/core/lib/Drupal/Core/Utility/NestedArray.php new file mode 100644 index 0000000..58cf283 --- /dev/null +++ b/core/lib/Drupal/Core/Utility/NestedArray.php @@ -0,0 +1,341 @@ + 'text_format', + * '#title' => t('Signature'), + * ); + * // Or, it might be further nested: + * $form['signature_settings']['user']['signature'] = array( + * '#type' => 'text_format', + * '#title' => t('Signature'), + * ); + * @endcode + * + * To deal with the situation, the code needs to figure out the route to the + * element, given an array of parents that is either + * @code array('signature_settings', 'signature') @endcode + * in the first case or + * @code array('signature_settings', 'user', 'signature') @endcode + * in the second case. + * + * Without this helper function the only way to set the signature element in + * one line would be using eval(), which should be avoided: + * @code + * // Do not do this! Avoid eval(). + * eval('$form[\'' . implode("']['", $parents) . '\'] = $element;'); + * @endcode + * + * Instead, use this helper function: + * @code + * NestedArray::setValue($form, $parents, $element); + * @endcode + * + * However if the number of array parent keys is static, the value should + * always be set directly rather than calling this function. For instance, + * for the first example we could just do: + * @code + * $form['signature_settings']['signature'] = $element; + * @endcode + * + * @param array $array + * A reference to the array to modify. + * @param array $parents + * An array of parent keys, starting with the outermost key. + * @param mixed $value + * The value to set. + * @param bool $force + * (optional) If TRUE, the value is forced into the structure even if it + * requires the deletion of an already existing non-array parent value. If + * FALSE, PHP throws an error if trying to add into a value that is not an + * array. Defaults to FALSE. + * + * @see NestedArray::unsetValue() + * @see NestedArray::getValue() + */ + public static function setValue(array &$array, array $parents, $value, $force = FALSE) { + $ref = &$array; + foreach ($parents as $parent) { + // PHP auto-creates container arrays and NULL entries without error if $ref + // is NULL, but throws an error if $ref is set, but not an array. + if ($force && isset($ref) && !is_array($ref)) { + $ref = array(); + } + $ref = &$ref[$parent]; + } + $ref = $value; + } + + /** + * Unsets a value in a nested array with variable depth. + * + * This helper function should be used when the depth of the array element you + * are changing may vary (that is, the number of parent keys is variable). It + * is primarily used for form structures and renderable arrays. + * + * Example: + * @code + * // Assume you have a 'signature' element somewhere in a form. It might be: + * $form['signature_settings']['signature'] = array( + * '#type' => 'text_format', + * '#title' => t('Signature'), + * ); + * // Or, it might be further nested: + * $form['signature_settings']['user']['signature'] = array( + * '#type' => 'text_format', + * '#title' => t('Signature'), + * ); + * @endcode + * + * To deal with the situation, the code needs to figure out the route to the + * element, given an array of parents that is either + * @code array('signature_settings', 'signature') @endcode + * in the first case or + * @code array('signature_settings', 'user', 'signature') @endcode + * in the second case. + * + * Without this helper function the only way to unset the signature element in + * one line would be using eval(), which should be avoided: + * @code + * // Do not do this! Avoid eval(). + * eval('unset($form[\'' . implode("']['", $parents) . '\']);'); + * @endcode + * + * Instead, use this helper function: + * @code + * NestedArray::unset_nested_value($form, $parents, $element); + * @endcode + * + * However if the number of array parent keys is static, the value should + * always be set directly rather than calling this function. For instance, for + * the first example we could just do: + * @code + * unset($form['signature_settings']['signature']); + * @endcode + * + * @param array $array + * A reference to the array to modify. + * @param array $parents + * An array of parent keys, starting with the outermost key and including + * the key to be unset. + * @param bool $key_existed + * (optional) If given, an already defined variable that is altered by + * reference. + * + * @see NestedArray::setValue() + * @see NestedArray::getValue() + */ + public static function unsetValue(array &$array, array $parents, &$key_existed = NULL) { + $unset_key = array_pop($parents); + $ref = &self::getValue($array, $parents, $key_existed); + if ($key_existed && is_array($ref) && array_key_exists($unset_key, $ref)) { + $key_existed = TRUE; + unset($ref[$unset_key]); + } + else { + $key_existed = FALSE; + } + } + + /** + * Determines whether a nested array contains the requested keys. + * + * This helper function should be used when the depth of the array element to + * be checked may vary (that is, the number of parent keys is variable). See + * NestedArray::setValue() for details. It is primarily used for form + * structures and renderable arrays. + * + * If it is required to also get the value of the checked nested key, use + * NestedArray::getValue() instead. + * + * If the number of array parent keys is static, this helper function is + * unnecessary and the following code can be used instead: + * @code + * $value_exists = isset($form['signature_settings']['signature']); + * $key_exists = array_key_exists('signature', $form['signature_settings']); + * @endcode + * + * @param array $array + * The array with the value to check for. + * @param array $parents + * An array of parent keys of the value, starting with the outermost key. + * + * @return bool + * TRUE if all the parent keys exist, FALSE otherwise. + * + * @see NestedArray::getValue() + */ + public static function keyExists(array $array, array $parents) { + // Although this function is similar to PHP's array_key_exists(), its + // arguments should be consistent with getValue(). + $key_exists = NULL; + self::getValue($array, $parents, $key_exists); + return $key_exists; + } + + /** + * Merges multiple arrays, recursively, and returns the merged array. + * + * This function is similar to PHP's array_merge_recursive() function, but it + * handles non-array values differently. When merging values that are not both + * arrays, the latter value replaces the former rather than merging with it. + * + * Example: + * @code + * $link_options_1 = array('fragment' => 'x', 'attributes' => array('title' => t('X'), 'class' => array('a', 'b'))); + * $link_options_2 = array('fragment' => 'y', 'attributes' => array('title' => t('Y'), 'class' => array('c', 'd'))); + * + * // This results in array('fragment' => array('x', 'y'), 'attributes' => array('title' => array(t('X'), t('Y')), 'class' => array('a', 'b', 'c', 'd'))). + * $incorrect = array_merge_recursive($link_options_1, $link_options_2); + * + * // This results in array('fragment' => 'y', 'attributes' => array('title' => t('Y'), 'class' => array('a', 'b', 'c', 'd'))). + * $correct = NestedArray::mergeDeep($link_options_1, $link_options_2); + * @endcode + * + * @param array ... + * Arrays to merge. + * + * @return array + * The merged array. + * + * @see NestedArray::mergeDeepArray() + */ + public static function mergeDeep() { + return self::mergeDeepArray(func_get_args()); + } + + /** + * Merges multiple arrays, recursively, and returns the merged array. + * + * This function is equivalent to NestedArray::mergeDeep(), except the + * input arrays are passed as a single array parameter rather than a variable + * parameter list. + * + * The following are equivalent: + * - NestedArray::mergeDeep($a, $b); + * - NestedArray::mergeDeepArray(array($a, $b)); + * + * The following are also equivalent: + * - call_user_func_array('NestedArray::mergeDeep', $arrays_to_merge); + * - NestedArray::mergeDeepArray($arrays_to_merge); + * + * @see NestedArray::mergeDeep() + */ + public static function mergeDeepArray(array $arrays) { + $result = array(); + foreach ($arrays as $array) { + foreach ($array as $key => $value) { + // Renumber integer keys as array_merge_recursive() does. Note that PHP + // automatically converts array keys that are integer strings (e.g., '1') + // to integers. + if (is_integer($key)) { + $result[] = $value; + } + // Recurse when both values are arrays. + elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) { + $result[$key] = self::mergeDeepArray(array($result[$key], $value)); + } + // Otherwise, use the latter value, overriding any previous value. + else { + $result[$key] = $value; + } + } + } + return $result; + } + +}