]> granicus.if.org Git - php/commitdiff
Adding object aggregation capability along with tests.
authorAndrei Zmievski <andrei@php.net>
Thu, 21 Feb 2002 03:32:42 +0000 (03:32 +0000)
committerAndrei Zmievski <andrei@php.net>
Thu, 21 Feb 2002 03:32:42 +0000 (03:32 +0000)
17 files changed:
NEWS
ext/standard/Makefile.in
ext/standard/aggregation.c [new file with mode: 0644]
ext/standard/aggregation.h [new file with mode: 0644]
ext/standard/basic_functions.c
ext/standard/basic_functions.h
ext/standard/php_standard.h
ext/standard/tests/aggregation/aggregate.lib.php [new file with mode: 0644]
ext/standard/tests/aggregation/aggregate.phpt [new file with mode: 0644]
ext/standard/tests/aggregation/aggregate_methods.phpt [new file with mode: 0644]
ext/standard/tests/aggregation/aggregate_methods_by_list.phpt [new file with mode: 0644]
ext/standard/tests/aggregation/aggregate_methods_by_regexp.phpt [new file with mode: 0644]
ext/standard/tests/aggregation/aggregate_properties.phpt [new file with mode: 0644]
ext/standard/tests/aggregation/aggregate_properties_by_list.phpt [new file with mode: 0644]
ext/standard/tests/aggregation/aggregate_properties_by_regexp.phpt [new file with mode: 0644]
ext/standard/tests/aggregation/aggregation_info.phpt [new file with mode: 0644]
ext/standard/tests/aggregation/deaggregate.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index d27233a655aee874abcd6e4544385970e0eeb5f3..f1ce2e7d1628827f98a434334cbd3ec4906c67ad 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,7 @@
 PHP 4                                                                      NEWS
 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
 ?? ??? 200?, Version 4.2.0-dev
+- Added object aggregation capability, see aggregation_*() functions. (Andrei)
 - Added debug_zval_dump(), which works similar to var_dump, yet displays extra
   internal information such as refcounts and true type names. (Jason)
 - Added Andrei's tokenizer extension. (Stig)
index 82ee3aa940026f3751001b8512b87ca215489982..6f7e06dcb537a253e35114e8a4f77ae15428031f 100644 (file)
@@ -9,7 +9,7 @@ LTLIBRARY_SOURCES=\
        syslog.c type.c uniqid.c url.c url_scanner.c var.c versioning.c assert.c \
        strnatcmp.c levenshtein.c incomplete_class.c url_scanner_ex.c \
        ftp_fopen_wrapper.c http_fopen_wrapper.c php_fopen_wrapper.c credits.c \
-       var_unserializer.c ftok.c
+       var_unserializer.c ftok.c aggregation.c
 
 include $(top_srcdir)/build/dynlib.mk
 
