Skip to content

Commit 10037a7

Browse files
Accept an array of strings in the $key parameter
1 parent 3c71ecf commit 10037a7

6 files changed

Lines changed: 130 additions & 56 deletions

File tree

ext/standard/array.c

Lines changed: 92 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6915,111 +6915,157 @@ PHP_FUNCTION(array_key_exists)
69156915
}
69166916
/* }}} */
69176917

6918-
/* {{{ Helper function to get a nested value from array using dot notation */
6919-
static zval* array_get_nested(HashTable *ht, const char *key, size_t key_len)
6918+
/* {{{ Helper function to get a nested value from array using an array of segments */
6919+
static zval* array_get_nested(HashTable *ht, HashTable *segments)
69206920
{
6921-
const char *dot;
6921+
zval *segment_val;
69226922
zval *current;
6923+
HashTable *current_ht;
6924+
uint32_t idx;
6925+
uint32_t num_segments;
69236926

6924-
/* Find the first dot in the key */
6925-
dot = memchr(key, '.', key_len);
6927+
current_ht = ht;
6928+
num_segments = zend_hash_num_elements(segments);
69266929

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

6935-
/* We have a dot, so we need to recurse */
6936-
size_t segment_len = dot - key;
6937-
zend_string *segment = zend_string_init(key, segment_len, 0);
6938-
current = zend_symtable_find(ht, segment);
6939-
zend_string_release(segment);
6955+
/* Check if the segment exists and is an array for next iteration */
6956+
if (current == NULL || Z_TYPE_P(current) != IS_ARRAY) {
6957+
return NULL;
6958+
}
69406959

6941-
if (current == NULL || Z_TYPE_P(current) != IS_ARRAY) {
6942-
return NULL;
6960+
/* Move to the next level */
6961+
current_ht = Z_ARRVAL_P(current);
69436962
}
69446963

6945-
/* Recurse into the nested array with the remaining key */
6946-
return array_get_nested(Z_ARRVAL_P(current), dot + 1, key_len - segment_len - 1);
6964+
/* Empty segments array */
6965+
return NULL;
69476966
}
69486967
/* }}} */
69496968

