-
Notifications
You must be signed in to change notification settings - Fork 8k
array_path_get and array_path_exists functions #21637
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
cb40915
3c71ecf
10037a7
cb4855d
4af39b9
6f1bc06
b947093
01b4726
b3f7065
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6915,6 +6915,109 @@ PHP_FUNCTION(array_key_exists) | |
| } | ||
| /* }}} */ | ||
|
|
||
| /* {{{ Helper function to get a nested value from array using an array of path segments */ | ||
| static zval* array_get_nested(HashTable *ht, HashTable *path) | ||
| { | ||
| zval *segment_val; | ||
| zval *current = NULL; | ||
| HashTable *current_ht = ht; | ||
| uint32_t num_segments = zend_hash_num_elements(path); | ||
| uint32_t segment_index = 0; | ||
|
|
||
| /* Iterate through each segment in the path array */ | ||
| ZEND_HASH_FOREACH_VAL(path, segment_val) { | ||
| segment_index++; | ||
|
|
||
| /* Dereference segment if it's a reference */ | ||
| ZVAL_DEREF(segment_val); | ||
|
|
||
| /* Segment must be a string or int */ | ||
| if (Z_TYPE_P(segment_val) == IS_STRING) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh and references are also not handled for segment_val. You can use zend_hash_index_find_deref, or if you switch to a ZEND_HASH_FOREACH loop use something like ZVAL_DEREF. |
||
| current = zend_symtable_find(current_ht, Z_STR_P(segment_val)); | ||
| } else if (Z_TYPE_P(segment_val) == IS_LONG) { | ||
| current = zend_hash_index_find(current_ht, Z_LVAL_P(segment_val)); | ||
| } else { | ||
| /* Invalid segment type - throw TypeError */ | ||
| zend_type_error("Path segment must be of type string|int, %s given", zend_zval_value_name(segment_val)); | ||
| return NULL; | ||
| } | ||
|
|
||
| /* If segment not found, return NULL */ | ||
| if (current == NULL) { | ||
| return NULL; | ||
| } | ||
|
|
||
| /* Dereference if it's a reference */ | ||
| ZVAL_DEREF(current); | ||
|
|
||
| /* If current is not an array and we're not at the last segment, | ||
| * we can't continue traversing the path */ | ||
| if (Z_TYPE_P(current) != IS_ARRAY && segment_index < num_segments) { | ||
| return NULL; | ||
| } | ||
|
|
||
| /* Update current_ht for next iteration if it's an array */ | ||
| if (Z_TYPE_P(current) == IS_ARRAY) { | ||
| current_ht = Z_ARRVAL_P(current); | ||
| } | ||
| } ZEND_HASH_FOREACH_END(); | ||
|
|
||
| return current; | ||
| } | ||
| /* }}} */ | ||
|
|
||
| /* {{{ Retrieves a value from a deeply nested array using an array path */ | ||
| PHP_FUNCTION(array_path_get) | ||
| { | ||
| zval *array; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where possible, please merge the declaration and assignment. Splitting them is bad practice nowadays because the scope of the variable is unclear. |
||
| zval *path; | ||
| zval *default_value = NULL; | ||
|
|
||
| ZEND_PARSE_PARAMETERS_START(2, 3) | ||
| Z_PARAM_ARRAY(array) | ||
| Z_PARAM_ARRAY(path) | ||
| Z_PARAM_OPTIONAL | ||
| Z_PARAM_ZVAL(default_value) | ||
| ZEND_PARSE_PARAMETERS_END(); | ||
|
|
||
| zval *result = array_get_nested(Z_ARRVAL_P(array), Z_ARRVAL_P(path)); | ||
|
|
||
| if (EG(exception)) { | ||
| RETURN_THROWS(); | ||
| } | ||
|
|
||
| if (result != NULL) { | ||
| RETURN_COPY_DEREF(result); | ||
| } | ||
|
|
||
| /* Path not found, return default value */ | ||
| if (default_value != NULL) { | ||
| RETURN_COPY(default_value); | ||
| } | ||
| } | ||
| /* }}} */ | ||
|
|
||
| /* {{{ Checks whether a given item exists in an array using an array path */ | ||
| PHP_FUNCTION(array_path_exists) | ||
| { | ||
| zval *array; | ||
| zval *path; | ||
|
|
||
| ZEND_PARSE_PARAMETERS_START(2, 2) | ||
| Z_PARAM_ARRAY(array) | ||
| Z_PARAM_ARRAY(path) | ||
| ZEND_PARSE_PARAMETERS_END(); | ||
|
|
||
| zval *result = array_get_nested(Z_ARRVAL_P(array), Z_ARRVAL_P(path)); | ||
|
|
||
| if (EG(exception)) { | ||
| RETURN_THROWS(); | ||
| } | ||
|
|
||
| RETURN_BOOL(result != NULL); | ||
| } | ||
| /* }}} */ | ||
|
|
||
| /* {{{ Split array into chunks */ | ||
| PHP_FUNCTION(array_chunk) | ||
| { | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| --TEST-- | ||
| Test array_path_exists() function | ||
| --FILE-- | ||
| <?php | ||
| echo "*** Testing array_path_exists() ***\n"; | ||
|
|
||
| // Basic array | ||
| $array = ['product' => ['name' => 'Desk', 'price' => 100]]; | ||
|
|
||
| // Test nested key exists with array path | ||
| var_dump(array_path_exists($array, ['product', 'name'])); | ||
|
|
||
| // Test nested key doesn't exist | ||
| var_dump(array_path_exists($array, ['product', 'color'])); | ||
|
|
||
| // Test intermediate key doesn't exist | ||
| var_dump(array_path_exists($array, ['category', 'name'])); | ||
|
|
||
| // Test simple path with single level | ||
| $simple = ['name' => 'John', 'age' => 30]; | ||
| var_dump(array_path_exists($simple, ['name'])); | ||
| var_dump(array_path_exists($simple, ['missing'])); | ||
|
|
||
| // Test with integer key in path | ||
| $users = ['users' => [['name' => 'Alice'], ['name' => 'Bob']]]; | ||
| var_dump(array_path_exists($users, ['users', 0, 'name'])); | ||
| var_dump(array_path_exists($users, ['users', 1, 'name'])); | ||
| var_dump(array_path_exists($users, ['users', 2, 'name'])); | ||
|
|
||
| // Test with value that is null (key exists, but value is null) | ||
| $withNull = ['key' => null]; | ||
| var_dump(array_path_exists($withNull, ['key'])); | ||
|
|
||
| // Test with invalid segment type in array path | ||
| try { | ||
| var_dump(array_path_exists($array, ['product', new stdClass()])); | ||
| } catch (TypeError $e) { | ||
| echo $e->getMessage() . "\n"; | ||
| } | ||
|
|
||
| // Test with reference to an array in the path | ||
| $array2 = ['world']; | ||
| $array_with_ref = ['hello' => &$array2]; | ||
| var_dump(array_path_exists($array_with_ref, ['hello', 0])); | ||
|
|
||
| // Test with path segment that is a reference | ||
| $key1 = 'product'; | ||
| $key2 = 'name'; | ||
| $path_with_refs = [&$key1, &$key2]; | ||
| var_dump(array_path_exists($array, $path_with_refs)); | ||
|
|
||
| echo "Done"; | ||
| ?> | ||
| --EXPECT-- | ||
| *** Testing array_path_exists() *** | ||
| bool(true) | ||
| bool(false) | ||
| bool(false) | ||
| bool(true) | ||
| bool(false) | ||
| bool(true) | ||
| bool(true) | ||
| bool(false) | ||
| bool(true) | ||
| Path segment must be of type string|int, stdClass given | ||
| bool(true) | ||
| bool(true) | ||
| Done |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| --TEST-- | ||
| Test array_path_get() function | ||
| --FILE-- | ||
| <?php | ||
| echo "*** Testing array_path_get() ***\n"; | ||
|
|
||
| // Basic nested array access | ||
| $array = ['products' => ['desk' => ['price' => 100]]]; | ||
|
|
||
| // Test nested access with array path | ||
| var_dump(array_path_get($array, ['products', 'desk', 'price'])); | ||
|
|
||
| // Test with default value when path doesn't exist | ||
| var_dump(array_path_get($array, ['products', 'desk', 'discount'], 5)); | ||
|
|
||
| // Test simple path with single level | ||
| $simple = ['name' => 'John', 'age' => 30]; | ||
| var_dump(array_path_get($simple, ['name'])); | ||
| var_dump(array_path_get($simple, ['missing'], 'default')); | ||
|
|
||
| // Test single level key that doesn't exist | ||
| var_dump(array_path_get($array, ['missing'])); | ||
|
|
||
| // Test with integer key in path | ||
| $users = ['users' => [['name' => 'Alice'], ['name' => 'Bob']]]; | ||
| var_dump(array_path_get($users, ['users', 0, 'name'])); | ||
| var_dump(array_path_get($users, ['users', 1, 'name'])); | ||
|
|
||
| // Test nested with missing intermediate key | ||
| var_dump(array_path_get($array, ['products', 'chair', 'price'], 75)); | ||
|
|
||
| // Test with invalid segment type in array path | ||
| try { | ||
| var_dump(array_path_get($array, ['products', new stdClass(), 'price'], 'invalid')); | ||
| } catch (TypeError $e) { | ||
| echo $e->getMessage() . "\n"; | ||
| } | ||
|
|
||
| // Test with references - ensure returned value is a copy, not a reference | ||
| $ref_array = ['data' => ['value' => 'original']]; | ||
| $ref =& $ref_array['data']['value']; | ||
| $result = array_path_get($ref_array, ['data', 'value']); | ||
| var_dump($result); | ||
| $ref = 'modified'; | ||
| var_dump($result); // Should still be 'original' (not affected by reference change) | ||
|
|
||
| // Test with default value being a reference | ||
| $default_value = 'default'; | ||
| $default_ref =& $default_value; | ||
| $result_with_ref_default = array_path_get($ref_array, ['missing', 'key'], $default_ref); | ||
| var_dump($result_with_ref_default); | ||
| $default_value = 'changed'; | ||
| var_dump($result_with_ref_default); // Should still be 'default' (not affected by reference change) | ||
|
|
||
| // Test with reference to an array in the path | ||
| $array2 = ['world']; | ||
| $array_with_ref = ['hello' => &$array2]; | ||
| var_dump(array_path_get($array_with_ref, ['hello', 0])); | ||
|
|
||
| // Test with path segment that is a reference | ||
| $key1 = 'products'; | ||
| $key2 = 'desk'; | ||
| $path_with_refs = [&$key1, &$key2, 'price']; | ||
| var_dump(array_path_get($array, $path_with_refs)); | ||
|
|
||
| echo "Done"; | ||
| ?> | ||
| --EXPECT-- | ||
| *** Testing array_path_get() *** | ||
| int(100) | ||
| int(5) | ||
| string(4) "John" | ||
| string(7) "default" | ||
| NULL | ||
| string(5) "Alice" | ||
| string(3) "Bob" | ||
| int(75) | ||
| Path segment must be of type string|int, stdClass given | ||
| string(8) "original" | ||
| string(8) "original" | ||
| string(7) "default" | ||
| string(7) "default" | ||
| string(5) "world" | ||
| int(100) | ||
| Done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where possible, please merge the declaration and assignment. Splitting them is bad practice nowadays because the scope of the variable is unclear.