Skip to content

Commit f3248f2

Browse files
Accept an array of strings in the $key parameter
1 parent e4789d0 commit f3248f2

File tree

6 files changed

+129
-55
lines changed

6 files changed

+129
-55
lines changed

ext/standard/array.c

Lines changed: 92 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6910,111 +6910,157 @@ PHP_FUNCTION(array_key_exists)
69106910
}
69116911
/* }}} */
69126912

6913-
/* {{{ Helper function to get a nested value from array using dot notation */
6914-
static zval* array_get_nested(HashTable *ht, const char *key, size_t key_len)
6913+
/* {{{ Helper function to get a nested value from array using an array of segments */
6914+
static zval* array_get_nested(HashTable *ht, HashTable *segments)
69156915
{
6916-
const char *dot;
6916+
zval *segment_val;
69176917
zval *current;
6918+
HashTable *current_ht;
6919+
uint32_t idx;
6920+
uint32_t num_segments;
69186921

6919-
/* Find the first dot in the key */
6920-
dot = memchr(key, '.', key_len);
6922+
current_ht = ht;
6923+
num_segments = zend_hash_num_elements(segments);
69216924

6922-
if (dot == NULL) {
6923-
/* No dot found, this is a simple key lookup */
6924-
zend_string *zkey = zend_string_init(key, key_len, 0);
6925-
current = zend_symtable_find(ht, zkey);
6926-
zend_string_release(zkey);
6927-
return current;
6928-
}
6925+
/* Iterate through each segment in the array */
6926+
for (idx = 0; idx < num_segments; idx++) {
6927+
/* Get the segment at the current index */
6928+
segment_val = zend_hash_index_find(segments, idx);
6929+
6930+
if (segment_val == NULL) {
6931+
/* Missing segment in array */
6932+
return NULL;
6933+
}
6934+
6935+
/* Segment must be a string or int */
6936+
if (Z_TYPE_P(segment_val) == IS_STRING) {
6937+
current = zend_symtable_find(current_ht, Z_STR_P(segment_val));
6938+
} else if (Z_TYPE_P(segment_val) == IS_LONG) {
6939+
current = zend_hash_index_find(current_ht, Z_LVAL_P(segment_val));
6940+
} else {
6941+
/* Invalid segment type */
6942+
return NULL;
6943+
}
6944+
6945+
/* If this is the last segment, return the result */
6946+
if (idx == num_segments - 1) {
6947+
return current;
6948+
}
69296949

6930-
/* We have a dot, so we need to recurse */
6931-
size_t segment_len = dot - key;
6932-
zend_string *segment = zend_string_init(key, segment_len, 0);
6933-
current = zend_symtable_find(ht, segment);
6934-
zend_string_release(segment);
6950+
/* Check if the segment exists and is an array for next iteration */
6951+
if (current == NULL || Z_TYPE_P(current) != IS_ARRAY) {
6952+
return NULL;
6953+
}
69356954

6936-
if (current == NULL || Z_TYPE_P(current) != IS_ARRAY) {
6937-
return NULL;
6955+
/* Move to the next level */
6956+
current_ht = Z_ARRVAL_P(current);
69386957
}
69396958

6940-
/* Recurse into the nested array with the remaining key */
6941-
return array_get_nested(Z_ARRVAL_P(current), dot + 1, key_len - segment_len - 1);
6959+
/* Empty segments array */
6960+
return NULL;
69426961
}
69436962
/* }}} */
69446963

69456964
/* {{{ Retrieves a value from a deeply nested array using "dot" notation */
69466965
PHP_FUNCTION(array_get)
69476966
{
6948-
HashTable *ht;
6967+
zval *array;
69496968
zval *key = NULL;
69506969
zval *default_value = NULL;
69516970
zval *result;
6971+
zval segments_array;
6972+
HashTable *ht;
69526973

69536974
ZEND_PARSE_PARAMETERS_START(2, 3)
6954-
Z_PARAM_ARRAY_HT(ht)
6975+
Z_PARAM_ARRAY(array)
69556976
Z_PARAM_ZVAL_OR_NULL(key)
69566977
Z_PARAM_OPTIONAL
69576978
Z_PARAM_ZVAL(default_value)
69586979
ZEND_PARSE_PARAMETERS_END();
69596980

69606981
/* If key is null, return the whole array */
69616982
if (key == NULL || Z_TYPE_P(key) == IS_NULL) {
6962-
ZVAL_ARR(return_value, zend_array_dup(ht));
6963-
return;
6983+
RETURN_COPY(array);
69646984
}
69656985

6966-
/* Handle string keys with dot notation */
6967-
if (Z_TYPE_P(key) == IS_STRING) {
6968-
result = array_get_nested(ht, Z_STRVAL_P(key), Z_STRLEN_P(key));
6986+
ht = Z_ARRVAL_P(array);
6987+
6988+
/* Handle array keys (array of segments) */
6989+
if (Z_TYPE_P(key) == IS_ARRAY) {
6990+
result = array_get_nested(ht, Z_ARRVAL_P(key));
69696991

69706992
if (result != NULL) {
6971-
ZVAL_COPY(return_value, result);
6972-
return;
6993+
RETURN_COPY(result);
69736994
}
69746995
}
6975-
/* Handle integer keys (no dot notation support) */
6996+
/* Handle string keys with dot notation - convert to array of segments */
6997+
else if (Z_TYPE_P(key) == IS_STRING) {
6998+
/* Use php_explode to split the string by '.' */
6999+
zend_string *delim = ZSTR_CHAR('.');
7000+
array_init(&segments_array);
7001+
php_explode(delim, Z_STR_P(key), &segments_array, ZEND_LONG_MAX);
7002+
7003+
result = array_get_nested(ht, Z_ARRVAL(segments_array));
7004+
7005+
zval_ptr_dtor(&segments_array);
7006+
7007+
if (result != NULL) {
7008+
RETURN_COPY(result);
7009+
}
7010+
}
7011+
/* Handle integer keys (simple lookup) */
69767012
else if (Z_TYPE_P(key) == IS_LONG) {
69777013
result = zend_hash_index_find(ht, Z_LVAL_P(key));
69787014

69797015
if (result != NULL) {
6980-
ZVAL_COPY(return_value, result);
6981-
return;
7016+
RETURN_COPY(result);
69827017
}
69837018
}
69847019

69857020
/* Key not found, return default value */
69867021
if (default_value != NULL) {
6987-
ZVAL_COPY(return_value, default_value);
6988-
} else {
6989-
RETVAL_NULL();
7022+
RETURN_COPY(default_value);
69907023
}
69917024
}
69927025
/* }}} */
69937026

69947027
/* {{{ Checks whether a given item exists in an array using "dot" notation */
69957028
PHP_FUNCTION(array_has)
69967029
{
6997-
HashTable *ht;
7030+
zval *array;
69987031
zval *key;
69997032
zval *result;
7033+
zval segments_array;
7034+
HashTable *ht;
70007035

70017036
ZEND_PARSE_PARAMETERS_START(2, 2)
7002-
Z_PARAM_ARRAY_HT(ht)
7037+
Z_PARAM_ARRAY(array)
70037038
Z_PARAM_ZVAL(key)
70047039
ZEND_PARSE_PARAMETERS_END();
70057040

7006-
/* Handle string keys with dot notation */
7007-
if (Z_TYPE_P(key) == IS_STRING) {
7008-
result = array_get_nested(ht, Z_STRVAL_P(key), Z_STRLEN_P(key));
7041+
ht = Z_ARRVAL_P(array);
7042+
7043+
/* Handle array keys (array of segments) */
7044+
if (Z_TYPE_P(key) == IS_ARRAY) {
7045+
result = array_get_nested(ht, Z_ARRVAL_P(key));
70097046
RETURN_BOOL(result != NULL);
70107047
}
7011-
/* Handle integer keys (no dot notation support) */
7012-
else if (Z_TYPE_P(key) == IS_LONG) {
7013-
RETURN_BOOL(zend_hash_index_exists(ht, Z_LVAL_P(key)));
7048+
/* Handle string keys with dot notation - convert to array of segments */
7049+
if (Z_TYPE_P(key) == IS_STRING) {
7050+
/* Use php_explode to split the string by '.' */
7051+
zend_string *delim = ZSTR_CHAR('.');
7052+
array_init(&segments_array);
7053+
php_explode(delim, Z_STR_P(key), &segments_array, ZEND_LONG_MAX);
7054+
7055+
result = array_get_nested(ht, Z_ARRVAL(segments_array));
7056+
7057+
zval_ptr_dtor(&segments_array);
7058+
RETURN_BOOL(result != NULL);
70147059
}
70157060

7016-
/* Invalid key type */
7017-
RETURN_FALSE;
7061+
/* Handle integer keys (simple lookup) */
7062+
ZEND_ASSERT(Z_TYPE_P(key) == IS_LONG);
7063+
RETURN_BOOL(zend_hash_index_exists(ht, Z_LVAL_P(key)));
70187064
}
70197065
/* }}} */
70207066

ext/standard/basic_functions.stub.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,12 +1906,12 @@ function key_exists($key, array $array): bool {}
19061906
/**
19071907
* @compile-time-eval
19081908
*/
1909-
function array_get(array $array, string|int|null $key = null, mixed $default = null): mixed {}
1909+
function array_get(array $array, string|int|array|null $key = null, mixed $default = null): mixed {}
19101910

19111911
/**
19121912
* @compile-time-eval
19131913
*/
1914-
function array_has(array $array, string|int $key): bool {}
1914+
function array_has(array $array, string|int|array $key): bool {}
19151915

19161916
/**
19171917
* @compile-time-eval

ext/standard/basic_functions_arginfo.h

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/standard/basic_functions_decl.h

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/standard/tests/array/array_get.phpt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ $users = ['users' => [['name' => 'Alice'], ['name' => 'Bob']]];
4242
var_dump(array_get($users, 'users.0.name'));
4343
var_dump(array_get($users, 'users.1.age', 70));
4444

45+
// Test with array key (equivalent to dot notation)
46+
var_dump(array_get($array, ['products', 'desk', 'price']));
47+
var_dump(array_get($simple, ['name']));
48+
var_dump(array_get($users, ['users', 0, 'name']));
49+
var_dump(array_get($array, ['products', 'chair', 'price'], 75));
50+
51+
// Test with invalid segment type in array key
52+
var_dump(array_get($array, ['products', new stdClass(), 'price'], 'invalid'));
53+
4554
echo "Done";
4655
?>
4756
--EXPECT--
@@ -60,4 +69,9 @@ int(50)
6069
NULL
6170
string(5) "Alice"
6271
int(70)
72+
int(100)
73+
string(4) "John"
74+
string(5) "Alice"
75+
int(75)
76+
string(7) "invalid"
6377
Done

ext/standard/tests/array/array_has.phpt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ var_dump(array_has($users, 'users.0.name'));
4141
var_dump(array_has($users, 'users.1.age'));
4242
var_dump(array_has($users, 'users.2.name'));
4343

44+
// Test with array key (equivalent to dot notation)
45+
var_dump(array_has($array, ['product', 'name']));
46+
var_dump(array_has($simple, ['name']));
47+
var_dump(array_has($users, ['users', 0, 'name']));
48+
var_dump(array_has($array, ['product', 'missing']));
49+
50+
// Test with invalid segment type in array key
51+
var_dump(array_has($array, ['product', new stdClass()]));
52+
4453
echo "Done";
4554
?>
4655
--EXPECT--
@@ -57,4 +66,9 @@ bool(true)
5766
bool(true)
5867
bool(false)
5968
bool(false)
69+
bool(true)
70+
bool(true)
71+
bool(true)
72+
bool(false)
73+
bool(false)
6074
Done

0 commit comments

Comments
 (0)