Skip to content

Commit

Permalink
Add support for non-scalar Iterator keys in foreach
Browse files Browse the repository at this point in the history
  • Loading branch information
nikic committed Mar 12, 2013
1 parent 8436342 commit fcc6611
Show file tree
Hide file tree
Showing 37 changed files with 379 additions and 501 deletions.
2 changes: 2 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ PHP 5.5 UPGRADE NOTES
a string constant. (https://wiki.php.net/rfc/class_name_scalars)
- Support for changing the process's title in CLI/CLI-Server SAPIs. (Keyur)
(https://wiki.php.net/rfc/cli_process_title)
- Added support for non-scalar Iterator keys in foreach.
(https://wiki.php.net/rfc/foreach-non-scalar-keys).

========================================
2. Changes in SAPI modules
Expand Down
8 changes: 8 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ void zend_qsort_r(void *base, size_t nmemb, size_t siz, compare_r_func_t compare
The extra argument it has (relatively to zend_qsort()) is passed to the
comparison function.

d. get_current_key

The signature of the get_current_key iteration handler has been changed to:

void (*get_current_key)(zend_object_iterator *iter, zval *key TSRMLS_DC);

The key should be written into the zval* using the ZVAL_* macros.

========================
2. Build system changes
========================
Expand Down
52 changes: 52 additions & 0 deletions Zend/tests/generators/generator_with_nonscalar_keys.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
--TEST--
Generators can return non-scalar keys
--FILE--
<?php

function gen() {
yield [1, 2, 3] => [4, 5, 6];
yield (object) ['a' => 'b'] => (object) ['b' => 'a'];
yield 3.14 => 2.73;
yield false => true;
yield true => false;
yield null => null;
}

foreach (gen() as $k => $v) {
var_dump($k, $v);
}

?>
--EXPECT--
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
array(3) {
[0]=>
int(4)
[1]=>
int(5)
[2]=>
int(6)
}
object(stdClass)#3 (1) {
["a"]=>
string(1) "b"
}
object(stdClass)#4 (1) {
["b"]=>
string(1) "a"
}
float(3.14)
float(2.73)
bool(false)
bool(true)
bool(true)
bool(false)
NULL
NULL
1 change: 0 additions & 1 deletion Zend/zend.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ void zend_error_noreturn(int type, const char *format, ...) __attribute__ ((nore
/*
* zval
*/
typedef struct _zval_struct zval;
typedef struct _zend_class_entry zend_class_entry;

typedef struct _zend_guard {
Expand Down
34 changes: 34 additions & 0 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -1502,6 +1502,40 @@ ZEND_API int add_get_index_stringl(zval *arg, ulong index, const char *str, uint
}
/* }}} */

ZEND_API int array_set_zval_key(HashTable *ht, zval *key, zval *value) /* {{{ */
{
int result;

switch (Z_TYPE_P(key)) {
case IS_STRING:
result = zend_symtable_update(ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, &value, sizeof(zval *), NULL);
break;
case IS_NULL:
result = zend_symtable_update(ht, "", 1, &value, sizeof(zval *), NULL);
break;
case IS_RESOURCE:
zend_error(E_STRICT, "Resource ID#%ld used as offset, casting to integer (%ld)", Z_LVAL_P(key), Z_LVAL_P(key));
/* break missing intentionally */
case IS_BOOL:
case IS_LONG:
result = zend_hash_index_update(ht, Z_LVAL_P(key), &value, sizeof(zval *), NULL);
break;
case IS_DOUBLE:
result = zend_hash_index_update(ht, zend_dval_to_lval(Z_LVAL_P(key)), &value, sizeof(zval *), NULL);
break;
default:
zend_error(E_WARNING, "Illegal offset type");
result = FAILURE;
}

if (result == SUCCESS) {
Z_ADDREF_P(value);
}

return result;
}
/* }}} */

ZEND_API int add_property_long_ex(zval *arg, const char *key, uint key_len, long n TSRMLS_DC) /* {{{ */
{
zval *tmp;
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ ZEND_API int add_get_index_double(zval *arg, ulong idx, double d, void **dest);
ZEND_API int add_get_index_string(zval *arg, ulong idx, const char *str, void **dest, int duplicate);
ZEND_API int add_get_index_stringl(zval *arg, ulong idx, const char *str, uint length, void **dest, int duplicate);

ZEND_API int array_set_zval_key(HashTable *ht, zval *key, zval *value);

ZEND_API int add_property_long_ex(zval *arg, const char *key, uint key_len, long l TSRMLS_DC);
ZEND_API int add_property_null_ex(zval *arg, const char *key, uint key_len TSRMLS_DC);
ZEND_API int add_property_bool_ex(zval *arg, const char *key, uint key_len, int b TSRMLS_DC);
Expand Down
24 changes: 5 additions & 19 deletions Zend/zend_generators.c
Original file line number Diff line number Diff line change
Expand Up @@ -755,31 +755,17 @@ static void zend_generator_iterator_get_data(zend_object_iterator *iterator, zva
}
/* }}} */

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

zend_generator_ensure_initialized(generator TSRMLS_CC);

if (!generator->key) {
return HASH_KEY_NON_EXISTANT;
}

if (Z_TYPE_P(generator->key) == IS_LONG) {
*int_key = Z_LVAL_P(generator->key);
return HASH_KEY_IS_LONG;
}

if (Z_TYPE_P(generator->key) == IS_STRING) {
*str_key = estrndup(Z_STRVAL_P(generator->key), Z_STRLEN_P(generator->key));
*str_key_len = Z_STRLEN_P(generator->key) + 1;
return HASH_KEY_IS_STRING;
if (generator->key) {
ZVAL_ZVAL(key, generator->key, 1, 0);
} else {
ZVAL_NULL(key);
}

/* Waiting for Etienne's patch to allow arbitrary zval keys. Until then
* error out on non-int and non-string keys. */
zend_error_noreturn(E_ERROR, "Currently only int and string keys can be yielded");
return HASH_KEY_NON_EXISTANT; /* Nerver reached */
}
/* }}} */

Expand Down
18 changes: 18 additions & 0 deletions Zend/zend_hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,24 @@ ZEND_API int zend_hash_get_current_key_ex(const HashTable *ht, char **str_index,
return HASH_KEY_NON_EXISTANT;
}

ZEND_API void zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos) {
Bucket *p;

IS_CONSISTENT(ht);

p = pos ? (*pos) : ht->pInternalPointer;

if (!p) {
Z_TYPE_P(key) = IS_NULL;
} else if (p->nKeyLength) {
Z_TYPE_P(key) = IS_STRING;
Z_STRVAL_P(key) = IS_INTERNED(p->arKey) ? (char *) p->arKey : estrndup(p->arKey, p->nKeyLength - 1);
Z_STRLEN_P(key) = p->nKeyLength - 1;
} else {
Z_TYPE_P(key) = IS_LONG;
Z_LVAL_P(key) = p->h;
}
}

ZEND_API int zend_hash_get_current_key_type_ex(HashTable *ht, HashPosition *pos)
{
Expand Down
4 changes: 3 additions & 1 deletion Zend/zend_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,13 @@ ZEND_API int zend_hash_quick_exists(const HashTable *ht, const char *arKey, uint
ZEND_API int zend_hash_index_exists(const HashTable *ht, ulong h);
ZEND_API ulong zend_hash_next_free_element(const HashTable *ht);


/* traversing */
#define zend_hash_has_more_elements_ex(ht, pos) \
(zend_hash_get_current_key_type_ex(ht, pos) == HASH_KEY_NON_EXISTANT ? FAILURE : SUCCESS)
ZEND_API int zend_hash_move_forward_ex(HashTable *ht, HashPosition *pos);
ZEND_API int zend_hash_move_backwards_ex(HashTable *ht, HashPosition *pos);
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);
ZEND_API void zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos);
ZEND_API int zend_hash_get_current_key_type_ex(HashTable *ht, HashPosition *pos);
ZEND_API int zend_hash_get_current_data_ex(HashTable *ht, void **pData, HashPosition *pos);
ZEND_API void zend_hash_internal_pointer_reset_ex(HashTable *ht, HashPosition *pos);
Expand All @@ -199,6 +199,8 @@ ZEND_API int zend_hash_set_pointer(HashTable *ht, const HashPointer *ptr);
zend_hash_move_backwards_ex(ht, NULL)
#define zend_hash_get_current_key(ht, str_index, num_index, duplicate) \
zend_hash_get_current_key_ex(ht, str_index, NULL, num_index, duplicate, NULL)
#define zend_hash_get_current_key_zval(ht, key) \
zend_hash_get_current_key_zval_ex(ht, key, NULL)
#define zend_hash_get_current_key_type(ht) \
zend_hash_get_current_key_type_ex(ht, NULL)
#define zend_hash_get_current_data(ht, pData) \
Expand Down
38 changes: 6 additions & 32 deletions Zend/zend_interfaces.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,50 +195,24 @@ static int zend_user_it_get_current_key_default(zend_object_iterator *_iter, cha
/* }}} */

/* {{{ zend_user_it_get_current_key */
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)
ZEND_API void zend_user_it_get_current_key(zend_object_iterator *_iter, zval *key TSRMLS_DC)
{
zend_user_iterator *iter = (zend_user_iterator*)_iter;
zval *object = (zval*)iter->it.data;
zval *retval;

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

if (!retval) {
*int_key = 0;
if (!EG(exception))
{
if (retval) {
ZVAL_ZVAL(key, retval, 1, 1);
} else {
if (!EG(exception)) {
zend_error(E_WARNING, "Nothing returned from %s::key()", iter->ce->name);
}
return HASH_KEY_IS_LONG;
}
switch (Z_TYPE_P(retval)) {
default:
zend_error(E_WARNING, "Illegal type returned from %s::key()", iter->ce->name);
case IS_NULL:
*int_key = 0;
zval_ptr_dtor(&retval);
return HASH_KEY_IS_LONG;

case IS_STRING:
*str_key = estrndup(Z_STRVAL_P(retval), Z_STRLEN_P(retval));
*str_key_len = Z_STRLEN_P(retval)+1;
zval_ptr_dtor(&retval);
return HASH_KEY_IS_STRING;

case IS_DOUBLE:
*int_key = (long)Z_DVAL_P(retval);
zval_ptr_dtor(&retval);
return HASH_KEY_IS_LONG;

case IS_RESOURCE:
case IS_BOOL:
case IS_LONG:
*int_key = (long)Z_LVAL_P(retval);
zval_ptr_dtor(&retval);
return HASH_KEY_IS_LONG;
ZVAL_LONG(key, 0);
}
}
/* }}} */

/* {{{ zend_user_it_move_forward */
ZEND_API void zend_user_it_move_forward(zend_object_iterator *_iter TSRMLS_DC)
Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_interfaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ ZEND_API zval* zend_call_method(zval **object_pp, zend_class_entry *obj_ce, zend

ZEND_API void zend_user_it_rewind(zend_object_iterator *_iter TSRMLS_DC);
ZEND_API int zend_user_it_valid(zend_object_iterator *_iter TSRMLS_DC);
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);
ZEND_API void zend_user_it_get_current_key(zend_object_iterator *_iter, zval *key TSRMLS_DC);
ZEND_API void zend_user_it_get_current_data(zend_object_iterator *_iter, zval ***data TSRMLS_DC);
ZEND_API void zend_user_it_move_forward(zend_object_iterator *_iter TSRMLS_DC);
ZEND_API void zend_user_it_invalidate_current(zend_object_iterator *_iter TSRMLS_DC);
Expand Down
7 changes: 5 additions & 2 deletions Zend/zend_iterators.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ typedef struct _zend_object_iterator_funcs {
/* fetch the item data for the current element */
void (*get_current_data)(zend_object_iterator *iter, zval ***data TSRMLS_DC);

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

/* step forwards to next element */
void (*move_forward)(zend_object_iterator *iter TSRMLS_DC);
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ typedef unsigned long zend_uintptr_t;

typedef unsigned int zend_object_handle;
typedef struct _zend_object_handlers zend_object_handlers;
typedef struct _zval_struct zval;

typedef struct _zend_object_value {
zend_object_handle handle;
Expand Down
Loading

0 comments on commit fcc6611

Please sign in to comment.