Skip to content

Commit fdfd3b1

Browse files
Accept an array of strings in the $key parameter
1 parent 7134302 commit fdfd3b1

4 files changed

Lines changed: 122 additions & 48 deletions

File tree

ext/standard/array.c

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

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

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

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

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

6939-
if (current == NULL || Z_TYPE_P(current) != IS_ARRAY) {
6940-
return NULL;
6958+
/* Move to the next level */
6959+
current_ht = Z_ARRVAL_P(current);
69416960
}
69426961

6943-
/* Recurse into the nested array with the remaining key */
6944-
return array_get_nested(Z_ARRVAL_P(current), dot + 1, key_len - segment_len - 1);
6962+
/* Empty segments array */
6963+
return NULL;
69456964
}
69466965
/* }}} */
69476966

69486967
/* {{{ Retrieves a value from a deeply nested array using "dot" notation */
69496968
PHP_FUNCTION(array_get)
69506969
{
6951-
HashTable *ht;
6970+
zval *array;
69526971
zval *key = NULL;
69536972
zval *default_value = NULL;
69546973
zval *result;
6974+
zval segments_array;
6975+
HashTable *ht;
69556976

69566977
ZEND_PARSE_PARAMETERS_START(2, 3)
6957-
Z_PARAM_ARRAY_HT(ht)
6978+
Z_PARAM_ARRAY(array)
69586979
Z_PARAM_ZVAL_OR_NULL(key)
69596980
Z_PARAM_OPTIONAL
69606981
Z_PARAM_ZVAL(default_value)
69616982
ZEND_PARSE_PARAMETERS_END();
69626983

69636984
/* If key is null, return the whole array */
69646985
if (key == NULL || Z_TYPE_P(key) == IS_NULL) {
6965-
ZVAL_ARR(return_value, zend_array_dup(ht));
6966-
return;
6986+
RETURN_COPY(array);
69676987
}
69686988

6969-
/* Handle string keys with dot notation */
6970-
if (Z_TYPE_P(key) == IS_STRING) {
6971-
result = array_get_nested(ht, Z_STRVAL_P(key), Z_STRLEN_P(key));
6989+
ht = Z_ARRVAL_P(array);
6990+
6991+
/* Handle array keys (array of segments) */
6992+
if (Z_TYPE_P(key) == IS_ARRAY) {
6993+
result = array_get_nested(ht, Z_ARRVAL_P(key));
69726994

69736995
if (result != NULL) {
6974-
ZVAL_COPY(return_value, result);
6975-
return;
6996+
RETURN_COPY(result);
69766997
}
69776998
}
6978-
/* Handle integer keys (no dot notation support) */
6999+
/* Handle string keys with dot notation - convert to array of segments */
7000+
else if (Z_TYPE_P(key) == IS_STRING) {
7001+
/* Use php_explode to split the string by '.' */
7002+
zend_string *delim = ZSTR_CHAR('.');
7003+
array_init(&segments_array);
7004+
php_explode(delim, Z_STR_P(key), &segments_array, ZEND_LONG_MAX);
7005+
7006+
result = array_get_nested(ht, Z_ARRVAL(segments_array));
7007+
7008+
zval_ptr_dtor(&segments_array);
7009+
7010+
if (result != NULL) {
7011+
RETURN_COPY(result);
7012+
}
7013+
}
7014+
/* Handle integer keys (simple lookup) */
69797015
else if (Z_TYPE_P(key) == IS_LONG) {
69807016
result = zend_hash_index_find(ht, Z_LVAL_P(key));
69817017

69827018
if (result != NULL) {
6983-
ZVAL_COPY(return_value, result);
6984-
return;
7019+
RETURN_COPY(result);
69857020
}
69867021
}
69877022

69887023
/* Key not found, return default value */
69897024
if (default_value != NULL) {
6990-
ZVAL_COPY(return_value, default_value);
6991-
} else {
6992-
RETVAL_NULL();
7025+
RETURN_COPY(default_value);
69937026
}
69947027
}
69957028
/* }}} */
69967029

69977030
/* {{{ Checks whether a given item exists in an array using "dot" notation */
69987031
PHP_FUNCTION(array_has)
69997032
{
7000-
HashTable *ht;
7033+
zval *array;
70017034
zval *key;
70027035
zval *result;
7036+
zval segments_array;
7037+
HashTable *ht;
70037038

70047039
ZEND_PARSE_PARAMETERS_START(2, 2)
7005-
Z_PARAM_ARRAY_HT(ht)
7040+
Z_PARAM_ARRAY(array)
70067041
Z_PARAM_ZVAL(key)
70077042
ZEND_PARSE_PARAMETERS_END();
70087043

7009-
/* Handle string keys with dot notation */
7010-
if (Z_TYPE_P(key) == IS_STRING) {
7011-
result = array_get_nested(ht, Z_STRVAL_P(key), Z_STRLEN_P(key));
7044+
ht = Z_ARRVAL_P(array);
7045+
7046+
/* Handle array keys (array of segments) */
7047+
if (Z_TYPE_P(key) == IS_ARRAY) {
7048+
result = array_get_nested(ht, Z_ARRVAL_P(key));
70127049
RETURN_BOOL(result != NULL);
70137050
}
7014-
/* Handle integer keys (no dot notation support) */
7015-
else if (Z_TYPE_P(key) == IS_LONG) {
7016-
RETURN_BOOL(zend_hash_index_exists(ht, Z_LVAL_P(key)));
7051+
/* Handle string keys with dot notation - convert to array of segments */
7052+
if (Z_TYPE_P(key) == IS_STRING) {
7053+
/* Use php_explode to split the string by '.' */
7054+
zend_string *delim = ZSTR_CHAR('.');
7055+
array_init(&segments_array);
7056+
php_explode(delim, Z_STR_P(key), &segments_array, ZEND_LONG_MAX);
7057+
7058+
result = array_get_nested(ht, Z_ARRVAL(segments_array));
7059+
7060+
zval_ptr_dtor(&segments_array);
7061+
RETURN_BOOL(result != NULL);
70177062
}
70187063

7019-
/* Invalid key type */
7020-
RETURN_FALSE;
7064+
/* Handle integer keys (simple lookup) */
7065+
ZEND_ASSERT(Z_TYPE_P(key) == IS_LONG);
7066+
RETURN_BOOL(zend_hash_index_exists(ht, Z_LVAL_P(key)));
70217067
}
70227068
/* }}} */
70237069

ext/standard/basic_functions.stub.php

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

19151915
/**
19161916
* @compile-time-eval
19171917
*/
1918-
function array_has(array $array, string|int $key): bool {}
1918+
function array_has(array $array, string|int|array $key): bool {}
19191919

19201920
/**
19211921
* @compile-time-eval

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)