-
Notifications
You must be signed in to change notification settings - Fork 8k
Array_get and array_has 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
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 |
|---|---|---|
|
|
@@ -6910,6 +6910,160 @@ PHP_FUNCTION(array_key_exists) | |
| } | ||
| /* }}} */ | ||
|
|
||
| /* {{{ Helper function to get a nested value from array using an array of segments */ | ||
| static zval* array_get_nested(HashTable *ht, HashTable *segments) | ||
| { | ||
| zval *segment_val; | ||
| zval *current; | ||
| HashTable *current_ht; | ||
| uint32_t idx; | ||
| uint32_t num_segments; | ||
|
|
||
| current_ht = ht; | ||
| num_segments = zend_hash_num_elements(segments); | ||
|
|
||
| /* Iterate through each segment in the array */ | ||
| for (idx = 0; idx < num_segments; idx++) { | ||
| /* Get the segment at the current index */ | ||
| segment_val = zend_hash_index_find(segments, idx); | ||
|
|
||
| if (segment_val == NULL) { | ||
| /* Missing segment in array */ | ||
| return NULL; | ||
| } | ||
|
|
||
| /* Segment must be a string or int */ | ||
| if (Z_TYPE_P(segment_val) == IS_STRING) { | ||
| 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 */ | ||
| return NULL; | ||
| } | ||
|
|
||
| /* If this is the last segment, return the result */ | ||
| if (idx == num_segments - 1) { | ||
| return current; | ||
| } | ||
|
|
||
| /* Check if the segment exists and is an array for next iteration */ | ||
| if (current == NULL || Z_TYPE_P(current) != IS_ARRAY) { | ||
| return NULL; | ||
| } | ||
|
|
||
| /* Move to the next level */ | ||
| current_ht = Z_ARRVAL_P(current); | ||
| } | ||
|
|
||
| /* Empty segments array */ | ||
| return NULL; | ||
| } | ||
| /* }}} */ | ||
|
|
||
| /* {{{ Retrieves a value from a deeply nested array using "dot" notation */ | ||
| PHP_FUNCTION(array_get) | ||
| { | ||
| zval *array; | ||
| zval *key = NULL; | ||
| zval *default_value = NULL; | ||
| zval *result; | ||
| zval segments_array; | ||
| HashTable *ht; | ||
|
|
||
| ZEND_PARSE_PARAMETERS_START(2, 3) | ||
| Z_PARAM_ARRAY(array) | ||
| Z_PARAM_ZVAL_OR_NULL(key) | ||
| Z_PARAM_OPTIONAL | ||
| Z_PARAM_ZVAL(default_value) | ||
| ZEND_PARSE_PARAMETERS_END(); | ||
|
|
||
| /* If key is null, return the whole array */ | ||
| if (key == NULL || Z_TYPE_P(key) == IS_NULL) { | ||
| RETURN_COPY(array); | ||
| } | ||
|
|
||
| ht = Z_ARRVAL_P(array); | ||
|
|
||
| /* Handle array keys (array of segments) */ | ||
| if (Z_TYPE_P(key) == IS_ARRAY) { | ||
| result = array_get_nested(ht, Z_ARRVAL_P(key)); | ||
|
|
||
| if (result != NULL) { | ||
| RETURN_COPY(result); | ||
| } | ||
| } | ||
| /* Handle string keys with dot notation - convert to array of segments */ | ||
| else if (Z_TYPE_P(key) == IS_STRING) { | ||
| /* Use php_explode to split the string by '.' */ | ||
| zend_string *delim = ZSTR_CHAR('.'); | ||
| array_init(&segments_array); | ||
| php_explode(delim, Z_STR_P(key), &segments_array, ZEND_LONG_MAX); | ||
|
|
||
| result = array_get_nested(ht, Z_ARRVAL(segments_array)); | ||
|
|
||
| zval_ptr_dtor(&segments_array); | ||
|
|
||
| if (result != NULL) { | ||
| RETURN_COPY(result); | ||
| } | ||
| } | ||
| /* Handle integer keys (simple lookup) */ | ||
| else if (Z_TYPE_P(key) == IS_LONG) { | ||
| result = zend_hash_index_find(ht, Z_LVAL_P(key)); | ||
|
|
||
| if (result != NULL) { | ||
| RETURN_COPY(result); | ||
| } | ||
| } | ||
|
|
||
| /* Key not found, return default value */ | ||
| if (default_value != NULL) { | ||
| RETURN_COPY(default_value); | ||
| } | ||
| } | ||
| /* }}} */ | ||
|
|
||
| /* {{{ Checks whether a given item exists in an array using "dot" notation */ | ||
| PHP_FUNCTION(array_has) | ||
| { | ||
| zval *array; | ||
| zval *key; | ||
| zval *result; | ||
| zval segments_array; | ||
| HashTable *ht; | ||
|
|
||
| ZEND_PARSE_PARAMETERS_START(2, 2) | ||
| Z_PARAM_ARRAY(array) | ||
| Z_PARAM_ZVAL(key) | ||
| ZEND_PARSE_PARAMETERS_END(); | ||
|
|
||
| ht = Z_ARRVAL_P(array); | ||
|
|
||
| /* Handle array keys (array of segments) */ | ||
| if (Z_TYPE_P(key) == IS_ARRAY) { | ||
| result = array_get_nested(ht, Z_ARRVAL_P(key)); | ||
| RETURN_BOOL(result != NULL); | ||
| } | ||
| /* Handle string keys with dot notation - convert to array of segments */ | ||
| if (Z_TYPE_P(key) == IS_STRING) { | ||
| /* Use php_explode to split the string by '.' */ | ||
| zend_string *delim = ZSTR_CHAR('.'); | ||
| array_init(&segments_array); | ||
| php_explode(delim, Z_STR_P(key), &segments_array, ZEND_LONG_MAX); | ||
|
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. This is a bit of an inefficient way to go about it. |
||
|
|
||
| result = array_get_nested(ht, Z_ARRVAL(segments_array)); | ||
|
|
||
| zval_ptr_dtor(&segments_array); | ||
| RETURN_BOOL(result != NULL); | ||
| } | ||
|
|
||
| /* Handle integer keys (simple lookup) */ | ||
| ZEND_ASSERT(Z_TYPE_P(key) == IS_LONG); | ||
|
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. This is insufficient, the ZPP parsing still accepts any argument. The stubs don't enforce the argument type check in any way. |
||
| RETURN_BOOL(zend_hash_index_exists(ht, Z_LVAL_P(key))); | ||
| } | ||
| /* }}} */ | ||
|
|
||
| /* {{{ 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,77 @@ | ||
| --TEST-- | ||
| Test array_get() function | ||
| --FILE-- | ||
| <?php | ||
| /* | ||
| * Test functionality of array_get() | ||
| */ | ||
|
|
||
| echo "*** Testing array_get() ***\n"; | ||
|
|
||
| // Basic array access | ||
| $array = ['products' => ['desk' => ['price' => 100]]]; | ||
|
|
||
| // Test nested access with dot notation | ||
| var_dump(array_get($array, 'products.desk.price')); | ||
|
|
||
| // Test with default value when key doesn't exist | ||
| var_dump(array_get($array, 'products.desk.discount', 0)); | ||
|
|
||
| // Test simple key access | ||
| $simple = ['name' => 'John', 'age' => 30]; | ||
| var_dump(array_get($simple, 'name')); | ||
| var_dump(array_get($simple, 'missing', 'default')); | ||
|
|
||
| // Test with integer key | ||
| $indexed = ['a', 'b', 'c']; | ||
| var_dump(array_get($indexed, 0)); | ||
| var_dump(array_get($indexed, 5, 'not found')); | ||
|
|
||
| // Test with null key (returns whole array) | ||
| $test = ['foo' => 'bar']; | ||
| var_dump(array_get($test, null)); | ||
|
|
||
| // Test nested with missing intermediate key | ||
| var_dump(array_get($array, 'products.chair.price', 50)); | ||
|
|
||
| // Test single level key that doesn't exist | ||
| var_dump(array_get($array, 'missing')); | ||
|
|
||
| // Test with numeric string in path (like users.0.name) | ||
| $users = ['users' => [['name' => 'Alice'], ['name' => 'Bob']]]; | ||
| var_dump(array_get($users, 'users.0.name')); | ||
| var_dump(array_get($users, 'users.1.age', 70)); | ||
|
|
||
| // Test with array key (equivalent to dot notation) | ||
| var_dump(array_get($array, ['products', 'desk', 'price'])); | ||
| var_dump(array_get($simple, ['name'])); | ||
| var_dump(array_get($users, ['users', 0, 'name'])); | ||
| var_dump(array_get($array, ['products', 'chair', 'price'], 75)); | ||
|
|
||
| // Test with invalid segment type in array key | ||
| var_dump(array_get($array, ['products', new stdClass(), 'price'], 'invalid')); | ||
|
|
||
| echo "Done"; | ||
| ?> | ||
| --EXPECT-- | ||
| *** Testing array_get() *** | ||
| int(100) | ||
| int(0) | ||
| string(4) "John" | ||
| string(7) "default" | ||
| string(1) "a" | ||
| string(9) "not found" | ||
| array(1) { | ||
| ["foo"]=> | ||
| string(3) "bar" | ||
| } | ||
| int(50) | ||
| NULL | ||
| string(5) "Alice" | ||
| int(70) | ||
| int(100) | ||
| string(4) "John" | ||
| string(5) "Alice" | ||
| int(75) | ||
| string(7) "invalid" | ||
| Done |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| --TEST-- | ||
| Test array_has() function | ||
| --FILE-- | ||
| <?php | ||
| /* | ||
| * Test functionality of array_has() | ||
| */ | ||
|
|
||
| echo "*** Testing array_has() ***\n"; | ||
|
|
||
| // Basic array | ||
| $array = ['product' => ['name' => 'Desk', 'price' => 100]]; | ||
|
|
||
| // Test nested key exists with dot notation | ||
| var_dump(array_has($array, 'product.name')); | ||
|
|
||
| // Test nested key doesn't exist | ||
| var_dump(array_has($array, 'product.color')); | ||
|
|
||
| // Test intermediate key doesn't exist | ||
| var_dump(array_has($array, 'category.name')); | ||
|
|
||
| // Test simple key access | ||
| $simple = ['name' => 'John', 'age' => 30]; | ||
| var_dump(array_has($simple, 'name')); | ||
| var_dump(array_has($simple, 'missing')); | ||
|
|
||
| // Test with integer key | ||
| $indexed = ['a', 'b', 'c']; | ||
| var_dump(array_has($indexed, 0)); | ||
| var_dump(array_has($indexed, 1)); | ||
| var_dump(array_has($indexed, 5)); | ||
|
|
||
| // Test with value that is null (key exists, but value is null) | ||
| $withNull = ['key' => null]; | ||
| var_dump(array_has($withNull, 'key')); | ||
|
|
||
| // Test with numeric string in path (like users.0.name) | ||
| $users = ['users' => [['name' => 'Alice'], ['name' => 'Bob']]]; | ||
| var_dump(array_has($users, 'users.0.name')); | ||
| var_dump(array_has($users, 'users.1.age')); | ||
| var_dump(array_has($users, 'users.2.name')); | ||
|
|
||
| // Test with array key (equivalent to dot notation) | ||
| var_dump(array_has($array, ['product', 'name'])); | ||
| var_dump(array_has($simple, ['name'])); | ||
| var_dump(array_has($users, ['users', 0, 'name'])); | ||
| var_dump(array_has($array, ['product', 'missing'])); | ||
|
|
||
| // Test with invalid segment type in array key | ||
| var_dump(array_has($array, ['product', new stdClass()])); | ||
|
|
||
| echo "Done"; | ||
| ?> | ||
| --EXPECT-- | ||
| *** Testing array_has() *** | ||
| bool(true) | ||
| bool(false) | ||
| bool(false) | ||
| bool(true) | ||
| bool(false) | ||
| bool(true) | ||
| bool(true) | ||
| bool(false) | ||
| bool(true) | ||
| bool(true) | ||
| bool(false) | ||
| bool(false) | ||
| bool(true) | ||
| bool(true) | ||
| bool(true) | ||
| bool(false) | ||
| bool(false) | ||
| 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.
This won't work properly with references I believe.
The function stub doesn't seem to return a reference, so this needs to be RETURN_COPY_DEREF (same issue is also present in other places).