diff --git a/ext/standard/aggregation.c b/ext/standard/aggregation.c
new file mode 100644 (file)
index 0000000..65b7ee5
--- /dev/null
@@ -0,0 +1,586 @@
+/* 
+   +----------------------------------------------------------------------+
+   | PHP Version 4                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2002 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 2.02 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available at through the world-wide-web at                           |
+   | http://www.php.net/license/2_02.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.               |
+   +----------------------------------------------------------------------+
+   | Authors: Andrei Zmievski <andrei@php.net>                            |
+   +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#include "php.h"
+#include "basic_functions.h"
+#include "aggregation.h"
+#if HAVE_PCRE || HAVE_BUNDLED_PCRE
+#include "ext/pcre/php_pcre.h"
+#endif
+
+static void aggregation_info_dtor(aggregation_info *info)
+{
+       destroy_zend_class(info->new_ce);
+       efree(info->new_ce);
+       zval_ptr_dtor(&info->aggr_members);
+}
+
+/* {{{ static zval* array_to_hash */
+static zval *array_to_hash(zval *array)
+{
+       zval *hash, **entry;
+       char *name_lc;
+
+       /*
+        * Well, this just transposes the array, popularly known as flipping it, or
+        * giving it the finger.
+        */
+       MAKE_STD_ZVAL(hash);
+       array_init(hash);
+       for (zend_hash_internal_pointer_reset(Z_ARRVAL_P(array));
+                zend_hash_get_current_data(Z_ARRVAL_P(array), (void**)&entry) == SUCCESS;
+                zend_hash_move_forward(Z_ARRVAL_P(array))) {
+               if (Z_TYPE_PP(entry) == IS_STRING) {
+                       /*
+                        * I hate case-insensitivity. Die, die, die.
+                        */
+                       name_lc = estrndup(Z_STRVAL_PP(entry), Z_STRLEN_PP(entry));
+                       zend_str_tolower(name_lc, Z_STRLEN_PP(entry));
+                       add_assoc_bool_ex(hash, name_lc, Z_STRLEN_PP(entry)+1, 1);
+                       efree(name_lc);
+               }
+       }
+
+       return hash;
+}
+/* }}} */
+
+
+/* {{{ static void aggregate_methods() */
+static void aggregate_methods(zend_class_entry *ce, zend_class_entry *from_ce, int aggr_type, zval *aggr_filter, zend_bool exclude, zval *aggr_methods TSRMLS_DC)
+{
+       HashPosition pos;
+       zend_function *function;
+       char *func_name;
+       uint func_name_len;
+       ulong num_key;
+       zval *list_hash = NULL;
+       pcre *re = NULL;
+       pcre_extra *re_extra = NULL;
+       int re_options = 0;
+
+       /*
+        * Flip the array for easy lookup, or compile the regexp.
+        */
+       if (aggr_type == AGGREGATE_BY_LIST) {
+               list_hash = array_to_hash(aggr_filter);
+       } else if (aggr_type == AGGREGATE_BY_REGEXP) {
+               if ((re = pcre_get_compiled_regex(Z_STRVAL_P(aggr_filter), &re_extra, &re_options)) == NULL) {
+                       return;
+               }
+       }
+
+       /*
+        * "Just because it's not nice doesn't mean it's not miraculous."
+        *                      -- _Interesting Times_, Terry Pratchett
+        */
+
+       /*
+        * Aggregating by list without exclusion can be done more efficiently if we
+        * iterate through the list and check against function table instead of the
+        * other way around.
+        */
+       if (aggr_type != AGGREGATE_BY_LIST || exclude) {
+               zend_hash_internal_pointer_reset_ex(&from_ce->function_table, &pos);
+               while (zend_hash_get_current_data_ex(&from_ce->function_table, (void**)&function, &pos) == SUCCESS) {
+                       zend_hash_get_current_key_ex(&from_ce->function_table, &func_name, &func_name_len, &num_key, 0, &pos);
+
+                       /* We do not aggregate:
+                        * 1. constructors */
+                       if (!strncmp(func_name, from_ce->name, MIN(func_name_len-1, from_ce->name_length)) ||
+                       /* 2. private methods (heh, like we really have them) */
+                               func_name[0] == '_' ||
+                       /* 3. explicitly excluded methods */
+                               (aggr_type == AGGREGATE_BY_LIST && zend_hash_exists(Z_ARRVAL_P(list_hash), func_name, func_name_len)) ||
+                       /* 4. methods matching regexp as modified by the exclusion flag */
+                               (aggr_type == AGGREGATE_BY_REGEXP && (pcre_exec(re, re_extra, func_name, func_name_len-1, 0, 0, NULL, 0) < 0) ^ exclude) == 1) {
+                               zend_hash_move_forward_ex(&from_ce->function_table, &pos);
+                               continue;
+                       }
+
+                       /*
+                        * This is where the magic happens.
+                        */
+                       if (zend_hash_add(&ce->function_table, func_name, func_name_len,
+                                                         (void*)function, sizeof(zend_function), NULL) == SUCCESS) {
+                               add_next_index_stringl(aggr_methods, func_name, func_name_len-1, 1);
+                       }
+
+                       zend_hash_move_forward_ex(&from_ce->function_table, &pos);
+               }
+       } else {
+               /*
+                * This is just like above except the other way around.
+                */
+               zend_hash_internal_pointer_reset(Z_ARRVAL_P(list_hash));
+               while (zend_hash_get_current_key_ex(Z_ARRVAL_P(list_hash), &func_name, &func_name_len, &num_key, 0, NULL) == HASH_KEY_IS_STRING) {
+                       if (!strncmp(func_name, from_ce->name, MIN(func_name_len-1, from_ce->name_length)) ||
+                               func_name[0] == '_' ||
+                               zend_hash_find(&from_ce->function_table, func_name, func_name_len, (void**)&function) == FAILURE) {
+                               zend_hash_move_forward(Z_ARRVAL_P(list_hash));
+                               continue;
+                       }
+
+                       if (zend_hash_add(&ce->function_table, func_name, func_name_len,
+                                                         (void*)function, sizeof(zend_function), NULL) == SUCCESS) {
+                               add_next_index_stringl(aggr_methods, func_name, func_name_len-1, 1);
+                       }
+
+                       zend_hash_move_forward(Z_ARRVAL_P(list_hash));
+               }
+       }
+
+       if (list_hash) {
+               zval_ptr_dtor(&list_hash);
+       }
+}
+/* }}} */
+
+
+/* {{{ static void aggregate_properties() */
+static void aggregate_properties(zval *obj, zend_class_entry *from_ce, int aggr_type, zval *aggr_filter, zend_bool exclude, zval *aggr_props TSRMLS_DC)
+{
+       HashPosition pos;
+       zval **prop;
+       char *prop_name;
+       uint prop_name_len;
+       ulong num_key;
+       zval *list_hash = NULL;
+       pcre *re = NULL;
+       pcre_extra *re_extra = NULL;
+       int re_options = 0;
+
+       if (!from_ce->constants_updated) {
+               zend_hash_apply_with_argument(&from_ce->default_properties, (apply_func_arg_t) zval_update_constant, (void *) 1 TSRMLS_CC);
+               from_ce->constants_updated = 1;
+       }
+
+       /*
+        * Flip the array for easy lookup, or compile the regexp.
+        */
+       if (aggr_type == AGGREGATE_BY_LIST) {
+               list_hash = array_to_hash(aggr_filter);
+       } else if (aggr_type == AGGREGATE_BY_REGEXP) {
+               if ((re = pcre_get_compiled_regex(Z_STRVAL_P(aggr_filter), &re_extra, &re_options)) == NULL) {
+                       return;
+               }
+       }
+
+       /*
+        * "Just because it's not nice doesn't mean it's not miraculous."
+        *                      -- _Interesting Times_, Terry Pratchett
+        */
+
+       /*
+        * Aggregating by list without exclusion can be done more efficiently if we
+        * iterate through the list and check against default properties table
+        * instead of the other way around.
+        */
+       if (aggr_type != AGGREGATE_BY_LIST || exclude) {
+               zend_hash_internal_pointer_reset_ex(&from_ce->default_properties, &pos);
+               while (zend_hash_get_current_data_ex(&from_ce->default_properties, (void**)&prop, &pos) == SUCCESS) {
+                       zend_hash_get_current_key_ex(&from_ce->default_properties, &prop_name, &prop_name_len, &num_key, 0, &pos);
+
+                       /* We do not aggregate:
+                        * 1. private properties (heh, like we really have them) */
+                       if (prop_name[0] == '_' ||
+                       /* 2. explicitly excluded properties */
+                               (aggr_type == AGGREGATE_BY_LIST && zend_hash_exists(Z_ARRVAL_P(list_hash), prop_name, prop_name_len)) ||
+                       /* 3. properties matching regexp as modified by the exclusion flag */
+                               (aggr_type == AGGREGATE_BY_REGEXP && (pcre_exec(re, re_extra, prop_name, prop_name_len-1, 0, 0, NULL, 0) < 0) ^ exclude) == 1) {
+                               zend_hash_move_forward_ex(&from_ce->default_properties, &pos);
+                               continue;
+                       }
+
+                       /*
+                        * This is where the magic happens.
+                        */
+                       if (zend_hash_add(Z_OBJPROP_P(obj), prop_name, prop_name_len,
+                                                         (void*)prop, sizeof(zval *), NULL) == SUCCESS) {
+                               zval_add_ref(prop);
+                               add_next_index_stringl(aggr_props, prop_name, prop_name_len-1, 1);
+                       }
+
+                       zend_hash_move_forward_ex(&from_ce->default_properties, &pos);
+               }
+       } else {
+               /*
+                * This is just like above except the other way around.
+                */
+               zend_hash_internal_pointer_reset(Z_ARRVAL_P(list_hash));
+               while (zend_hash_get_current_key_ex(Z_ARRVAL_P(list_hash), &prop_name, &prop_name_len, &num_key, 0, NULL) == HASH_KEY_IS_STRING) {
+                       if (prop_name[0] == '_' ||
+                               zend_hash_find(&from_ce->default_properties, prop_name, prop_name_len, (void**)&prop) == FAILURE) {
+                               zend_hash_move_forward(Z_ARRVAL_P(list_hash));
+                               continue;
+                       }
+
+                       if (zend_hash_add(Z_OBJPROP_P(obj), prop_name, prop_name_len,
+                                                         (void*)prop, sizeof(zval *), NULL) == SUCCESS) {
+                               zval_add_ref(prop);
+                               add_next_index_stringl(aggr_props, prop_name, prop_name_len-1, 1);
+                       }
+
+                       zend_hash_move_forward(Z_ARRVAL_P(list_hash));
+               }
+       }
+
+       if (list_hash) {
+               zval_ptr_dtor(&list_hash);
+       }
+}
+/* }}} */
+
+
+/* {{{ static void aggregate() */
+static void aggregate(INTERNAL_FUNCTION_PARAMETERS, int aggr_what, int aggr_type)
+{
+       /* Incoming parameters. */
+       zval *obj, *aggr_list = NULL;
+       char *class_name, *class_name_lc, *aggr_regexp;
+       int class_name_len, aggr_regexp_len;
+       zend_bool exclude = 0;
+
+       /* Other variables. */
+       zval **aggr_members, z_aggr_regexp;
+       zend_class_entry *ce, *new_ce;
+       zend_function tmp_zend_function;
+       aggregation_info aggr_info_new, *aggr_info = &aggr_info_new;
+       zval *aggr_methods_new, **aggr_methods = &aggr_methods_new;
+       zval *aggr_props_new, **aggr_props = &aggr_props_new;
+       zval *aggr_filter = NULL;
+       int zpp_result = FAILURE;
+
+       /*
+        * Ah, the beauty of the new parameter parsing API. Almost as good as Heidi Klum.
+        */
+       switch (aggr_type) {
+               case AGGREGATE_ALL:
+                       zpp_result = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "os", &obj,
+                                                                                          &class_name, &class_name_len);
+                       break;
+
+               case AGGREGATE_BY_LIST:
+                       zpp_result = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "osa|b", &obj,
+                                                                                          &class_name, &class_name_len,
+                                                                                          &aggr_list, &exclude);
+                       break;
+
+               case AGGREGATE_BY_REGEXP:
+                       zpp_result = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "oss|b", &obj,
+                                                                                          &class_name, &class_name_len,
+                                                                                          &aggr_regexp, &aggr_regexp_len, &exclude);
+                       ZVAL_STRINGL(&z_aggr_regexp, aggr_regexp, aggr_regexp_len, 0);
+                       break;
+       }
+
+       if (zpp_result == FAILURE) {
+               return;
+       }
+
+       /*
+        * Case-insensitivity is like that last freaking mutant from horror movies:
+        * irradiated, blown in half, its eyes melting in their sockets, yet still
+        * dragging itself closer and closer to you until it's pulverized into
+        * microscopic pieces via some last-minute contrivance. And even then you
+        * are not sure that it's finally dead. But that's just how I feel.
+        */
+       class_name_lc = estrndup(class_name, class_name_len);
+       zend_str_tolower(class_name_lc, class_name_len);
+       if (zend_hash_find(EG(class_table), class_name_lc,
+                                          class_name_len+1, (void **)&ce) == FAILURE) {
+               php_error(E_WARNING, "%s() expects parameter 2 to be a valid class name, '%s' given\n", get_active_function_name(TSRMLS_C), class_name);
+               efree(class_name_lc);
+               return;
+       }
+
+       /*
+        * And God said, Let there be light; and there was light. But only once.
+        */
+       if (!BG(aggregation_table)) {
+               BG(aggregation_table) = (HashTable *) emalloc(sizeof(HashTable));
+               zend_hash_init(BG(aggregation_table), 5, NULL, (dtor_func_t) aggregation_info_dtor, 0);
+       }
+
+       /*
+        * Digging deep in the rabbit hole with a long object.. and coming up
+        * more empty than the imagination of whoever made "Battlefield Earth".
+        */ 
+       if (zend_hash_index_find(BG(aggregation_table), (long)obj, (void**)&aggr_info) == FAILURE) {
+               zval *tmp;
+
+               /*
+                * You are not expected to understand this.
+                */
+               new_ce = emalloc(sizeof(zend_class_entry));
+               new_ce->type = ZEND_USER_CLASS;
+               new_ce->name = estrndup(Z_OBJCE_P(obj)->name, Z_OBJCE_P(obj)->name_length);
+               new_ce->name_length = Z_OBJCE_P(obj)->name_length;
+               new_ce->parent = Z_OBJCE_P(obj)->parent;
+               new_ce->refcount = (int *) emalloc(sizeof(int));
+               *new_ce->refcount = 1;
+               new_ce->constants_updated = Z_OBJCE_P(obj)->constants_updated;
+               zend_hash_init(&new_ce->function_table, 10, NULL, ZEND_FUNCTION_DTOR, 0);
+               zend_hash_init(&new_ce->default_properties, 10, NULL, ZVAL_PTR_DTOR, 0);
+               zend_hash_copy(&new_ce->function_table, &Z_OBJCE_P(obj)->function_table, (copy_ctor_func_t) function_add_ref, &tmp_zend_function, sizeof(zend_function));
+               zend_hash_copy(&new_ce->default_properties, &Z_OBJCE_P(obj)->default_properties, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *));
+               new_ce->builtin_functions = Z_OBJCE_P(obj)->builtin_functions;
+               new_ce->handle_function_call = Z_OBJCE_P(obj)->handle_function_call;
+               new_ce->handle_property_get  = Z_OBJCE_P(obj)->handle_property_get;
+               new_ce->handle_property_set  = Z_OBJCE_P(obj)->handle_property_set;
+
+               /*
+                * Okay, that was kind of exhausting. Let's invoke programmer virtue #1
+                * and stuff this where it belongs so we don't have to work so hard next
+                * time.
+                */
+               Z_OBJCE_P(obj) = new_ce;
+               aggr_info_new.new_ce = new_ce;
+               MAKE_STD_ZVAL(aggr_info_new.aggr_members);
+               array_init(aggr_info_new.aggr_members);
+
+               zend_hash_index_update(BG(aggregation_table), (long)obj,
+                                                          (void *)&aggr_info_new, sizeof(aggregation_info), NULL);
+
+       } else {
+               /*
+                * Seek and ye shall find.
+                */
+               new_ce = aggr_info->new_ce;
+       }
+
+       /*
+        * This should be easy to understand. If not, ask Rasmus about it at his
+        * next tutorial.
+        */
+       if (zend_hash_find(Z_ARRVAL_P(aggr_info->aggr_members), class_name_lc,
+                                          class_name_len+1, (void **)&aggr_members) == FAILURE) {
+               zval *tmp;
+
+               MAKE_STD_ZVAL(tmp);
+               array_init(tmp);
+               MAKE_STD_ZVAL(aggr_methods_new);
+               array_init(aggr_methods_new);
+               MAKE_STD_ZVAL(aggr_props_new);
+               array_init(aggr_props_new);
+               add_assoc_zval_ex(tmp, "methods", sizeof("methods"), aggr_methods_new);
+               add_assoc_zval_ex(tmp, "properties", sizeof("properties"), aggr_props_new);
+
+               zend_hash_add(Z_ARRVAL_P(aggr_info->aggr_members), class_name_lc,
+                                         class_name_len+1, &tmp, sizeof(zval *), NULL);
+       } else {
+               zend_hash_find(Z_ARRVAL_PP(aggr_members), "methods", sizeof("methods"), (void**)&aggr_methods);
+               zend_hash_find(Z_ARRVAL_PP(aggr_members), "properties", sizeof("properties"), (void**)&aggr_props);
+       }
+
+       if (aggr_type == AGGREGATE_BY_LIST) {
+               aggr_filter = aggr_list;
+       } else if (aggr_type == AGGREGATE_BY_REGEXP) {
+               aggr_filter = &z_aggr_regexp;
+       }
+
+       if (aggr_what == AGGREGATE_METHODS || aggr_what == AGGREGATE_ALL) {
+               aggregate_methods(new_ce, ce, aggr_type, aggr_filter, exclude, *aggr_methods TSRMLS_CC);
+       }
+
+       if (aggr_what == AGGREGATE_PROPERTIES || aggr_what == AGGREGATE_ALL) {
+               aggregate_properties(obj, ce, aggr_type, aggr_filter, exclude, *aggr_props TSRMLS_CC);
+       }
+
+       /*
+        * Yes, we have to clean up after monsters. Tsk-tsk.
+        */
+       efree(class_name_lc);
+}
+/* }}} */
+
+
+/* {{{ proto void aggregate(object obj, string class)
+   */
+PHP_FUNCTION(aggregate)
+{
+       aggregate(INTERNAL_FUNCTION_PARAM_PASSTHRU, AGGREGATE_ALL, AGGREGATE_ALL);
+}
+/* }}} */
+
+
+/* {{{ proto void aggregate_methods(object obj, string class)
+   */
+PHP_FUNCTION(aggregate_methods)
+{
+       aggregate(INTERNAL_FUNCTION_PARAM_PASSTHRU, AGGREGATE_METHODS, AGGREGATE_ALL);
+}
+/* }}} */
+
+
+/* {{{ proto void aggregate_methods_by_list(object obj, string class, array method_list [, bool exclude])
+   */
+PHP_FUNCTION(aggregate_methods_by_list)
+{
+       aggregate(INTERNAL_FUNCTION_PARAM_PASSTHRU, AGGREGATE_METHODS, AGGREGATE_BY_LIST);
+}
+/* }}} */
+
+
+/* {{{ proto void aggregate_methods_by_regexp(object obj, string class, string regexp [, bool exclude])
+   */
+PHP_FUNCTION(aggregate_methods_by_regexp)
+{
+       aggregate(INTERNAL_FUNCTION_PARAM_PASSTHRU, AGGREGATE_METHODS, AGGREGATE_BY_REGEXP);
+}
+/* }}} */
+
+
+/* {{{ proto void aggregate_properties(object obj, string class)
+   */
+PHP_FUNCTION(aggregate_properties)
+{
+       aggregate(INTERNAL_FUNCTION_PARAM_PASSTHRU, AGGREGATE_PROPERTIES, AGGREGATE_ALL);
+}
+/* }}} */
+
+
+/* {{{ proto void aggregate_properites_by_list(object obj, string class, array props_list [, bool exclude])
+   */
+PHP_FUNCTION(aggregate_properties_by_list)
+{
+       aggregate(INTERNAL_FUNCTION_PARAM_PASSTHRU, AGGREGATE_PROPERTIES, AGGREGATE_BY_LIST);
+}
+/* }}} */
+
+
+/* {{{ proto void aggregate_properties_by_regexp(object obj, string class, string regexp [, bool exclude])
+   */
+PHP_FUNCTION(aggregate_properties_by_regexp)
+{
+       aggregate(INTERNAL_FUNCTION_PARAM_PASSTHRU, AGGREGATE_PROPERTIES, AGGREGATE_BY_REGEXP);
+}
+/* }}} */
+
+
+/* {{{ proto array aggregation_info(object obj)
+ */
+PHP_FUNCTION(aggregation_info)
+{
+       zval *obj;
+       aggregation_info *aggr_info;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &obj) == FAILURE) {
+               return;
+       }
+
+       if (!BG(aggregation_table) ||
+               zend_hash_index_find(BG(aggregation_table), (long)obj, (void**)&aggr_info) == FAILURE) {
+               RETURN_FALSE;
+       }
+
+       *return_value = *aggr_info->aggr_members;
+       zval_copy_ctor(return_value);
+}
+/* }}} */
+
+
+/* {{{ proto void deaggregate(object obj [, string class])
+ */
+PHP_FUNCTION(deaggregate)
+{
+       zval *obj;
+       char *class_name = NULL, *class_name_lc;
+       int class_name_len;
+       aggregation_info *aggr_info;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o|s", &obj,
+                                                         &class_name, &class_name_len) == FAILURE) {
+               return;
+       }
+
+       if (!BG(aggregation_table) ||
+               zend_hash_index_find(BG(aggregation_table), (long)obj, (void**)&aggr_info) == FAILURE) {
+               return;
+       }
+
+       if (class_name) {
+               zval **aggr_members,
+                        **aggr_methods,
+                        **aggr_props,
+                        **method, **prop;
+
+               class_name_lc = estrndup(class_name, class_name_len);
+               zend_str_tolower(class_name_lc, class_name_len);
+
+               if (zend_hash_find(Z_ARRVAL_P(aggr_info->aggr_members), class_name_lc,
+                                                  class_name_len+1, (void **)&aggr_members) == FAILURE) {
+                       efree(class_name_lc);
+                       return;
+               }
+
+               zend_hash_find(Z_ARRVAL_PP(aggr_members), "methods", sizeof("methods"), (void**)&aggr_methods);
+               for (zend_hash_internal_pointer_reset(Z_ARRVAL_PP(aggr_methods));
+                        zend_hash_get_current_data(Z_ARRVAL_PP(aggr_methods), (void**)&method) == SUCCESS;
+                        zend_hash_move_forward(Z_ARRVAL_PP(aggr_methods))) {
+                       zend_hash_del(&Z_OBJCE_P(obj)->function_table, Z_STRVAL_PP(method), Z_STRLEN_PP(method)+1);
+               }
+
+               zend_hash_find(Z_ARRVAL_PP(aggr_members), "properties", sizeof("properties"), (void**)&aggr_props);
+               for (zend_hash_internal_pointer_reset(Z_ARRVAL_PP(aggr_props));
+                        zend_hash_get_current_data(Z_ARRVAL_PP(aggr_props), (void**)&prop) == SUCCESS;
+                        zend_hash_move_forward(Z_ARRVAL_PP(aggr_props))) {
+                       zend_hash_del(Z_OBJPROP_P(obj), Z_STRVAL_PP(prop), Z_STRLEN_PP(prop)+1);
+               }
+
+               zend_hash_del(Z_ARRVAL_P(aggr_info->aggr_members), class_name_lc, class_name_len+1);
+
+               efree(class_name_lc);
+       } else {
+               zend_class_entry *orig_ce;
+               zval **aggr_members;
+               zval **aggr_props, **prop;
+
+               if (zend_hash_find(EG(class_table), Z_OBJCE_P(obj)->name,
+                                                  Z_OBJCE_P(obj)->name_length+1, (void **)&orig_ce) == FAILURE) {
+                       php_error(E_WARNING, "internal deaggregation error");
+                       return;
+               }
+
+               for (zend_hash_internal_pointer_reset(Z_ARRVAL_P(aggr_info->aggr_members));
+                        zend_hash_get_current_data(Z_ARRVAL_P(aggr_info->aggr_members), (void **)&aggr_members) == SUCCESS;
+                        zend_hash_move_forward(Z_ARRVAL_P(aggr_info->aggr_members))) {
+                       zend_hash_find(Z_ARRVAL_PP(aggr_members), "properties", sizeof("properties"), (void**)&aggr_props);
+                       for (zend_hash_internal_pointer_reset(Z_ARRVAL_PP(aggr_props));
+                                zend_hash_get_current_data(Z_ARRVAL_PP(aggr_props), (void**)&prop) == SUCCESS;
+                                zend_hash_move_forward(Z_ARRVAL_PP(aggr_props))) {
+                               zend_hash_del(Z_OBJPROP_P(obj), Z_STRVAL_PP(prop), Z_STRLEN_PP(prop)+1);
+                       }
+               }
+
+               Z_OBJCE_P(obj) = orig_ce;
+               zend_hash_index_del(BG(aggregation_table), (long)obj);
+       }
+}
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/ext/standard/aggregation.h b/ext/standard/aggregation.h
new file mode 100644 (file)
index 0000000..6103cb5
--- /dev/null
@@ -0,0 +1,51 @@
+/* 
+   +----------------------------------------------------------------------+
+   | PHP Version 4                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2002 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 2.02 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available at through the world-wide-web at                           |
+   | http://www.php.net/license/2_02.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.               |
+   +----------------------------------------------------------------------+
+   | Authors: Andrei Zmievski <andrei@php.net>                            |
+   +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifndef AGGREGATION_H
+#define AGGREGATION_H
+
+#define AGGREGATE_ALL                  0
+
+#define AGGREGATE_METHODS              1
+#define AGGREGATE_PROPERTIES   2
+
+#define AGGREGATE_BY_LIST      1
+#define AGGREGATE_BY_REGEXP    2
+
+/*
+ * Data structure that is stored in aggregation_table hashtable for each object.
+ */
+typedef struct {
+       zend_class_entry *new_ce;
+       zval *aggr_members;
+} aggregation_info;
+
+PHP_FUNCTION(aggregate);
+PHP_FUNCTION(aggregate_methods);
+PHP_FUNCTION(aggregate_methods_by_list);
+PHP_FUNCTION(aggregate_methods_by_regexp);
+PHP_FUNCTION(aggregate_properties);
+PHP_FUNCTION(aggregate_properties_by_list);
+PHP_FUNCTION(aggregate_properties_by_regexp);
+PHP_FUNCTION(aggregate);
+PHP_FUNCTION(deaggregate);
+PHP_FUNCTION(aggregation_info);
+
+#endif /* AGGREGATION_H */
index e4acca53879ebfd673ad8fe0e64e3b2445c90b2d..49cc891dbb4a19579b41d692207c422f29ad4938 100644 (file)
@@ -812,6 +812,17 @@ function_entry basic_functions[] = {
 #endif 
 
        PHP_FE(str_rot13, NULL)
+
+       /* functions from aggregate.c */
+       PHP_FE(aggregate,                                               first_arg_force_ref)
+       PHP_FE(aggregate_methods,                               first_arg_force_ref)
+       PHP_FE(aggregate_methods_by_list,               first_arg_force_ref)
+       PHP_FE(aggregate_methods_by_regexp,             first_arg_force_ref)
+       PHP_FE(aggregate_properties,                    first_arg_force_ref)
+       PHP_FE(aggregate_properties_by_list,    first_arg_force_ref)
+       PHP_FE(aggregate_properties_by_regexp,  first_arg_force_ref)
+       PHP_FE(deaggregate,                                             first_arg_force_ref)
+       PHP_FE(aggregation_info,                                first_arg_force_ref)
        {NULL, NULL, NULL}
 };
 
