diff --git a/core/includes/common.inc b/core/includes/common.inc index 967a59688ae..2f1474056ad 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -6447,6 +6447,78 @@ function element_set_attributes(array &$element, array $map) { } } +/** + * Retrieves a value from a nested array with variable depth. + * + * This helper function should be used when the depth of the array element being + * retrieved may vary (that is, the number of parent keys is variable). It is + * primarily used for form structures and renderable arrays. + * + * Without this helper function the only way to get a nested array value with + * variable depth in one line would be using eval(), which should be avoided: + * @code + * // Do not do this! Avoid eval(). + * // May also throw a PHP notice, if the variable array keys do not exist. + * eval('$value = $array[\'' . implode("']['", $parents) . "'];"); + * @endcode + * + * Instead, use this helper function: + * @code + * $value = drupal_array_get_nested_value($form, $parents); + * @endcode + * + * The return value will be NULL, regardless of whether the actual value is NULL + * or whether the requested key does not exist. If it is required to know + * whether the nested array key actually exists, pass a third argument that is + * altered by reference: + * @code + * $key_exists = NULL; + * $value = drupal_array_get_nested_value($form, $parents, $key_exists); + * if ($key_exists) { + * // ... do something with $value ... + * } + * @endcode + * + * However if the number of array parent keys is static, the value should always + * be retrieved directly rather than calling this function. For instance: + * @code + * $value = $form['signature_settings']['signature']; + * @endcode + * + * @param $array + * The array from which to get the value. + * @param $parents + * An array of parent keys of the value, starting with the outermost key. + * @param $key_exists + * (optional) If given, an already defined variable that is altered by + * reference. + * + * @return + * The requested nested value. Possibly NULL if the value is NULL or not all + * nested parent keys exist. $key_exists is altered by reference and is a + * Boolean that indicates whether all nested parent keys exist (TRUE) or not + * (FALSE). This allows to distinguish between the two possibilities when NULL + * is returned. + * + * @see drupal_array_set_nested_value() + * @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; +} + /** * Sets a value in a nested array with variable depth. * @@ -6505,6 +6577,7 @@ function element_set_attributes(array &$element, array $map) { * FALSE, PHP throws an error if trying to add into a value that is not an * array. Defaults to FALSE. * + * @see drupal_array_unset_nested_value() * @see drupal_array_get_nested_value() */ function drupal_array_set_nested_value(array &$array, array $parents, $value, $force = FALSE) { @@ -6521,74 +6594,73 @@ function drupal_array_set_nested_value(array &$array, array $parents, $value, $f } /** - * Retrieves a value from a nested array with variable depth. + * Unsets a value in a nested array with variable depth. * - * This helper function should be used when the depth of the array element being - * retrieved may vary (that is, the number of parent keys is variable). It is - * primarily used for form structures and renderable arrays. + * 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. * - * Without this helper function the only way to get a nested array value with - * variable depth in one line would be using eval(), which should be avoided: + * 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(). - * // May also throw a PHP notice, if the variable array keys do not exist. - * eval('$value = $array[\'' . implode("']['", $parents) . "'];"); + * eval('unset($form[\'' . implode("']['", $parents) . '\']);'); * @endcode * * Instead, use this helper function: * @code - * $value = drupal_array_get_nested_value($form, $parents); - * @endcode - * - * The return value will be NULL, regardless of whether the actual value is NULL - * or whether the requested key does not exist. If it is required to know - * whether the nested array key actually exists, pass a third argument that is - * altered by reference: - * @code - * $key_exists = NULL; - * $value = drupal_array_get_nested_value($form, $parents, $key_exists); - * if ($key_exists) { - * // ... do something with $value ... - * } + * drupal_array_unset_nested_value($form, $parents, $element); * @endcode * * However if the number of array parent keys is static, the value should always - * be retrieved directly rather than calling this function. For instance: + * be set directly rather than calling this function. For instance, for the + * first example we could just do: * @code - * $value = $form['signature_settings']['signature']; + * unset($form['signature_settings']['signature']); * @endcode * * @param $array - * The array from which to get the value. + * A reference to the array to modify. * @param $parents - * An array of parent keys of the value, starting with the outermost key. - * @param $key_exists + * An array of parent keys, starting with the outermost key and including the + * key to be unset. + * @param $key_existed * (optional) If given, an already defined variable that is altered by * reference. * - * @return - * The requested nested value. Possibly NULL if the value is NULL or not all - * nested parent keys exist. $key_exists is altered by reference and is a - * Boolean that indicates whether all nested parent keys exist (TRUE) or not - * (FALSE). This allows to distinguish between the two possibilities when NULL - * is returned. - * * @see drupal_array_set_nested_value() + * @see drupal_array_get_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; - } +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; } - $key_exists = TRUE; - return $ref; } /** diff --git a/core/modules/simpletest/tests/common.test b/core/modules/simpletest/tests/common.test index 66c0400d0f4..b07ad18bb34 100644 --- a/core/modules/simpletest/tests/common.test +++ b/core/modules/simpletest/tests/common.test @@ -2411,6 +2411,118 @@ class DrupalAttributesUnitTest extends DrupalUnitTestCase { } } +/** + * Tests the various drupal_array_* helper functions. + */ +class CommonDrupalArrayUnitTest extends DrupalUnitTestCase { + + /** + * Form array to check. + */ + protected $form; + + /** + * Array of parents for the nested element. + */ + protected $parents; + + public static function getInfo() { + return array( + 'name' => 'drupal_array_*() tests', + 'description' => 'Tests the various drupal_array_* helper functions.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp(); + + // Create a form structure with a nested element. + $this->form['fieldset']['element'] = array( + '#value' => 'Nested element', + ); + + // Set up parent array. + $this->parents = array('fieldset', 'element'); + } + + /** + * Tests getting nested array values. + */ + function testGet() { + // Verify getting a value of a nested element. + $value = drupal_array_get_nested_value($this->form, $this->parents); + $this->assertEqual($value['#value'], 'Nested element', 'Nested element value found.'); + + // Verify changing a value of a nested element by reference. + $value = &drupal_array_get_nested_value($this->form, $this->parents); + $value['#value'] = 'New value'; + $value = drupal_array_get_nested_value($this->form, $this->parents); + $this->assertEqual($value['#value'], 'New value', 'Nested element value was changed by reference.'); + $this->assertEqual($this->form['fieldset']['element']['#value'], 'New value', 'Nested element value was changed by reference.'); + + // Verify that an existing key is reported back. + $key_exists = NULL; + drupal_array_get_nested_value($this->form, $this->parents, $key_exists); + $this->assertIdentical($key_exists, TRUE, 'Existing key found.'); + + // Verify that a non-existing key is reported back and throws no errors. + $key_exists = NULL; + $parents = $this->parents; + $parents[] = 'foo'; + drupal_array_get_nested_value($this->form, $parents, $key_exists); + $this->assertIdentical($key_exists, FALSE, 'Non-existing key not found.'); + } + + /** + * Tests setting nested array values. + */ + function testSet() { + $new_value = array( + '#value' => 'New value', + '#required' => TRUE, + ); + + // Verify setting the value of a nested element. + drupal_array_set_nested_value($this->form, $this->parents, $new_value); + $this->assertEqual($this->form['fieldset']['element']['#value'], 'New value', 'Changed nested element value found.'); + $this->assertIdentical($this->form['fieldset']['element']['#required'], TRUE, 'New nested element value found.'); + } + + /** + * Tests unsetting nested array values. + */ + function testUnset() { + // Verify unsetting a non-existing nested element throws no errors and the + // non-existing key is properly reported. + $key_existed = NULL; + $parents = $this->parents; + $parents[] = 'foo'; + drupal_array_unset_nested_value($this->form, $parents, $key_existed); + $this->assertTrue(isset($this->form['fieldset']['element']['#value']), 'Outermost nested element key still exists.'); + $this->assertIdentical($key_existed, FALSE, 'Non-existing key not found.'); + + // Verify unsetting a nested element. + $key_existed = NULL; + drupal_array_unset_nested_value($this->form, $this->parents, $key_existed); + $this->assertFalse(isset($this->form['fieldset']['element']), 'Removed nested element not found.'); + $this->assertIdentical($key_existed, TRUE, 'Existing key was found.'); + } + + /** + * Tests existence of array key. + */ + function testKeyExists() { + // Verify that existing key is found. + $this->assertIdentical(drupal_array_nested_key_exists($this->form, $this->parents), TRUE, 'Nested key found.'); + + // Verify that non-existing keys are not found. + $parents = $this->parents; + $parents[] = 'foo'; + $this->assertIdentical(drupal_array_nested_key_exists($this->form, $parents), FALSE, 'Non-existing nested key not found.'); + } +} + /** * Tests converting PHP variables to JSON strings and back. */