diff --git a/ext/spl/config.m4 b/ext/spl/config.m4 index f15e124ba3f5e..7c091d0ab7a4c 100644 --- a/ext/spl/config.m4 +++ b/ext/spl/config.m4 @@ -8,6 +8,7 @@ PHP_NEW_EXTENSION([spl], m4_normalize([ spl_functions.c spl_heap.c spl_iterators.c + spl_multimap.c spl_observer.c ]), [no],, @@ -22,6 +23,7 @@ PHP_INSTALL_HEADERS([ext/spl], m4_normalize([ spl_functions.h spl_heap.h spl_iterators.h + spl_multimap.h spl_observer.h ])) PHP_ADD_EXTENSION_DEP(spl, pcre, true) diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index f796b936daeec..abd112a117f23 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -32,6 +32,7 @@ #include "spl_dllist.h" #include "spl_fixedarray.h" #include "spl_heap.h" +#include "spl_multimap.h" #include "zend_exceptions.h" #include "zend_interfaces.h" #include "main/snprintf.h" @@ -203,6 +204,7 @@ PHP_FUNCTION(class_uses) SPL_ADD_CLASS(SplHeap, z_list, sub, allow, ce_flags); \ SPL_ADD_CLASS(SplMinHeap, z_list, sub, allow, ce_flags); \ SPL_ADD_CLASS(SplMaxHeap, z_list, sub, allow, ce_flags); \ + SPL_ADD_CLASS(SplMultiMap, z_list, sub, allow, ce_flags); \ SPL_ADD_CLASS(SplObjectStorage, z_list, sub, allow, ce_flags); \ SPL_ADD_CLASS(SplObserver, z_list, sub, allow, ce_flags); \ SPL_ADD_CLASS(SplPriorityQueue, z_list, sub, allow, ce_flags); \ @@ -734,6 +736,7 @@ PHP_MINIT_FUNCTION(spl) PHP_MINIT(spl_dllist)(INIT_FUNC_ARGS_PASSTHRU); PHP_MINIT(spl_heap)(INIT_FUNC_ARGS_PASSTHRU); PHP_MINIT(spl_fixedarray)(INIT_FUNC_ARGS_PASSTHRU); + PHP_MINIT(spl_multimap)(INIT_FUNC_ARGS_PASSTHRU); PHP_MINIT(spl_observer)(INIT_FUNC_ARGS_PASSTHRU); return SUCCESS; diff --git a/ext/spl/spl_multimap.c b/ext/spl/spl_multimap.c new file mode 100644 index 0000000000000..90f1c20c8348d --- /dev/null +++ b/ext/spl/spl_multimap.c @@ -0,0 +1,616 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "php.h" +#include "zend_interfaces.h" +#include "zend_exceptions.h" + +#include "spl_multimap_arginfo.h" +#include "spl_multimap.h" +#include "spl_exceptions.h" + +static zend_object_handlers spl_handler_SplMultiMap; +PHPAPI zend_class_entry *spl_ce_SplMultiMap; + +typedef struct _spl_multimap { + HashTable *data; + zend_long total_size; +} spl_multimap; + +typedef struct _spl_multimap_object { + spl_multimap multimap; + zend_object std; +} spl_multimap_object; + +typedef struct _spl_multimap_it { + zend_object_iterator intern; + zend_string *current_key; + zend_long current_value_index; +} spl_multimap_it; + +static spl_multimap_object *spl_multimap_from_obj(zend_object *obj) +{ + return (spl_multimap_object*)((char*)(obj) - XtOffsetOf(spl_multimap_object, std)); +} + +#define Z_SPLMULTIMAP_P(zv) spl_multimap_from_obj(Z_OBJ_P((zv))) + +static void spl_multimap_init(spl_multimap *multimap) +{ + ALLOC_HASHTABLE(multimap->data); + zend_hash_init(multimap->data, 0, NULL, ZVAL_PTR_DTOR, 0); + multimap->total_size = 0; +} + +static void spl_multimap_destroy(spl_multimap *multimap) +{ + if (multimap->data) { + zend_hash_destroy(multimap->data); + FREE_HASHTABLE(multimap->data); + multimap->data = NULL; + } + + multimap->total_size = 0; +} + +static zend_object *spl_multimap_new(zend_class_entry *class_type) +{ + spl_multimap_object *intern; + + intern = zend_object_alloc(sizeof(spl_multimap_object), class_type); + + zend_object_std_init(&intern->std, class_type); + object_properties_init(&intern->std, class_type); + + spl_multimap_init(&intern->multimap); + + intern->std.handlers = &spl_handler_SplMultiMap; + + return &intern->std; +} + +static void spl_multimap_object_free_storage(zend_object *object) +{ + spl_multimap_object *intern = spl_multimap_from_obj(object); + + spl_multimap_destroy(&intern->multimap); + + zend_object_std_dtor(&intern->std); +} + +static HashTable *spl_multimap_object_get_gc(zend_object *obj, zval **table, int *n) +{ + spl_multimap_object *intern = spl_multimap_from_obj(obj); + + *table = NULL; + *n = 0; + + return intern->multimap.data; +} + +static zend_object *spl_multimap_object_clone(zend_object *obj) +{ + spl_multimap_object *old_object = spl_multimap_from_obj(obj); + zend_object *new_obj = spl_multimap_new(obj->ce); + spl_multimap_object *new_object = spl_multimap_from_obj(new_obj); + + zend_objects_clone_members(new_obj, obj); + + spl_multimap_destroy(&new_object->multimap); + spl_multimap_init(&new_object->multimap); + + new_object->multimap.total_size = old_object->multimap.total_size; + + zend_string *key; + zval *key_array; + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(old_object->multimap.data, key, key_array) { + zval new_array; + array_init(&new_array); + + zval *current_value; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(key_array), current_value) { + Z_TRY_ADDREF_P(current_value); + add_next_index_zval(&new_array, current_value); + } ZEND_HASH_FOREACH_END(); + + zend_hash_update(new_object->multimap.data, key, &new_array); + } ZEND_HASH_FOREACH_END(); + + return new_obj; +} + +static zend_result spl_multimap_object_count_elements(zend_object *object, zend_long *count) +{ + spl_multimap_object *intern = spl_multimap_from_obj(object); + *count = intern->multimap.total_size; + + return SUCCESS; +} + +PHP_METHOD(SplMultiMap, __construct) +{ + ZEND_PARSE_PARAMETERS_NONE(); +} + +PHP_METHOD(SplMultiMap, put) +{ + zend_string *key; + zval *value; + spl_multimap_object *intern; + zval *key_array; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(key) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + key_array = zend_hash_find(intern->multimap.data, key); + + if (key_array == NULL) { + zval new_array; + + array_init(&new_array); + key_array = zend_hash_add(intern->multimap.data, key, &new_array); + } + + Z_TRY_ADDREF_P(value); + add_next_index_zval(key_array, value); + + intern->multimap.total_size++; +} + +PHP_METHOD(SplMultiMap, putAll) +{ + zend_string *key; + zval *values; + spl_multimap_object *intern; + zval *key_array; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(key) + Z_PARAM_ARRAY(values) + ZEND_PARSE_PARAMETERS_END(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + + // Only create the key if we have values to add + if (zend_array_count(Z_ARRVAL_P(values)) == 0) { + return; + } + + key_array = zend_hash_find(intern->multimap.data, key); + if (key_array == NULL) { + zval new_array; + array_init(&new_array); + key_array = zend_hash_add(intern->multimap.data, key, &new_array); + } + + zval *current_value; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(values), current_value) { + Z_TRY_ADDREF_P(current_value); + add_next_index_zval(key_array, current_value); + intern->multimap.total_size++; + } ZEND_HASH_FOREACH_END(); +} + +PHP_METHOD(SplMultiMap, get) +{ + zend_string *key; + spl_multimap_object *intern; + zval *key_array; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(key) + ZEND_PARSE_PARAMETERS_END(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + + key_array = zend_hash_find(intern->multimap.data, key); + if (key_array == NULL) { + array_init(return_value); + return; + } + + ZVAL_COPY(return_value, key_array); +} + +PHP_METHOD(SplMultiMap, remove) +{ + zend_string *key; + zval *value; + spl_multimap_object *intern; + zval *key_array; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(key) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + + key_array = zend_hash_find(intern->multimap.data, key); + if (key_array == NULL) { + RETURN_FALSE; + } + + HashTable *ht = Z_ARRVAL_P(key_array); + bool found = false; + zval new_array; + array_init(&new_array); + + zval *current_value; + ZEND_HASH_FOREACH_VAL(ht, current_value) { + if (!found && zend_is_identical(value, current_value)) { + found = true; + } else { + Z_TRY_ADDREF_P(current_value); + add_next_index_zval(&new_array, current_value); + } + } ZEND_HASH_FOREACH_END(); + + if (found) { + zend_hash_update(intern->multimap.data, key, &new_array); + intern->multimap.total_size--; + + if (zend_array_count(Z_ARRVAL_P(zend_hash_find(intern->multimap.data, key))) == 0) { + zend_hash_del(intern->multimap.data, key); + } + } else { + zval_ptr_dtor(&new_array); + } + + RETURN_BOOL(found); +} + +PHP_METHOD(SplMultiMap, removeAll) +{ + zend_string *key; + spl_multimap_object *intern; + zval *key_array; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(key) + ZEND_PARSE_PARAMETERS_END(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + + key_array = zend_hash_find(intern->multimap.data, key); + if (key_array == NULL) { + RETURN_FALSE; + } + + intern->multimap.total_size -= zend_array_count(Z_ARRVAL_P(key_array)); + zend_hash_del(intern->multimap.data, key); + RETURN_TRUE; +} + +PHP_METHOD(SplMultiMap, replaceAll) +{ + zend_string *key; + zval *values; + spl_multimap_object *intern; + zval *key_array; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(key) + Z_PARAM_ARRAY(values) + ZEND_PARSE_PARAMETERS_END(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + + /* Remove existing values for this key if they exist. */ + key_array = zend_hash_find(intern->multimap.data, key); + if (key_array != NULL) { + intern->multimap.total_size -= zend_array_count(Z_ARRVAL_P(key_array)); + } + + zval new_array; + array_init(&new_array); + key_array = zend_hash_update(intern->multimap.data, key, &new_array); + + zval *current_value; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(values), current_value) { + Z_TRY_ADDREF_P(current_value); + add_next_index_zval(key_array, current_value); + intern->multimap.total_size++; + } ZEND_HASH_FOREACH_END(); + + if (zend_array_count(Z_ARRVAL_P(key_array)) == 0) { + zend_hash_del(intern->multimap.data, key); + } +} + +PHP_METHOD(SplMultiMap, containsKey) +{ + zend_string *key; + spl_multimap_object *intern; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(key) + ZEND_PARSE_PARAMETERS_END(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + + RETURN_BOOL(zend_hash_exists(intern->multimap.data, key)); +} + +PHP_METHOD(SplMultiMap, containsValue) +{ + zend_string *key; + zval *value; + spl_multimap_object *intern; + zval *key_array; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(key) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + + key_array = zend_hash_find(intern->multimap.data, key); + if (key_array == NULL) { + RETURN_FALSE; + } + + zval *current_value; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(key_array), current_value) { + if (zend_is_identical(value, current_value)) { + RETURN_TRUE; + } + } ZEND_HASH_FOREACH_END(); + + RETURN_FALSE; +} + +PHP_METHOD(SplMultiMap, keys) +{ + spl_multimap_object *intern; + zend_string *key; + + ZEND_PARSE_PARAMETERS_NONE(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + + array_init(return_value); + + ZEND_HASH_MAP_FOREACH_STR_KEY(intern->multimap.data, key) { + add_next_index_str(return_value, zend_string_copy(key)); + } ZEND_HASH_FOREACH_END(); +} + +PHP_METHOD(SplMultiMap, values) +{ + spl_multimap_object *intern; + zval *key_array, *current_value; + + ZEND_PARSE_PARAMETERS_NONE(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + array_init(return_value); + + ZEND_HASH_FOREACH_VAL(intern->multimap.data, key_array) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(key_array), current_value) { + Z_TRY_ADDREF_P(current_value); + add_next_index_zval(return_value, current_value); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); +} + + +PHP_METHOD(SplMultiMap, isEmpty) +{ + spl_multimap_object *intern; + + ZEND_PARSE_PARAMETERS_NONE(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + + RETURN_BOOL(intern->multimap.total_size == 0); +} + +PHP_METHOD(SplMultiMap, clear) +{ + spl_multimap_object *intern; + + ZEND_PARSE_PARAMETERS_NONE(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + + zend_hash_clean(intern->multimap.data); + intern->multimap.total_size = 0; +} + +PHP_METHOD(SplMultiMap, count) +{ + spl_multimap_object *intern; + + ZEND_PARSE_PARAMETERS_NONE(); + + intern = Z_SPLMULTIMAP_P(ZEND_THIS); + + RETURN_LONG(intern->multimap.total_size); +} + +static void spl_multimap_it_dtor(zend_object_iterator *iter) +{ + spl_multimap_it *iterator = (spl_multimap_it*)iter; + + if (iterator->current_key) { + zend_string_release(iterator->current_key); + } + + zval_ptr_dtor(&iter->data); +} + +static void spl_multimap_it_rewind(zend_object_iterator *iter) +{ + spl_multimap_it *iterator = (spl_multimap_it*)iter; + spl_multimap_object *object = Z_SPLMULTIMAP_P(&iter->data); + + iterator->current_value_index = 0; + + if (iterator->current_key) { + zend_string_release(iterator->current_key); + iterator->current_key = NULL; + } + + zend_string *key; + zend_hash_internal_pointer_reset(object->multimap.data); + if (zend_hash_get_current_key(object->multimap.data, &key, NULL) == HASH_KEY_IS_STRING) { + iterator->current_key = zend_string_copy(key); + } +} + +static zend_result spl_multimap_it_valid(zend_object_iterator *iter) +{ + spl_multimap_it *iterator = (spl_multimap_it*)iter; + spl_multimap_object *object = Z_SPLMULTIMAP_P(&iter->data); + + if (!iterator->current_key) { + return FAILURE; + } + + zval *key_array = zend_hash_find(object->multimap.data, iterator->current_key); + if (!key_array) { + return FAILURE; + } + + return iterator->current_value_index < zend_array_count(Z_ARRVAL_P(key_array)) ? SUCCESS : FAILURE; +} + +static zval *spl_multimap_it_get_current_data(zend_object_iterator *iter) +{ + spl_multimap_it *iterator = (spl_multimap_it*)iter; + spl_multimap_object *object = Z_SPLMULTIMAP_P(&iter->data); + + if (!iterator->current_key) { + return &EG(uninitialized_zval); + } + + zval *key_array = zend_hash_find(object->multimap.data, iterator->current_key); + if (!key_array) { + return &EG(uninitialized_zval); + } + + zval *value = zend_hash_index_find(Z_ARRVAL_P(key_array), iterator->current_value_index); + + return value ? value : &EG(uninitialized_zval); +} + +static void spl_multimap_it_get_current_key(zend_object_iterator *iter, zval *key) +{ + spl_multimap_it *iterator = (spl_multimap_it*)iter; + + if (iterator->current_key) { + ZVAL_STR_COPY(key, iterator->current_key); + } else { + ZVAL_NULL(key); + } +} + +static void spl_multimap_it_move_forward(zend_object_iterator *iter) +{ + spl_multimap_it *iterator = (spl_multimap_it*)iter; + spl_multimap_object *object = Z_SPLMULTIMAP_P(&iter->data); + + if (!iterator->current_key) { + return; + } + + zval *key_array = zend_hash_find(object->multimap.data, iterator->current_key); + if (!key_array) { + return; + } + + iterator->current_value_index++; + + if (iterator->current_value_index >= zend_array_count(Z_ARRVAL_P(key_array))) { + zend_hash_move_forward(object->multimap.data); + iterator->current_value_index = 0; + + if (iterator->current_key) { + zend_string_release(iterator->current_key); + iterator->current_key = NULL; + } + + zend_string *key; + if (zend_hash_get_current_key(object->multimap.data, &key, NULL) == HASH_KEY_IS_STRING) { + iterator->current_key = zend_string_copy(key); + } + } +} + +static const zend_object_iterator_funcs spl_multimap_it_funcs = { + spl_multimap_it_dtor, + spl_multimap_it_valid, + spl_multimap_it_get_current_data, + spl_multimap_it_get_current_key, + spl_multimap_it_move_forward, + spl_multimap_it_rewind, + NULL, + NULL, +}; + +static zend_object_iterator *spl_multimap_get_iterator(zend_class_entry *ce, zval *object, int by_ref) +{ + spl_multimap_it *iterator; + + if (by_ref) { + zend_throw_error(NULL, "An iterator cannot be used with foreach by reference"); + return NULL; + } + + iterator = emalloc(sizeof(spl_multimap_it)); + + zend_iterator_init((zend_object_iterator*)iterator); + + ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); + iterator->intern.funcs = &spl_multimap_it_funcs; + iterator->current_key = NULL; + iterator->current_value_index = 0; + + return &iterator->intern; +} + +PHP_METHOD(SplMultiMap, getIterator) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zend_create_internal_iterator_zval(return_value, ZEND_THIS); +} + +PHP_MINIT_FUNCTION(spl_multimap) +{ + spl_ce_SplMultiMap = register_class_SplMultiMap(zend_ce_aggregate, zend_ce_countable); + + spl_ce_SplMultiMap->create_object = spl_multimap_new; + spl_ce_SplMultiMap->default_object_handlers = &spl_handler_SplMultiMap; + spl_ce_SplMultiMap->get_iterator = spl_multimap_get_iterator; + + memcpy(&spl_handler_SplMultiMap, &std_object_handlers, sizeof(zend_object_handlers)); + + spl_handler_SplMultiMap.offset = XtOffsetOf(spl_multimap_object, std); + spl_handler_SplMultiMap.count_elements = spl_multimap_object_count_elements; + spl_handler_SplMultiMap.get_gc = spl_multimap_object_get_gc; + spl_handler_SplMultiMap.free_obj = spl_multimap_object_free_storage; + spl_handler_SplMultiMap.clone_obj = spl_multimap_object_clone; + + return SUCCESS; +} diff --git a/ext/spl/spl_multimap.h b/ext/spl/spl_multimap.h new file mode 100644 index 0000000000000..691f0b481ddc3 --- /dev/null +++ b/ext/spl/spl_multimap.h @@ -0,0 +1,24 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#ifndef SPL_MULTIMAP_H +#define SPL_MULTIMAP_H + +#include "php.h" + +extern PHPAPI zend_class_entry *spl_ce_SplMultiMap; + +PHP_MINIT_FUNCTION(spl_multimap); + +#endif /* SPL_MULTIMAP_H */ diff --git a/ext/spl/spl_multimap.stub.php b/ext/spl/spl_multimap.stub.php new file mode 100644 index 0000000000000..3355cc0ef8c2e --- /dev/null +++ b/ext/spl/spl_multimap.stub.php @@ -0,0 +1,36 @@ +isEmpty()); +var_dump($mm->count()); +var_dump($mm->get('nonexistent')); + +$mm->put('colors', 'red'); +$mm->put('colors', 'blue'); +$mm->put('colors', 'red'); +$mm->put('numbers', 42); + +var_dump($mm->get('colors')); +var_dump($mm->get('numbers')); +var_dump($mm->get('nonexistent')); + +var_dump($mm->count()); +var_dump(count($mm)); +var_dump($mm->isEmpty()); +?> +--EXPECT-- +bool(true) +int(0) +array(0) { +} +array(3) { + [0]=> + string(3) "red" + [1]=> + string(4) "blue" + [2]=> + string(3) "red" +} +array(1) { + [0]=> + int(42) +} +array(0) { +} +int(4) +int(4) +bool(false) diff --git a/ext/spl/tests/multimap/bulk_operations.phpt b/ext/spl/tests/multimap/bulk_operations.phpt new file mode 100644 index 0000000000000..c0db0a6daca27 --- /dev/null +++ b/ext/spl/tests/multimap/bulk_operations.phpt @@ -0,0 +1,68 @@ +--TEST-- +SplMultiMap: Bulk operations (putAll, replaceAll) +--FILE-- +putAll('fruits', ['apple', 'banana', 'cherry']); +var_dump($mm->get('fruits')); +var_dump($mm->count()); + +$mm->putAll('fruits', ['date', 'elderberry']); +var_dump($mm->get('fruits')); +var_dump($mm->count()); + +$mm->replaceAll('fruits', ['grape', 'honeydew']); +var_dump($mm->get('fruits')); +var_dump($mm->count()); + +$mm->replaceAll('vegetables', ['carrot', 'broccoli']); +var_dump($mm->get('vegetables')); +var_dump($mm->count()); + +$mm->replaceAll('fruits', []); +var_dump($mm->get('fruits')); +var_dump($mm->containsKey('fruits')); +var_dump($mm->count()); +?> +--EXPECT-- +array(3) { + [0]=> + string(5) "apple" + [1]=> + string(6) "banana" + [2]=> + string(6) "cherry" +} +int(3) +array(5) { + [0]=> + string(5) "apple" + [1]=> + string(6) "banana" + [2]=> + string(6) "cherry" + [3]=> + string(4) "date" + [4]=> + string(10) "elderberry" +} +int(5) +array(2) { + [0]=> + string(5) "grape" + [1]=> + string(8) "honeydew" +} +int(2) +array(2) { + [0]=> + string(6) "carrot" + [1]=> + string(8) "broccoli" +} +int(4) +array(0) { +} +bool(false) +int(2) diff --git a/ext/spl/tests/multimap/clone.phpt b/ext/spl/tests/multimap/clone.phpt new file mode 100644 index 0000000000000..257c057b0aedc --- /dev/null +++ b/ext/spl/tests/multimap/clone.phpt @@ -0,0 +1,48 @@ +--TEST-- +SplMultiMap: Clone behavior - shallow clone with shared object references +--FILE-- +value = 'original'; + +$mm1->put('key1', $obj); +$mm1->put('key1', 'string_value'); +$mm1->put('key2', [1, 2, 3]); + +$mm2 = clone $mm1; + +echo "Original count: " . $mm1->count() . "\n"; +echo "Clone count: " . $mm2->count() . "\n"; + +$mm1->put('key3', 'only_in_original'); +$mm2->put('key4', 'only_in_clone'); + +echo "Original has key3: " . ($mm1->containsKey('key3') ? 'true' : 'false') . "\n"; +echo "Clone has key3: " . ($mm2->containsKey('key3') ? 'true' : 'false') . "\n"; +echo "Original has key4: " . ($mm1->containsKey('key4') ? 'true' : 'false') . "\n"; +echo "Clone has key4: " . ($mm2->containsKey('key4') ? 'true' : 'false') . "\n"; + +$obj->value = 'modified'; + +$values1 = $mm1->get('key1'); +$values2 = $mm2->get('key1'); + +echo "Original object value: " . $values1[0]->value . "\n"; +echo "Clone object value: " . $values2[0]->value . "\n"; + +echo "Same object reference: " . ($values1[0] === $values2[0] ? 'true' : 'false') . "\n"; + +?> +--EXPECT-- +Original count: 3 +Clone count: 3 +Original has key3: true +Clone has key3: false +Original has key4: false +Clone has key4: true +Original object value: modified +Clone object value: modified +Same object reference: true diff --git a/ext/spl/tests/multimap/constructor.phpt b/ext/spl/tests/multimap/constructor.phpt new file mode 100644 index 0000000000000..3f4c6eaa5ae75 --- /dev/null +++ b/ext/spl/tests/multimap/constructor.phpt @@ -0,0 +1,15 @@ +--TEST-- +SplMultiMap: Constructor and instantiation +--FILE-- +isEmpty()); + +$mm2 = new SplMultiMap(); +var_dump($mm2->count()); + +?> +--EXPECT-- +bool(true) +int(0) diff --git a/ext/spl/tests/multimap/edge_cases.phpt b/ext/spl/tests/multimap/edge_cases.phpt new file mode 100644 index 0000000000000..251432d9081b5 --- /dev/null +++ b/ext/spl/tests/multimap/edge_cases.phpt @@ -0,0 +1,67 @@ +--TEST-- +SplMultiMap: Edge cases and special values +--FILE-- +put('', 'empty key value'); +var_dump($mm->get('')); +var_dump($mm->containsKey('')); + +$mm->put("null\0byte", 'null byte value'); +var_dump($mm->get("null\0byte")); + +$mm->put('123', 'numeric string key'); +var_dump($mm->containsKey('123')); + +echo "\nTesting different value types:\n"; +$mm->put('mixed', null); +$mm->put('mixed', false); +$mm->put('mixed', 0); +$mm->put('mixed', ''); +$mm->put('mixed', []); +$mm->put('mixed', new stdClass()); + +var_dump($mm->count()); // Should be 9 (1 + 1 + 1 + 6) + +$obj1 = new stdClass(); +$obj2 = new stdClass(); +$mm->clear(); +$mm->put('objects', $obj1); +$mm->put('objects', $obj2); + +var_dump($mm->containsValue('objects', $obj1)); // true +var_dump($mm->containsValue('objects', $obj2)); // true +var_dump($mm->containsValue('objects', new stdClass())); // false (different object) + +$mm->clear(); +$arr1 = [1, 2, 3]; +$arr2 = [1, 2, 3]; +$mm->put('arrays', $arr1); + +var_dump($mm->containsValue('arrays', $arr1)); +var_dump($mm->containsValue('arrays', $arr2)); +var_dump($mm->containsValue('arrays', [1, 2, 3])); +var_dump($mm->containsValue('arrays', [1, 2])); +?> +--EXPECT-- +array(1) { + [0]=> + string(15) "empty key value" +} +bool(true) +array(1) { + [0]=> + string(15) "null byte value" +} +bool(true) + +Testing different value types: +int(9) +bool(true) +bool(true) +bool(false) +bool(true) +bool(true) +bool(true) +bool(false) diff --git a/ext/spl/tests/multimap/iterator.phpt b/ext/spl/tests/multimap/iterator.phpt new file mode 100644 index 0000000000000..401673b11117c --- /dev/null +++ b/ext/spl/tests/multimap/iterator.phpt @@ -0,0 +1,52 @@ +--TEST-- +SplMultiMap: Iterator +--FILE-- + $value) { + echo "Should not print\n"; +} +echo "Done with empty iteration\n"; + +$mm->put('colors', 'red'); +$mm->put('colors', 'blue'); +$mm->put('numbers', 42); +$mm->put('numbers', 84); + +echo "\nNormal iteration:\n"; +$results = []; +foreach ($mm as $key => $value) { + $results[] = "Key: $key, Value: " . (is_array($value) ? 'array' : $value); +} + +sort($results); +foreach ($results as $result) { + echo $result . "\n"; +} + +echo "\nUsing getIterator():\n"; +$iterator = $mm->getIterator(); +var_dump($iterator instanceof Iterator); + +echo "\nInterface check:\n"; +var_dump($mm instanceof IteratorAggregate); +var_dump($mm instanceof Countable); +?> +--EXPECT-- +Empty multimap iteration: +Done with empty iteration + +Normal iteration: +Key: colors, Value: blue +Key: colors, Value: red +Key: numbers, Value: 42 +Key: numbers, Value: 84 + +Using getIterator(): +bool(true) + +Interface check: +bool(true) +bool(true) diff --git a/ext/spl/tests/multimap/iterator_by_reference_error.phpt b/ext/spl/tests/multimap/iterator_by_reference_error.phpt new file mode 100644 index 0000000000000..32e518f72b58d --- /dev/null +++ b/ext/spl/tests/multimap/iterator_by_reference_error.phpt @@ -0,0 +1,17 @@ +--TEST-- +SplMultiMap: Iterator by-reference throws Error +--FILE-- +put('key', 'value'); + +try { + foreach ($mm as $k => &$v) { + echo "Should not reach this\n"; + } +} catch (Error $e) { + echo "Caught Error: " . $e->getMessage() . "\n"; +} +?> +--EXPECT-- +Caught Error: An iterator cannot be used with foreach by reference diff --git a/ext/spl/tests/multimap/query_operations.phpt b/ext/spl/tests/multimap/query_operations.phpt new file mode 100644 index 0000000000000..04f7265dff7d8 --- /dev/null +++ b/ext/spl/tests/multimap/query_operations.phpt @@ -0,0 +1,90 @@ +--TEST-- +SplMultiMap: Query operations (containsKey, containsValue, keys, values) +--FILE-- +put('colors', 'red'); +$mm->put('colors', 'blue'); +$mm->put('numbers', 42); +$mm->put('arrays', [1, 2]); + +echo "containsKey tests:\n"; +var_dump($mm->containsKey('colors')); +var_dump($mm->containsKey('numbers')); +var_dump($mm->containsKey('missing')); + +// Test containsValue +echo "\ncontainsValue tests:\n"; +var_dump($mm->containsValue('colors', 'red')); +var_dump($mm->containsValue('colors', 'green')); +var_dump($mm->containsValue('missing', 'red')); +var_dump($mm->containsValue('numbers', 42)); +var_dump($mm->containsValue('numbers', '42')); + +$testArray = [1, 2]; +var_dump($mm->containsValue('arrays', $testArray)); +var_dump($mm->containsValue('arrays', [1, 2])); + +echo "\nkeys() test:\n"; +var_dump($mm->keys()); + +echo "\nvalues() test:\n"; +var_dump($mm->values()); + +echo "\nAfter clear:\n"; +$mm->clear(); +var_dump($mm->isEmpty()); +var_dump($mm->count()); +var_dump($mm->keys()); +var_dump($mm->values()); +?> +--EXPECT-- +containsKey tests: +bool(true) +bool(true) +bool(false) + +containsValue tests: +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) +bool(true) +bool(true) + +keys() test: +array(3) { + [0]=> + string(6) "colors" + [1]=> + string(7) "numbers" + [2]=> + string(6) "arrays" +} + +values() test: +array(4) { + [0]=> + string(3) "red" + [1]=> + string(4) "blue" + [2]=> + int(42) + [3]=> + array(2) { + [0]=> + int(1) + [1]=> + int(2) + } +} + +After clear: +bool(true) +int(0) +array(0) { +} +array(0) { +} diff --git a/ext/spl/tests/multimap/removal_operations.phpt b/ext/spl/tests/multimap/removal_operations.phpt new file mode 100644 index 0000000000000..f7e293a08e8aa --- /dev/null +++ b/ext/spl/tests/multimap/removal_operations.phpt @@ -0,0 +1,99 @@ +--TEST-- +SplMultiMap: Removal operations (remove, removeAll) +--FILE-- +put('colors', 'red'); +$mm->put('colors', 'blue'); +$mm->put('colors', 'red'); // Duplicate +$mm->put('numbers', 42); +$mm->put('numbers', 84); + +echo "Initial state:\n"; +var_dump($mm->get('colors')); +var_dump($mm->count()); + +echo "\nAfter removing 'red' once:\n"; +var_dump($mm->remove('colors', 'red')); +var_dump($mm->get('colors')); +var_dump($mm->count()); + +echo "\nTrying to remove non-existent value:\n"; +var_dump($mm->remove('colors', 'green')); +var_dump($mm->get('colors')); + +echo "\nTrying to remove from non-existent key:\n"; +var_dump($mm->remove('missing', 'value')); + +echo "\nRemoving all values from colors:\n"; +var_dump($mm->remove('colors', 'blue')); +var_dump($mm->remove('colors', 'red')); +var_dump($mm->containsKey('colors')); +var_dump($mm->count()); + +echo "\nTesting removeAll:\n"; +var_dump($mm->get('numbers')); +var_dump($mm->removeAll('numbers')); +var_dump($mm->get('numbers')); +var_dump($mm->containsKey('numbers')); +var_dump($mm->count()); + +echo "\nTesting removeAll on non-existent key:\n"; +var_dump($mm->removeAll('missing')); +?> +--EXPECT-- +Initial state: +array(3) { + [0]=> + string(3) "red" + [1]=> + string(4) "blue" + [2]=> + string(3) "red" +} +int(5) + +After removing 'red' once: +bool(true) +array(2) { + [0]=> + string(4) "blue" + [1]=> + string(3) "red" +} +int(4) + +Trying to remove non-existent value: +bool(false) +array(2) { + [0]=> + string(4) "blue" + [1]=> + string(3) "red" +} + +Trying to remove from non-existent key: +bool(false) + +Removing all values from colors: +bool(true) +bool(true) +bool(false) +int(2) + +Testing removeAll: +array(2) { + [0]=> + int(42) + [1]=> + int(84) +} +bool(true) +array(0) { +} +bool(false) +int(0) + +Testing removeAll on non-existent key: +bool(false)