@@ -895,6 +906,7 @@ static void basic_globals_ctor(php_basic_globals *basic_globals_p TSRMLS_DC)
        BG(next) = NULL;
        BG(left) = -1;
        BG(user_tick_functions) = NULL;
+       BG(aggregation_table) = NULL;
        zend_hash_init(&BG(sm_protected_env_vars), 5, NULL, NULL, 1);
        BG(sm_allowed_env_vars) = NULL;
 
@@ -1100,6 +1112,13 @@ PHP_RSHUTDOWN_FUNCTION(basic)
                efree(BG(user_tick_functions));
                BG(user_tick_functions) = NULL;
        }
+
+       if (BG(aggregation_table)) {
+               zend_hash_destroy(BG(aggregation_table));
+               efree(BG(aggregation_table));
+               BG(aggregation_table) = NULL;
+       }
+
 #ifdef HAVE_MMAP
        if (BG(mmap_file)) {
                munmap(BG(mmap_file), BG(mmap_len));
@@ -2243,7 +2262,7 @@ PHP_FUNCTION(register_tick_function)
                BG(user_tick_functions) = (zend_llist *) emalloc(sizeof(zend_llist));
                zend_llist_init(BG(user_tick_functions),
                                                sizeof(user_tick_function_entry),
-                                               (void (*)(void *)) user_tick_function_dtor, 0);
+                                               (llist_dtor_func_t) user_tick_function_dtor, 0);
                php_add_tick_function(run_user_tick_functions);
        }
 
