Skip to content

Commit fcc6611

Browse files
committed
Add support for non-scalar Iterator keys in foreach
RFC: https://wiki.php.net/rfc/foreach-non-scalar-keys
1 parent 8436342 commit fcc6611

37 files changed

+379
-501
lines changed

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ PHP 5.5 UPGRADE NOTES
8181
a string constant. (https://wiki.php.net/rfc/class_name_scalars)
8282
- Support for changing the process's title in CLI/CLI-Server SAPIs. (Keyur)
8383
(https://wiki.php.net/rfc/cli_process_title)
84+
- Added support for non-scalar Iterator keys in foreach.
85+
(https://wiki.php.net/rfc/foreach-non-scalar-keys).
8486

8587
========================================
8688
2. Changes in SAPI modules

UPGRADING.INTERNALS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ void zend_qsort_r(void *base, size_t nmemb, size_t siz, compare_r_func_t compare
6464
The extra argument it has (relatively to zend_qsort()) is passed to the
6565
comparison function.
6666

67+
d. get_current_key
68+
69+
The signature of the get_current_key iteration handler has been changed to:
70+
71+
void (*get_current_key)(zend_object_iterator *iter, zval *key TSRMLS_DC);
72+
73+
The key should be written into the zval* using the ZVAL_* macros.
74+
6775
========================
6876
2. Build system changes
6977
========================
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
--TEST--
2+
Generators can return non-scalar keys
3+
--FILE--
4+
<?php
5+
6+
function gen() {
7+
yield [1, 2, 3] => [4, 5, 6];
8+
yield (object) ['a' => 'b'] => (object) ['b' => 'a'];
9+
yield 3.14 => 2.73;
10+
yield false => true;
11+
yield true => false;
12+
yield null => null;
13+
}
14+
15+
foreach (gen() as $k => $v) {
16+
var_dump($k, $v);
17+
}
18+
19+
?>
20+
--EXPECT--
21+
array(3) {
22+
[0]=>
23+
int(1)
24+
[1]=>
25+
int(2)
26+
[2]=>
27+
int(3)
28+
}
29+
array(3) {
30+
[0]=>
31+
int(4)
32+
[1]=>
33+
int(5)
34+
[2]=>
35+
int(6)
36+
}
37+
object(stdClass)#3 (1) {
38+
["a"]=>
39+
string(1) "b"
40+
}
41+
object(stdClass)#4 (1) {
42+
["b"]=>
43+
string(1) "a"
44+
}
45+
float(3.14)
46+
float(2.73)
47+
bool(false)
48+
bool(true)
49+
bool(true)
50+
bool(false)
51+
NULL
52+
NULL

Zend/zend.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,6 @@ void zend_error_noreturn(int type, const char *format, ...) __attribute__ ((nore
299299
/*
300300
* zval
301301
*/
302-
typedef struct _zval_struct zval;
303302
typedef struct _zend_class_entry zend_class_entry;
304303

305304
typedef struct _zend_guard {

Zend/zend_API.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,6 +1502,40 @@ ZEND_API int add_get_index_stringl(zval *arg, ulong index, const char *str, uint
15021502
}
15031503
/* }}} */
15041504

1505+
ZEND_API int array_set_zval_key(HashTable *ht, zval *key, zval *value) /* {{{ */
1506+
{
1507+
int result;
1508+
1509+
switch (Z_TYPE_P(key)) {
1510+
case IS_STRING:
1511+
result = zend_symtable_update(ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, &value, sizeof(zval *), NULL);
1512+
break;
1513+
case IS_NULL:
1514+
result = zend_symtable_update(ht, "", 1, &value, sizeof(zval *), NULL);
1515+
break;
1516+
case IS_RESOURCE:
1517+
zend_error(E_STRICT, "Resource ID#%ld used as offset, casting to integer (%ld)", Z_LVAL_P(key), Z_LVAL_P(key));
1518+
/* break missing intentionally */
1519+
case IS_BOOL:
1520+
case IS_LONG:
1521+
result = zend_hash_index_update(ht, Z_LVAL_P(key), &value, sizeof(zval *), NULL);
1522+
break;
1523+
case IS_DOUBLE:
1524+
result = zend_hash_index_update(ht, zend_dval_to_lval(Z_LVAL_P(key)), &value, sizeof(zval *), NULL);
1525+
break;
1526+
default:
1527+
zend_error(E_WARNING, "Illegal offset type");
1528+
result = FAILURE;
1529+
}
1530+
1531+
if (result == SUCCESS) {
1532+
Z_ADDREF_P(value);
1533+
}
1534+
1535+
return result;
1536+
}
1537+
/* }}} */
1538+
15051539
ZEND_API int add_property_long_ex(zval *arg, const char *key, uint key_len, long n TSRMLS_DC) /* {{{ */
15061540
{
15071541
zval *tmp;

Zend/zend_API.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,8 @@ ZEND_API int add_get_index_double(zval *arg, ulong idx, double d, void **dest);
426426
ZEND_API int add_get_index_string(zval *arg, ulong idx, const char *str, void **dest, int duplicate);
427427
ZEND_API int add_get_index_stringl(zval *arg, ulong idx, const char *str, uint length, void **dest, int duplicate);
428428

429+
ZEND_API int array_set_zval_key(HashTable *ht, zval *key, zval *value);
430+
429431
ZEND_API int add_property_long_ex(zval *arg, const char *key, uint key_len, long l TSRMLS_DC);
430432
ZEND_API int add_property_null_ex(zval *arg, const char *key, uint key_len TSRMLS_DC);
431433
ZEND_API int add_property_bool_ex(zval *arg, const char *key, uint key_len, int b TSRMLS_DC);

Zend/zend_generators.c

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -755,31 +755,17 @@ static void zend_generator_iterator_get_data(zend_object_iterator *iterator, zva
755755
}
756756
/* }}} */
757757

758-
static int zend_generator_iterator_get_key(zend_object_iterator *iterator, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC) /* {{{ */
758+
static void zend_generator_iterator_get_key(zend_object_iterator *iterator, zval *key TSRMLS_DC) /* {{{ */
759759
{
760760
zend_generator *generator = (zend_generator *) iterator->data;
761761

762762
zend_generator_ensure_initialized(generator TSRMLS_CC);
763763

764-
if (!generator->key) {
765-
return HASH_KEY_NON_EXISTANT;
766-
}
767-
768-
if (Z_TYPE_P(generator->key) == IS_LONG) {
769-
*int_key = Z_LVAL_P(generator->key);
770-
return HASH_KEY_IS_LONG;
771-
}
772-
773-
if (Z_TYPE_P(generator->key) == IS_STRING) {
774-
*str_key = estrndup(Z_STRVAL_P(generator->key), Z_STRLEN_P(generator->key));
775-
*str_key_len = Z_STRLEN_P(generator->key) + 1;
776-
return HASH_KEY_IS_STRING;
764+
if (generator->key) {
765+
ZVAL_ZVAL(key, generator->key, 1, 0);
766+
} else {
767+
ZVAL_NULL(key);
777768
}
778-
779-
/* Waiting for Etienne's patch to allow arbitrary zval keys. Until then
780-
* error out on non-int and non-string keys. */
781-
zend_error_noreturn(E_ERROR, "Currently only int and string keys can be yielded");
782-
return HASH_KEY_NON_EXISTANT; /* Nerver reached */
783769
}
784770
/* }}} */
785771

Zend/zend_hash.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,24 @@ ZEND_API int zend_hash_get_current_key_ex(const HashTable *ht, char **str_index,
11711171
return HASH_KEY_NON_EXISTANT;
11721172
}
11731173

1174+
ZEND_API void zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos) {
1175+
Bucket *p;
1176+
1177+
IS_CONSISTENT(ht);
1178+
1179+
p = pos ? (*pos) : ht->pInternalPointer;
1180+
1181+
if (!p) {
1182+
Z_TYPE_P(key) = IS_NULL;
1183+
} else if (p->nKeyLength) {
1184+
Z_TYPE_P(key) = IS_STRING;
1185+
Z_STRVAL_P(key) = IS_INTERNED(p->arKey) ? (char *) p->arKey : estrndup(p->arKey, p->nKeyLength - 1);
1186+
Z_STRLEN_P(key) = p->nKeyLength - 1;
1187+
} else {
1188+
Z_TYPE_P(key) = IS_LONG;
1189+
Z_LVAL_P(key) = p->h;
1190+
}
1191+
}
11741192

11751193
ZEND_API int zend_hash_get_current_key_type_ex(HashTable *ht, HashPosition *pos)
11761194
{

Zend/zend_hash.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,13 @@ ZEND_API int zend_hash_quick_exists(const HashTable *ht, const char *arKey, uint
170170
ZEND_API int zend_hash_index_exists(const HashTable *ht, ulong h);
171171
ZEND_API ulong zend_hash_next_free_element(const HashTable *ht);
172172

173-
174173
/* traversing */
175174
#define zend_hash_has_more_elements_ex(ht, pos) \
176175
(zend_hash_get_current_key_type_ex(ht, pos) == HASH_KEY_NON_EXISTANT ? FAILURE : SUCCESS)
177176
ZEND_API int zend_hash_move_forward_ex(HashTable *ht, HashPosition *pos);
178177
ZEND_API int zend_hash_move_backwards_ex(HashTable *ht, HashPosition *pos);
179178
ZEND_API int zend_hash_get_current_key_ex(const HashTable *ht, char **str_index, uint *str_length, ulong *num_index, zend_bool duplicate, HashPosition *pos);
179+
ZEND_API void zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos);
180180
ZEND_API int zend_hash_get_current_key_type_ex(HashTable *ht, HashPosition *pos);
181181
ZEND_API int zend_hash_get_current_data_ex(HashTable *ht, void **pData, HashPosition *pos);
182182
ZEND_API void zend_hash_internal_pointer_reset_ex(HashTable *ht, HashPosition *pos);
@@ -199,6 +199,8 @@ ZEND_API int zend_hash_set_pointer(HashTable *ht, const HashPointer *ptr);
199199
zend_hash_move_backwards_ex(ht, NULL)
200200
#define zend_hash_get_current_key(ht, str_index, num_index, duplicate) \
201201
zend_hash_get_current_key_ex(ht, str_index, NULL, num_index, duplicate, NULL)
202+
#define zend_hash_get_current_key_zval(ht, key) \
203+
zend_hash_get_current_key_zval_ex(ht, key, NULL)
202204
#define zend_hash_get_current_key_type(ht) \
203205
zend_hash_get_current_key_type_ex(ht, NULL)
204206
#define zend_hash_get_current_data(ht, pData) \

Zend/zend_interfaces.c

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -195,50 +195,24 @@ static int zend_user_it_get_current_key_default(zend_object_iterator *_iter, cha
195195
/* }}} */
196196

197197
/* {{{ zend_user_it_get_current_key */
198-
ZEND_API int zend_user_it_get_current_key(zend_object_iterator *_iter, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC)
198+
ZEND_API void zend_user_it_get_current_key(zend_object_iterator *_iter, zval *key TSRMLS_DC)
199199
{
200200
zend_user_iterator *iter = (zend_user_iterator*)_iter;
201201
zval *object = (zval*)iter->it.data;
202202
zval *retval;
203203

204204
zend_call_method_with_0_params(&object, iter->ce, &iter->ce->iterator_funcs.zf_key, "key", &retval);
205205

206-
if (!retval) {
207-
*int_key = 0;
208-
if (!EG(exception))
209-
{
206+
if (retval) {
207+
ZVAL_ZVAL(key, retval, 1, 1);
208+
} else {
209+
if (!EG(exception)) {
210210
zend_error(E_WARNING, "Nothing returned from %s::key()", iter->ce->name);
211211
}
212-
return HASH_KEY_IS_LONG;
213-
}
214-
switch (Z_TYPE_P(retval)) {
215-
default:
216-
zend_error(E_WARNING, "Illegal type returned from %s::key()", iter->ce->name);
217-
case IS_NULL:
218-
*int_key = 0;
219-
zval_ptr_dtor(&retval);
220-
return HASH_KEY_IS_LONG;
221-
222-
case IS_STRING:
223-
*str_key = estrndup(Z_STRVAL_P(retval), Z_STRLEN_P(retval));
224-
*str_key_len = Z_STRLEN_P(retval)+1;
225-
zval_ptr_dtor(&retval);
226-
return HASH_KEY_IS_STRING;
227-
228-
case IS_DOUBLE:
229-
*int_key = (long)Z_DVAL_P(retval);
230-
zval_ptr_dtor(&retval);
231-
return HASH_KEY_IS_LONG;
232212

233-
case IS_RESOURCE:
234-
case IS_BOOL:
235-
case IS_LONG:
236-
*int_key = (long)Z_LVAL_P(retval);
237-
zval_ptr_dtor(&retval);
238-
return HASH_KEY_IS_LONG;
213+
ZVAL_LONG(key, 0);
239214
}
240215
}
241-
/* }}} */
242216

243217
/* {{{ zend_user_it_move_forward */
244218
ZEND_API void zend_user_it_move_forward(zend_object_iterator *_iter TSRMLS_DC)

Zend/zend_interfaces.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ ZEND_API zval* zend_call_method(zval **object_pp, zend_class_entry *obj_ce, zend
5151

5252
ZEND_API void zend_user_it_rewind(zend_object_iterator *_iter TSRMLS_DC);
5353
ZEND_API int zend_user_it_valid(zend_object_iterator *_iter TSRMLS_DC);
54-
ZEND_API int zend_user_it_get_current_key(zend_object_iterator *_iter, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC);
54+
ZEND_API void zend_user_it_get_current_key(zend_object_iterator *_iter, zval *key TSRMLS_DC);
5555
ZEND_API void zend_user_it_get_current_data(zend_object_iterator *_iter, zval ***data TSRMLS_DC);
5656
ZEND_API void zend_user_it_move_forward(zend_object_iterator *_iter TSRMLS_DC);
5757
ZEND_API void zend_user_it_invalidate_current(zend_object_iterator *_iter TSRMLS_DC);

Zend/zend_iterators.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ typedef struct _zend_object_iterator_funcs {
3838
/* fetch the item data for the current element */
3939
void (*get_current_data)(zend_object_iterator *iter, zval ***data TSRMLS_DC);
4040

41-
/* fetch the key for the current element (return HASH_KEY_IS_STRING or HASH_KEY_IS_LONG) (optional, may be NULL) */
42-
int (*get_current_key)(zend_object_iterator *iter, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC);
41+
/* fetch the key for the current element (optional, may be NULL). The key
42+
* should be written into the provided zval* using the ZVAL_* macros. If
43+
* this handler is not provided auto-incrementing integer keys will be
44+
* used. */
45+
void (*get_current_key)(zend_object_iterator *iter, zval *key TSRMLS_DC);
4346

4447
/* step forwards to next element */
4548
void (*move_forward)(zend_object_iterator *iter TSRMLS_DC);

Zend/zend_types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ typedef unsigned long zend_uintptr_t;
5252

5353
typedef unsigned int zend_object_handle;
5454
typedef struct _zend_object_handlers zend_object_handlers;
55+
typedef struct _zval_struct zval;
5556

5657
typedef struct _zend_object_value {
5758
zend_object_handle handle;

0 commit comments

Comments
 (0)