69506969
/* {{{ Retrieves a value from a deeply nested array using "dot" notation */
69516970
PHP_FUNCTION(array_get)
69526971
{
6953-
HashTable *ht;
6972+
zval *array;
69546973
zval *key = NULL;
69556974
zval *default_value = NULL;
69566975
zval *result;
6976+
zval segments_array;
6977+
HashTable *ht;
69576978

69586979
ZEND_PARSE_PARAMETERS_START(2, 3)
6959-
Z_PARAM_ARRAY_HT(ht)
6980+
Z_PARAM_ARRAY(array)
69606981
Z_PARAM_ZVAL_OR_NULL(key)
69616982
Z_PARAM_OPTIONAL
69626983
Z_PARAM_ZVAL(default_value)
69636984
ZEND_PARSE_PARAMETERS_END();
69646985

69656986
/* If key is null, return the whole array */
69666987
if (key == NULL || Z_TYPE_P(key) == IS_NULL) {
6967-
ZVAL_ARR(return_value, zend_array_dup(ht));
6968-
return;
6988+
RETURN_COPY(array);
69696989
}
69706990

6971-
/* Handle string keys with dot notation */
6972-
if (Z_TYPE_P(key) == IS_STRING) {
6973-
result = array_get_nested(ht, Z_STRVAL_P(key), Z_STRLEN_P(key));
6991+
ht = Z_ARRVAL_P(array);
6992+
6993+
/* Handle array keys (array of segments) */
6994+
if (Z_TYPE_P(key) == IS_ARRAY) {
6995+
result = array_get_nested(ht, Z_ARRVAL_P(key));
69746996

69756997
if (result != NULL) {
6976-
ZVAL_COPY(return_value, result);
6977-
return;
6998+
RETURN_COPY(result);
69786999
}
69797000
}
6980-
/* Handle integer keys (no dot notation support) */
7001+
/* Handle string keys with dot notation - convert to array of segments */
7002+
else if (Z_TYPE_P(key) == IS_STRING) {
7003+
/* Use php_explode to split the string by '.' */
7004+
zend_string *delim = ZSTR_CHAR('.');
7005+
array_init(&segments_array);
7006+
php_explode(delim, Z_STR_P(key), &segments_array, ZEND_LONG_MAX);
7007+
7008+
result = array_get_nested(ht, Z_ARRVAL(segments_array));
7009+
7010+
zval_ptr_dtor(&segments_array);
7011+
7012+
if (result != NULL) {
7013+
RETURN_COPY(result);
7014+
}
7015+
}
7016+
/* Handle integer keys (simple lookup) */
69817017
else if (Z_TYPE_P(key) == IS_LONG) {
69827018
result = zend_hash_index_find(ht, Z_LVAL_P(key));
69837019

69847020
if (result != NULL) {
6985-
ZVAL_COPY(return_value, result);
6986-
return;
7021+
RETURN_COPY(result);
69877022
}
69887023
}
69897024

69907025
/* Key not found, return default value */
69917026
if (default_value != NULL) {
6992-
ZVAL_COPY(return_value, default_value);
6993-
} else {
6994-
RETVAL_NULL();
7027+
RETURN_COPY(default_value);
69957028
}
69967029
}
69977030
/* }}} */
69987031

69997032
/* {{{ Checks whether a given item exists in an array using "dot" notation */
70007033
PHP_FUNCTION(array_has)
70017034
{
7002-
HashTable *ht;
7035+
zval *array;
70037036
zval *key;
70047037
zval *result;
7038+
zval segments_array;
7039+
HashTable *ht;
70057040

70067041
ZEND_PARSE_PARAMETERS_START(2, 2)
7007-
Z_PARAM_ARRAY_HT(ht)
7042+
Z_PARAM_ARRAY(array)
70087043
Z_PARAM_ZVAL(key)
70097044
ZEND_PARSE_PARAMETERS_END();
70107045

7011-
/* Handle string keys with dot notation */
7012-
if (Z_TYPE_P(key) == IS_STRING) {
7013-
result = array_get_nested(ht, Z_STRVAL_P(key), Z_STRLEN_P(key));
7046+
ht = Z_ARRVAL_P(array);
7047+
7048+
/* Handle array keys (array of segments) */
7049+
if (Z_TYPE_P(key) == IS_ARRAY) {
7050+
result = array_get_nested(ht, Z_ARRVAL_P(key));
70147051
RETURN_BOOL(result != NULL);
70157052
}
7016-
/* Handle integer keys (no dot notation support) */
7017-
else if (Z_TYPE_P(key) == IS_LONG) {
7018-
RETURN_BOOL(zend_hash_index_exists(ht, Z_LVAL_P(key)));
7053+
/* Handle string keys with dot notation - convert to array of segments */
7054+
if (Z_TYPE_P(key) == IS_STRING) {
7055+
/* Use php_explode to split the string by '.' */
7056+
zend_string *delim = ZSTR_CHAR('.');
7057+
array_init(&segments_array);
7058+
php_explode(delim, Z_STR_P(key), &segments_array, ZEND_LONG_MAX);
7059+
7060+
result = array_get_nested(ht, Z_ARRVAL(segments_array));
7061+
7062+
zval_ptr_dtor(&segments_array);
7063+
RETURN_BOOL(result != NULL);
70197064
}
70207065

7021-
/* Invalid key type */
7022-
RETURN_FALSE;
7066+
/* Handle integer keys (simple lookup) */
7067+
ZEND_ASSERT(Z_TYPE_P(key) == IS_LONG);
7068+
RETURN_BOOL(zend_hash_index_exists(ht, Z_LVAL_P(key)));
70237069
}
70247070
/* }}} */
70257071

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: 4 additions & 4 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)