index 4a0c42cdfa1040be124fe9d14d69ebb4b619cc6c..7b3d83d764e72529f3081eed949f5bdd57309354 100644 (file)
@@ -180,6 +180,8 @@ typedef struct {
        void *mmap_file;
        size_t mmap_len;
 #endif
+
+       HashTable *aggregation_table;
 } php_basic_globals;
 
 #ifdef ZTS
index 6667fa6995284ef5858e55e6279fbffdd7f68133..5a2c6bcfd7b0a626c8fcfa3b0d39b71e9af9d503 100644 (file)
@@ -58,6 +58,7 @@
 #include "php_versioning.h"
 #include "php_ftok.h"
 #include "php_type.h"
+#include "aggregation.h"
 
 #define phpext_standard_ptr basic_functions_module_ptr
 
diff --git a/ext/standard/tests/aggregation/aggregate.lib.php b/ext/standard/tests/aggregation/aggregate.lib.php
new file mode 100644 (file)
index 0000000..3799285
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+
+class simple {
+       var $simple_prop = 100;
+
+       function simple()
+       {
+               print "I'm alive!\n";
+       }
+}
+
+class helper {
+       var $my_prop = 5;
+       var $your_prop = array('init' => PHP_VERSION);
+       var $our_prop = '****';
+       var $_priv_prop = null;
+
+       function helper()
+       {
+               print "just trying to help\n";
+       }
+
+       function do_this()
+       {
+               print "I'm helping!\n";
+       }
+
+       function do_that()
+       {
+               print "I'm aggregating!\n";
+       }
+
+       function just_another_method()
+       {
+               print "yep, that's me\n";
+       }
+
+       function _private()
+       {
+               print "Don't touch me!\n";
+       }
+
+       function __wakeup()
+       {
+       }
+}
+
+class mixin {
+       var $simple_prop = true;
+       var $mix = true;
+
+       function mix_it()
+       {
+               print "mixing\n";
+       }
+}
+
+class moby {
+       function mix_it()
+       {
+               print "I'm redundant!\n";
+       }
+}
+
+?>
diff --git a/ext/standard/tests/aggregation/aggregate.phpt b/ext/standard/tests/aggregation/aggregate.phpt
new file mode 100644 (file)
index 0000000..6fe21f8
--- /dev/null
@@ -0,0 +1,20 @@
+--TEST--
+aggregating everything
+--POST--
+--GET--
+--FILE--
+<?php
+include "./aggregate.lib.php";
+
+$obj = new simple();
+aggregate($obj, 'helper');
+$obj->do_this();
+$obj->do_that();
+print $obj->our_prop;
+
+?>
+--EXPECT--
+I'm alive!
+I'm helping!
+I'm aggregating!
+****
diff --git a/ext/standard/tests/aggregation/aggregate_methods.phpt b/ext/standard/tests/aggregation/aggregate_methods.phpt
new file mode 100644 (file)
index 0000000..4cd7db5
--- /dev/null
@@ -0,0 +1,25 @@
+--TEST--
+aggregating all methods
+--POST--
+--GET--
+--FILE--
+<?php
+include "./aggregate.lib.php";
+
+$obj = new simple();
+aggregate_methods($obj, 'mixin');
+$obj->mix_it();
+print $obj->simple_prop."\n";
+print implode(',', get_class_methods($obj))."\n";
+print implode(',', array_keys(get_object_vars($obj)))."\n";
+aggregate_methods($obj, 'moby');
+$obj->mix_it();
+
+?>
+--EXPECT--
+I'm alive!
+mixing
+100
+simple,mix_it
+simple_prop
+mixing
diff --git a/ext/standard/tests/aggregation/aggregate_methods_by_list.phpt b/ext/standard/tests/aggregation/aggregate_methods_by_list.phpt
new file mode 100644 (file)
index 0000000..390f577
--- /dev/null
@@ -0,0 +1,22 @@
+--TEST--
+aggregating methods specified in the list
+--POST--
+--GET--
+--FILE--
+<?php
+include "./aggregate.lib.php";
+
+$obj = new simple();
+aggregate_methods_by_list($obj, 'helper', array('just_another_method'));
+print implode(',', get_class_methods($obj))."\n";
+$obj2 = new simple();
+aggregate_methods_by_list($obj2, 'helper', array('just_another_method'), true);
+print implode(',', get_class_methods($obj2))."\n";
+$obj->just_another_method();
+?>
+--EXPECT--
+I'm alive!
+simple,just_another_method
+I'm alive!
+simple,do_this,do_that
+yep, that's me
diff --git a/ext/standard/tests/aggregation/aggregate_methods_by_regexp.phpt b/ext/standard/tests/aggregation/aggregate_methods_by_regexp.phpt
new file mode 100644 (file)
index 0000000..c229a85
--- /dev/null
@@ -0,0 +1,20 @@
+--TEST--
+aggregating methods matching regular expression
+--POST--
+--GET--
+--FILE--
+<?php
+include "./aggregate.lib.php";
+
+$obj = new simple();
+aggregate_methods_by_regexp($obj, 'helper', '/^do/');
+print implode(',', get_class_methods($obj))."\n";
+$obj2 = new simple();
+aggregate_methods_by_regexp($obj2, 'helper', '/^do/', true);
+print implode(',', get_class_methods($obj2))."\n";
+?>
+--EXPECT--
+I'm alive!
+simple,do_this,do_that
+I'm alive!
+simple,just_another_method
diff --git a/ext/standard/tests/aggregation/aggregate_properties.phpt b/ext/standard/tests/aggregation/aggregate_properties.phpt
new file mode 100644 (file)
index 0000000..cee3a4a
--- /dev/null
@@ -0,0 +1,19 @@
+--TEST--
+aggregating all default properties
+--POST--
+--GET--
+--FILE--
+<?php
+include "./aggregate.lib.php";
+
+$obj = new simple();
+aggregate_properties($obj, 'mixin');
+print implode(',', array_keys(get_object_vars($obj)))."\n";
+print $obj->simple_prop."\n";
+print implode(',', get_class_methods($obj))."\n";
+?>
+--EXPECT--
+I'm alive!
+simple_prop,mix
+100
+simple
diff --git a/ext/standard/tests/aggregation/aggregate_properties_by_list.phpt b/ext/standard/tests/aggregation/aggregate_properties_by_list.phpt
new file mode 100644 (file)
index 0000000..5349136
--- /dev/null
@@ -0,0 +1,20 @@
+--TEST--
+aggregating default properties specified in the list
+--POST--
+--GET--
+--FILE--
+<?php
+include "./aggregate.lib.php";
+
+$obj = new simple();
+aggregate_properties_by_list($obj, 'helper', array('my_prop', 'our_prop'));
+print implode(',', array_keys(get_object_vars($obj)))."\n";
+$obj2 = new simple();
+aggregate_properties_by_list($obj2, 'helper', array('my_prop'), true);
+print implode(',', array_keys(get_object_vars($obj2)))."\n";
+?>
+--EXPECT--
+I'm alive!
+simple_prop,my_prop,our_prop
+I'm alive!
+simple_prop,your_prop,our_prop
diff --git a/ext/standard/tests/aggregation/aggregate_properties_by_regexp.phpt b/ext/standard/tests/aggregation/aggregate_properties_by_regexp.phpt
new file mode 100644 (file)
index 0000000..bc8340b
--- /dev/null
@@ -0,0 +1,20 @@
+--TEST--
+aggregating default properties matching regular expression
+--POST--
+--GET--
+--FILE--
+<?php
+include "./aggregate.lib.php";
+
+$obj = new simple();
+aggregate_properties_by_regexp($obj, 'helper', '/^my/');
+print implode(',', array_keys(get_object_vars($obj)))."\n";
+$obj2 = new simple();
+aggregate_properties_by_regexp($obj2, 'helper', '/^my/', true);
+print implode(',', array_keys(get_object_vars($obj2)))."\n";
+?>
+--EXPECT--
+I'm alive!
+simple_prop,my_prop
+I'm alive!
+simple_prop,your_prop,our_prop
diff --git a/ext/standard/tests/aggregation/aggregation_info.phpt b/ext/standard/tests/aggregation/aggregation_info.phpt
new file mode 100644 (file)
index 0000000..624a3a3
--- /dev/null
@@ -0,0 +1,31 @@
+--TEST--
+retrieving aggregation info
+--POST--
+--GET--
+--FILE--
+<?php
+include "./aggregate.lib.php";
+
+$obj = new simple();
+aggregate($obj, 'mixin');
+print_r(aggregation_info($obj));
+?>
+--EXPECT--
+I'm alive!
+Array
+(
+    [mixin] => Array
+        (
+            [methods] => Array
+                (
+                    [0] => mix_it
+                )
+
+            [properties] => Array
+                (
+                    [0] => mix
+                )
+
+        )
+
+)
diff --git a/ext/standard/tests/aggregation/deaggregate.phpt b/ext/standard/tests/aggregation/deaggregate.phpt
new file mode 100644 (file)
index 0000000..a807089
--- /dev/null
@@ -0,0 +1,72 @@
+--TEST--
+deaggreating
+--POST--
+--GET--
+--FILE--
+<?php
+include "./aggregate.lib.php";
+
+$obj = new simple();
+aggregate($obj, 'helper');
+aggregate($obj, 'mixin');
+print_r(aggregation_info($obj));
+deaggregate($obj, 'helper');
+print_r(aggregation_info($obj));
+deaggregate($obj);
+var_dump(aggregation_info($obj));
+?>
+--EXPECT--
+I'm alive!
+Array
+(
+    [helper] => Array
+        (
+            [methods] => Array
+                (
+                    [0] => do_this
+                    [1] => do_that
+                    [2] => just_another_method
+                )
+
+            [properties] => Array
+                (
+                    [0] => my_prop
+                    [1] => your_prop
+                    [2] => our_prop
+                )
+
+        )
+
+    [mixin] => Array
+        (
+            [methods] => Array
+                (
+                    [0] => mix_it
+                )
+
+            [properties] => Array
+                (
+                    [0] => mix
+                )
+
+        )
+
+)
+Array
+(
+    [mixin] => Array
+        (
+            [methods] => Array
+                (
+                    [0] => mix_it
+                )
+
+            [properties] => Array
+                (
+                    [0] => mix
+                )
+
+        )
+
+)
+bool(false)