operation). (Dmitry)
. Implemented FR #72768 (Add ENABLE_VIRTUAL_TERMINAL_PROCESSING flag for
php.exe). (Michele Locati)
+ . Implemented "Convert numeric keys in object/array casts" RFC, fixes
+ bugs #53838, #61655, #66173, #70925, #72254, etc. (Andrea)
- Date:
. Fixed bug #69587 (DateInterval properties and isset). (jhdxr)
. is_object() will now return true for objects of class
__PHP_Incomplete_Class.
. Support for Netware operating systems have been removed.
+ . Casting arrays to objects (with (object) or settype()) will now covert
+ integer keys to string property names. This fixes the behaviour of previous
+ versions, where integer keys would become inaccessible properties with
+ integer names.
+ . Casting objects to arrays (with (object) or settype()), and retrieving
+ object properties in an array with get_object_vars(), will now convert
+ numeric string property names (that is, property names of the format
+ /^(0|(-?[1-9][0-9]*))$/ where PHP_INT_MIN <= n <= PHP_INT_MAX) to integer
+ keys. This fixes the behaviour of previous versions, where numeric string
+ property names would become inaccessible string keys.
========================================
2. New Features
?>
--EXPECT--
object(stdClass)#1 (3) {
- [0]=>
+ ["0"]=>
int(1)
- [1]=>
+ ["1"]=>
int(2)
- [2]=>
+ ["2"]=>
int(3)
}
array(3) {
int(3)
}
object(stdClass)#1 (3) {
- [0]=>
+ ["0"]=>
int(1)
- [1]=>
+ ["1"]=>
int(2)
- [2]=>
+ ["2"]=>
int(3)
}
if (!zobj->ce->default_properties_count && properties == zobj->properties && !ZEND_HASH_GET_APPLY_COUNT(properties)) {
/* fast copy */
if (EXPECTED(zobj->handlers == &std_object_handlers)) {
- if (EXPECTED(!(GC_FLAGS(properties) & IS_ARRAY_IMMUTABLE))) {
- GC_REFCOUNT(properties)++;
- }
- RETURN_ARR(properties);
+ RETURN_ARR(zend_proptable_to_symtable(properties, 0));
}
- RETURN_ARR(zend_array_dup(properties));
+ RETURN_ARR(zend_proptable_to_symtable(properties, 1));
} else {
array_init_size(return_value, zend_hash_num_elements(properties));
const char *prop_name, *class_name;
size_t prop_len;
zend_unmangle_property_name_ex(key, &class_name, &prop_name, &prop_len);
+ /* We assume here that a mangled property name is never
+ * numeric. This is probably a safe assumption, but
+ * theoretically someone might write an extension with
+ * private, numeric properties. Well, too bad.
+ */
zend_hash_str_add_new(Z_ARRVAL_P(return_value), prop_name, prop_len, value);
} else {
- zend_hash_add_new(Z_ARRVAL_P(return_value), key, value);
+ zend_ulong num_key;
+ if (ZEND_HANDLE_NUMERIC(key, num_key)) {
+ zend_hash_index_add_new(Z_ARRVAL_P(return_value), num_key, value);
+ } else {
+ zend_hash_add_new(Z_ARRVAL_P(return_value), key, value);
+ }
}
}
}
}
}
+/* Takes a "symtable" hashtable (contains integer and non-numeric string keys)
+ * and converts it to a "proptable" (contains only string keys).
+ * If the symtable didn't need duplicating, its refcount is incremented.
+ */
+ZEND_API HashTable* ZEND_FASTCALL zend_symtable_to_proptable(HashTable *ht)
+{
+ zend_ulong num_key;
+ zend_string *str_key;
+ zval *zv;
+
+ if (UNEXPECTED(HT_IS_PACKED(ht))) {
+ goto convert;
+ }
+
+ ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, str_key, zv) {
+ if (!str_key) {
+ goto convert;
+ }
+ } ZEND_HASH_FOREACH_END();
+
+ if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) {
+ GC_REFCOUNT(ht)++;
+ }
+
+ return ht;
+
+convert:
+ {
+ HashTable *new_ht = emalloc(sizeof(HashTable));
+
+ zend_hash_init(new_ht, zend_hash_num_elements(ht), NULL, ZVAL_PTR_DTOR, 0);
+
+ ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, str_key, zv) {
+ if (!str_key) {
+ str_key = zend_long_to_str(num_key);
+ zend_string_delref(str_key);
+ }
+ do {
+ if (Z_OPT_REFCOUNTED_P(zv)) {
+ if (Z_ISREF_P(zv) && Z_REFCOUNT_P(zv) == 1) {
+ zv = Z_REFVAL_P(zv);
+ if (!Z_OPT_REFCOUNTED_P(zv)) {
+ break;
+ }
+ }
+ Z_ADDREF_P(zv);
+ }
+ } while (0);
+ zend_hash_update(new_ht, str_key, zv);
+ } ZEND_HASH_FOREACH_END();
+
+ return new_ht;
+ }
+}
+
+/* Takes a "proptable" hashtable (contains only string keys) and converts it to
+ * a "symtable" (contains integer and non-numeric string keys).
+ * If the proptable didn't need duplicating, its refcount is incremented.
+ */
+ZEND_API HashTable* ZEND_FASTCALL zend_proptable_to_symtable(HashTable *ht, zend_bool always_duplicate)
+{
+ zend_ulong num_key;
+ zend_string *str_key;
+ zval *zv;
+
+ ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, str_key, zv) {
+ /* The `str_key &&` here might seem redundant: property tables should
+ * only have string keys. Unfortunately, this isn't true, at the very
+ * least because of ArrayObject, which stores a symtable where the
+ * property table should be.
+ */
+ if (str_key && ZEND_HANDLE_NUMERIC(str_key, num_key)) {
+ goto convert;
+ }
+ } ZEND_HASH_FOREACH_END();
+
+ if (always_duplicate) {
+ return zend_array_dup(ht);
+ }
+
+ if (EXPECTED(!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE))) {
+ GC_REFCOUNT(ht)++;
+ }
+
+ return ht;
+
+convert:
+ {
+ HashTable *new_ht = emalloc(sizeof(HashTable));
+
+ zend_hash_init(new_ht, zend_hash_num_elements(ht), NULL, ZVAL_PTR_DTOR, 0);
+
+ ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, str_key, zv) {
+ do {
+ if (Z_OPT_REFCOUNTED_P(zv)) {
+ if (Z_ISREF_P(zv) && Z_REFCOUNT_P(zv) == 1) {
+ zv = Z_REFVAL_P(zv);
+ if (!Z_OPT_REFCOUNTED_P(zv)) {
+ break;
+ }
+ }
+ Z_ADDREF_P(zv);
+ }
+ } while (0);
+ /* Again, thank ArrayObject for `!str_key ||`. */
+ if (!str_key || ZEND_HANDLE_NUMERIC(str_key, num_key)) {
+ zend_hash_index_update(new_ht, num_key, zv);
+ } else {
+ zend_hash_update(new_ht, str_key, zv);
+ }
+ } ZEND_HASH_FOREACH_END();
+
+ return new_ht;
+ }
+}
+
/*
* Local variables:
* tab-width: 4
ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source);
ZEND_API void ZEND_FASTCALL zend_array_destroy(HashTable *ht);
ZEND_API void ZEND_FASTCALL zend_symtable_clean(HashTable *ht);
+ZEND_API HashTable* ZEND_FASTCALL zend_symtable_to_proptable(HashTable *ht);
+ZEND_API HashTable* ZEND_FASTCALL zend_proptable_to_symtable(HashTable *ht, zend_bool always_duplicate);
ZEND_API int ZEND_FASTCALL _zend_handle_numeric_str_ex(const char *key, size_t length, zend_ulong *idx);
if (obj_ht) {
zend_array *arr;
+ /* fast copy */
if (!Z_OBJCE_P(op)->default_properties_count &&
obj_ht == Z_OBJ_P(op)->properties &&
- !ZEND_HASH_GET_APPLY_COUNT(Z_OBJ_P(op)->properties)) {
- /* fast copy */
- if (EXPECTED(Z_OBJ_P(op)->handlers == &std_object_handlers)) {
- arr = obj_ht;
- if (EXPECTED(!(GC_FLAGS(Z_OBJ_P(op)->properties) & IS_ARRAY_IMMUTABLE))) {
- GC_REFCOUNT(Z_OBJ_P(op)->properties)++;
- }
- } else {
- arr = zend_array_dup(obj_ht);
- }
- zval_dtor(op);
- ZVAL_ARR(op, arr);
+ !ZEND_HASH_GET_APPLY_COUNT(Z_OBJ_P(op)->properties) &&
+ EXPECTED(Z_OBJ_P(op)->handlers == &std_object_handlers)) {
+ arr = zend_proptable_to_symtable(obj_ht, 0);
} else {
- arr = zend_array_dup(obj_ht);
- zval_dtor(op);
- ZVAL_ARR(op, arr);
+ arr = zend_proptable_to_symtable(obj_ht, 1);
}
+ zval_dtor(op);
+ ZVAL_ARR(op, arr);
return;
}
} else {
case IS_ARRAY:
{
HashTable *ht = Z_ARR_P(op);
- if (Z_IMMUTABLE_P(op)) {
+ ht = zend_symtable_to_proptable(ht);
+ if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) {
/* TODO: try not to duplicate immutable arrays as well ??? */
ht = zend_array_dup(ht);
}
+ zval_dtor(op);
object_and_properties_init(op, zend_standard_class_def, ht);
break;
}
Array
(
[0] => ReflectionProperty Object
+ (
+ [name] => 0
+ [class] => stdClass
+ )
+
+ [1] => ReflectionProperty Object
(
[name] => oo
[class] => stdClass
int(1)
}
object(stdClass)#1 (2) {
- [1]=>
+ ["1"]=>
int(1)
- [0]=>
+ ["0"]=>
int(2)
}
object(ArrayObject)#2 (1) {
["storage":"ArrayObject":private]=>
object(stdClass)#1 (2) {
- [1]=>
+ ["1"]=>
int(1)
- [0]=>
+ ["0"]=>
int(2)
}
}
?>
--EXPECT--
stdClass::__set_state(array(
- 0 => 1,
- 1 => 3,
+ '0' => 1,
+ '1' => 3,
'foo' => 'bar',
))
-- Iteration 1 --
bool(true)
object(stdClass)#2 (3) {
- [0]=>
+ ["0"]=>
int(1)
- [1]=>
+ ["1"]=>
int(2)
- [2]=>
+ ["2"]=>
int(3)
}
string(6) "object"
-- Iteration 3 --
bool(true)
object(stdClass)#2 (3) {
- [0]=>
+ ["0"]=>
int(2)
- [1]=>
+ ["1"]=>
int(3)
- [2]=>
+ ["2"]=>
int(4)
}
string(6) "object"
}
bool(true)
object(stdClass)#%d (3) {
- [0]=>
+ ["0"]=>
int(1)
- [1]=>
+ ["1"]=>
int(2)
- [2]=>
+ ["2"]=>
int(3)
}
bool(true)
}
bool(true)
object(stdClass)#%d (3) {
- [0]=>
+ ["0"]=>
int(2)
- [1]=>
+ ["1"]=>
int(3)
- [2]=>
+ ["2"]=>
int(4)
}
bool(true)