]> granicus.if.org Git - php/commitdiff
Adding user-space object overloading extension.
authorAndrei Zmievski <andrei@php.net>
Mon, 15 Oct 2001 20:32:56 +0000 (20:32 +0000)
committerAndrei Zmievski <andrei@php.net>
Mon, 15 Oct 2001 20:32:56 +0000 (20:32 +0000)
NEWS
ext/overload/CREDITS [new file with mode: 0644]
ext/overload/EXPERIMENTAL [new file with mode: 0644]
ext/overload/Makefile.in [new file with mode: 0644]
ext/overload/README [new file with mode: 0644]
ext/overload/config.m4 [new file with mode: 0644]
ext/overload/overload.c [new file with mode: 0644]
ext/overload/php_overload.h [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 2f35020f846e91e148378ee551f96c5e02eed848..80ffb370f656bea8131c388cd8b36f5ede704d44 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,7 @@
 PHP 4.0                                                                    NEWS
 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
 ?? ??? 200?, Version 4.2.0-dev
+- Added user-space object overloading extension. (Andrei)
 - Make UDM_PARAM_ISPELL_PREFIXES work under mnogosearch-3.2.x (gluke)
 - Added ldap_start_tls() function (Stig Venaas, patch by kuenne@rentec.com)
 - Added support for word match mnogosearch-3.2 command and for stopfile
diff --git a/ext/overload/CREDITS b/ext/overload/CREDITS
new file mode 100644 (file)
index 0000000..31a2a57
--- /dev/null
@@ -0,0 +1,2 @@
+overload
+Andrei Zmievski <andrei@php.net>
diff --git a/ext/overload/EXPERIMENTAL b/ext/overload/EXPERIMENTAL
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ext/overload/Makefile.in b/ext/overload/Makefile.in
new file mode 100644 (file)
index 0000000..0991be0
--- /dev/null
@@ -0,0 +1,8 @@
+# $Id$
+
+LTLIBRARY_NAME        = liboverload.la
+LTLIBRARY_SOURCES     = overload.c
+LTLIBRARY_SHARED_NAME = overload.la
+LTLIBRARY_SHARED_LIBADD  = $(OVERLOAD_SHARED_LIBADD)
+
+include $(top_srcdir)/build/dynlib.mk
diff --git a/ext/overload/README b/ext/overload/README
new file mode 100644 (file)
index 0000000..06c22ad
--- /dev/null
@@ -0,0 +1,72 @@
+This extension is experimental.
+
+That's all I'm required to say, as you should know the consequences, but
+I'll go ahead and add a few more notes.
+
+The purpose of this extension is to allow user-space overloading of object
+property access and method calls. It has only one function, overload() which
+takes the name of the class that should have this functionality enabled. But
+the class has to define appropriate methods if it wants to have this
+functionality: __get(), __set(), and __call(). So, overloading can be
+selective.
+
+Inside each handler the overloading is disabled so you can access object
+properties normally.
+
+
+Usage
+-----
+class OO {
+    var $a = 111;
+    var $elem = array('b' => 9, 'c' => 42);
+
+    function __get($prop_name, &$prop_value)
+    {
+        if (isset($this->elem[$prop_name])) {
+            $prop_value = $this->elem[$prop_name];
+            return true;
+        } else
+           return false;
+    }
+
+    function __set($prop_name, $prop_value)
+    {
+        $this->elem[$prop_name] = $prop_value;
+        return true;
+    }
+}
+
+overload('OO');
+
+$o = new OO;
+print "\$o->a: $o->a\n";
+print "\$o->b: $o->b\n";
+print "\$o->c: $o->c\n";
+print "\$o->d: $o->d\n";
+
+$val = new stdclass;
+$val->prop = 555;
+
+$o->a = array($val);
+var_dump($o->a[0]->prop);
+
+
+What works
+----------
+Whatever you can get it to do.
+
+
+What doesn't work
+-----------------
+__call() support.
+Invoking original overloading handlers, if the class had any.
+__set() only works to one level of property access, no chains yet
+Whatever I am forgetting about here.
+
+
+What might change
+-----------------
+Hell, anything, even the name of extension and its function.
+
+
+Feedback, please.
diff --git a/ext/overload/config.m4 b/ext/overload/config.m4
new file mode 100644 (file)
index 0000000..073d2c1
--- /dev/null
@@ -0,0 +1,10 @@
+dnl $Id$
+dnl config.m4 for extension overload
+
+PHP_ARG_ENABLE(overload,for user-space object overloading support,
+[  --enable-overload       Enable user-space object overloading support])
+
+if test "$PHP_OVERLOAD" != "no"; then
+       AC_DEFINE(HAVE_OVERLOAD, 1, [ ])
+       PHP_EXTENSION(overload, $ext_shared)
+fi
diff --git a/ext/overload/overload.c b/ext/overload/overload.c
new file mode 100644 (file)
index 0000000..c3bc310
--- /dev/null
@@ -0,0 +1,474 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP version 4.0                                                      |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997, 1998, 1999, 2000, 2001 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:                                                             |
+   |                                                                      |
+   +----------------------------------------------------------------------+
+ */
+
+/* 
+ * TODO:
+ * - provide a way for user to enable/disabling overloading of get/set/call
+ *   individually 
+ * - call original overloaded handlers if necessary
+ * + use local copy of CE with NULL'ed out handler when calling object's
+ *   overloaded function
+ * - handle both OE_IS_OBJECT and OE_IS_ARRAY in the whole chain
+ * + see how to fix the issue of object trying to set its own property inside
+ *   the handler
+ * - don't overload constructors?
+ * - flag to check if function exists in function table, then call it, otherwise
+ *   call handler (aka AUTOLOAD in Perl)
+ * + should it check for existing properties first before calling __get/__set:
+ *   yes
+ * + turn off all overloading handlers on a call to a handler
+ * - pass array overloading info on to handlers?
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_overload.h"
+
+#if HAVE_OVERLOAD
+
+#define GET_HANDLER  "__get"
+#define SET_HANDLER  "__set"
+#define CALL_HANDLER "__call"
+
+ZEND_DECLARE_MODULE_GLOBALS(overload)
+
+/* static int le_overload; */
+
+function_entry overload_functions[] = {
+       PHP_FE(overload,        NULL)
+       {NULL, NULL, NULL}
+};
+
+zend_module_entry overload_module_entry = {
+       STANDARD_MODULE_HEADER,
+       "overload",
+       overload_functions,
+       PHP_MINIT(overload),
+       NULL,
+       NULL,
+       NULL,
+       PHP_MINFO(overload),
+       NO_VERSION_YET,
+       STANDARD_MODULE_PROPERTIES
+};
+
+#ifdef COMPILE_DL_OVERLOAD
+ZEND_GET_MODULE(overload)
+#endif
+
+/* {{{ php_overload_init_globals
+ */
+static void php_overload_init_globals(zend_overload_globals *overload_globals)
+{
+       zend_hash_init_ex(&overload_globals->overloaded_classes, 10, NULL, NULL, 1, 0);
+}
+/* }}} */
+
+/* {{{ php_overload_destroy_globals
+ */
+static void php_overload_destroy_globals(zend_overload_globals *overload_globals)
+{
+       zend_hash_destroy(&overload_globals->overloaded_classes);
+}
+/* }}} */
+
+/* {{{ PHP_MINIT_FUNCTION
+ */
+PHP_MINIT_FUNCTION(overload)
+{
+       ZEND_INIT_MODULE_GLOBALS(overload, php_overload_init_globals, php_overload_destroy_globals);
+
+       /* If you have INI entries, uncomment these lines 
+       REGISTER_INI_ENTRIES();
+       */
+       return SUCCESS;
+}
+/* }}} */
+
+/* {{{ PHP_MINFO_FUNCTION
+ */
+PHP_MINFO_FUNCTION(overload)
+{
+       php_info_print_table_start();
+       php_info_print_table_header(2, "overload support", "enabled");
+       php_info_print_table_end();
+
+       /* Remove comments if you have entries in php.ini
+       DISPLAY_INI_ENTRIES();
+       */
+}
+/* }}} */
+
+typedef struct _oo_class_data {
+       void (*handle_function_call)(INTERNAL_FUNCTION_PARAMETERS, zend_property_reference *property_reference);
+       zval (*handle_property_get)(zend_property_reference *property_reference);
+       int (*handle_property_set)(zend_property_reference *property_reference, zval *value);
+} oo_class_data;
+
+#define DISABLE_HANDLERS(ce)          \
+       (ce).handle_property_get  = NULL; \
+       (ce).handle_property_set  = NULL; \
+       (ce).handle_function_call = NULL;
+
+static int call_get_handler(zval *object, zval *prop_name, zval **prop_value TSRMLS_DC)
+{
+       int call_result;
+       zend_class_entry temp_ce, *orig_ce;
+       zval result, *result_ptr = &result;
+       zval method_name;
+       zval **args[2];
+       zval *retval = NULL;
+       oo_class_data oo_data;
+
+       /*
+        * Save original CE of the object, and make it use a temporary one
+        * that has all handlers turned off. This is to avoid recursive
+        * calls to overload_property_get. We can't just change
+        * handle_property_get to NULL on the original CE, as that would
+        * disable overloading on other objects of the same class.
+        */
+       temp_ce = *Z_OBJCE_P(object);
+       DISABLE_HANDLERS(temp_ce);
+       orig_ce = Z_OBJCE_P(object);
+       Z_OBJCE_P(object) = &temp_ce;
+
+       result_ptr->is_ref = 1;
+       result_ptr->refcount = 1;
+       ZVAL_NULL(result_ptr);
+
+       ZVAL_STRINGL(&method_name, GET_HANDLER, sizeof(GET_HANDLER)-1, 0);
+       args[0] = &prop_name;
+       args[1] = &result_ptr;
+
+       call_result = call_user_function_ex(NULL,
+                                                                               &object,
+                                                                               &method_name,
+                                                                               &retval,
+                                                                               2, args,
+                                                                               0, NULL TSRMLS_CC);
+       /* Restore object's original CE. */
+       Z_OBJCE_P(object) = orig_ce;
+
+       if (call_result == FAILURE || !retval) {
+               php_error(E_WARNING, "unable to call %s::__get() handler", orig_ce->name);
+               return 0;
+       }
+
+       if (zval_is_true(retval)) {
+               REPLACE_ZVAL_VALUE(prop_value, result_ptr, 0);
+               zval_ptr_dtor(&retval);
+               return 1;
+       }
+
+       if (zend_hash_index_find(&OOG(overloaded_classes), (long)Z_OBJCE_P(object), (void*)&oo_data) == FAILURE) {
+               php_error(E_WARNING, "internal problem trying to get property");
+               return 0;
+       }
+
+       zval_ptr_dtor(&retval);
+       zval_dtor(result_ptr);
+
+       if (!oo_data.handle_property_get) {
+               return 0;
+       }
+
+       /* TODO: call original OO handler */
+
+       return 0;
+}
+
+int call_set_handler(zval *object, zval *prop_name, zval *value TSRMLS_DC)
+{
+       int call_result;
+       zend_class_entry temp_ce, *orig_ce;
+       zval method_name;
+       zval *value_copy;
+       zval **args[2];
+       zval *retval = NULL;
+       oo_class_data oo_data;
+
+       /*
+        * Save original CE of the object, and make it use a temporary one
+        * that has all handlers turned off. This is to avoid recursive
+        * calls to overload_property_set. We can't just change
+        * handle_property_set to NULL on the original CE, as that would
+        * disable overloading on other objects of the same class.
+        */
+       temp_ce = *Z_OBJCE_P(object);
+       DISABLE_HANDLERS(temp_ce);
+       orig_ce = Z_OBJCE_P(object);
+       Z_OBJCE_P(object) = &temp_ce;
+
+       ZVAL_STRINGL(&method_name, SET_HANDLER, sizeof(SET_HANDLER)-1, 0);
+       if (value->refcount == 0) {
+               MAKE_STD_ZVAL(value_copy);
+               *value_copy = *value;
+               zval_copy_ctor(value_copy);
+               value = value_copy;
+       }
+       args[0] = &prop_name;
+       args[1] = &value;
+
+       call_result = call_user_function_ex(NULL,
+                                                                               &object,
+                                                                               &method_name,
+                                                                               &retval,
+                                                                               2, args,
+                                                                               0, NULL TSRMLS_CC);
+       /* Restore object's original CE. */
+       Z_OBJCE_P(object) = orig_ce;
+
+       if (call_result == FAILURE || !retval) {
+               php_error(E_WARNING, "unable to call %s::__set() handler", orig_ce->name);
+               return 0;
+       }
+
+       if (zval_is_true(retval)) {
+               zval_ptr_dtor(&retval);
+               return 1;
+       }
+
+       if (zend_hash_index_find(&OOG(overloaded_classes), (long)Z_OBJCE_P(object), (void*)&oo_data) == FAILURE) {
+               php_error(E_WARNING, "internal problem trying to set property");
+               return 0;
+       }
+
+       zval_ptr_dtor(&retval);
+
+       if (!oo_data.handle_property_set) {
+               return 0;
+       }
+
+       /* TODO: call original OO handler */
+
+       return 0;
+
+}
+
+#define CLEANUP_OO_CHAIN() { \
+       for (; element; element=element->next) { \
+               zval_dtor(&((zend_overloaded_element *)element->data)->element); \
+       } \
+} \
+
+
+static zval overload_get_property(zend_property_reference *property_reference)
+{
+       zval result;
+       zval *result_ptr = &result;
+       zend_overloaded_element *overloaded_property;
+       zend_llist_element *element;
+       zval object = *property_reference->object;
+       zval **real_prop;
+       int got_prop = 0;
+       TSRMLS_FETCH();
+
+       INIT_PZVAL(result_ptr);
+
+       for (element=property_reference->elements_list->head; element; element=element->next) {
+               overloaded_property = (zend_overloaded_element *) element->data;
+               ZVAL_NULL(result_ptr);
+
+               if (Z_TYPE_P(overloaded_property) == OE_IS_OBJECT) {
+                       /* Trying to access a property on a non-object. */
+                       if (Z_TYPE(object) != IS_OBJECT) {
+                               CLEANUP_OO_CHAIN();
+                               return result;
+                       }
+
+                       if (zend_hash_find(Z_OBJPROP(object),
+                                                          Z_STRVAL(overloaded_property->element),
+                                                          Z_STRLEN(overloaded_property->element)+1,
+                                                          (void **)&real_prop) == SUCCESS) {
+                               result = **real_prop;
+                               /* printf("is_ref: %d, refcount: %d\n", (*real_prop)->is_ref, (*real_prop)->refcount); */
+                               /* REPLACE_ZVAL_VALUE(&result_ptr, *real_prop, 1); */
+                       } else if (!call_get_handler(&object,
+                                                                                &overloaded_property->element,
+                                                                                &result_ptr TSRMLS_CC)) {
+                               php_error(E_NOTICE, "Undefined property: %s", Z_STRVAL(overloaded_property->element));  
+                               CLEANUP_OO_CHAIN();
+                               return result;
+                       } else {
+                               got_prop = 1;
+                       }
+               } else if (Z_TYPE_P(overloaded_property) == OE_IS_ARRAY) {
+                       /* Trying to access index on a non-array. */
+                       if (Z_TYPE(object) != IS_ARRAY) {
+                               CLEANUP_OO_CHAIN();
+                               return result;
+                       }
+
+                       if (Z_TYPE(overloaded_property->element) == IS_STRING) {
+                               if (zend_hash_find(Z_ARRVAL(object),
+                                                                  Z_STRVAL(overloaded_property->element),
+                                                                  Z_STRLEN(overloaded_property->element)+1,
+                                                                  (void **)&real_prop) == FAILURE) {
+                                       CLEANUP_OO_CHAIN();
+                                       return result;
+                               }
+                       } else if (Z_TYPE(overloaded_property->element) == IS_LONG) {
+                               if (zend_hash_index_find(Z_ARRVAL(object),
+                                                                                 Z_LVAL(overloaded_property->element),
+                                                                                 (void **)&real_prop) == FAILURE) {
+                                       CLEANUP_OO_CHAIN();
+                                       return result;
+                               }
+                       }
+
+                       result = **real_prop;
+               }
+
+               zval_dtor(&overloaded_property->element);
+               /* printf("got_prop: %d\n", got_prop); */
+               if (element != property_reference->elements_list->head && got_prop) {
+                       zval_dtor(&object);
+                       got_prop = 0;
+               }
+
+               object = result;
+       }
+
+       if (!got_prop)
+               zval_copy_ctor(&result);
+
+       return result;
+}
+
+static int overload_set_property(zend_property_reference *property_reference, zval *value)
+{
+       zval result;
+       zval *result_ptr = &result;
+       zend_overloaded_element *overloaded_property;
+       zend_llist_element *element;
+       zval **object = &property_reference->object;
+       TSRMLS_FETCH();
+
+       for (element=property_reference->elements_list->head; element; element=element->next) {
+               overloaded_property = (zend_overloaded_element *) element->data;
+               ZVAL_NULL(result_ptr);
+
+               if (Z_TYPE_P(overloaded_property) == OE_IS_OBJECT) {
+                       /* Trying to access a property on a non-object. */
+                       if (Z_TYPE_PP(object) != IS_OBJECT) {
+                               CLEANUP_OO_CHAIN();
+                               return FAILURE;
+                       } 
+                       
+                       if (zend_hash_find(Z_OBJPROP_PP(object),
+                                                          Z_STRVAL(overloaded_property->element),
+                                                          Z_STRLEN(overloaded_property->element)+1,
+                                                          (void **)&object) == FAILURE) {
+
+                               if (element == property_reference->elements_list->tail) {
+                                       if (!call_set_handler(*object,
+                                                                                 &overloaded_property->element,
+                                                                                 value TSRMLS_CC)) {
+                                               CLEANUP_OO_CHAIN();
+                                               return FAILURE;
+                                       }
+                                       CLEANUP_OO_CHAIN();
+                                       return SUCCESS;
+                               }
+
+                               if (!call_get_handler(*object,
+                                                                         &overloaded_property->element,
+                                                                         &result_ptr TSRMLS_CC)) {
+                                       php_error(E_NOTICE, "Undefined property: %s", Z_STRVAL(overloaded_property->element));  
+                                       CLEANUP_OO_CHAIN();
+                                       return FAILURE;
+                               } else
+                                       object = &result_ptr;
+                       }
+               } else if (Z_TYPE_P(overloaded_property) == OE_IS_ARRAY) {
+               }
+
+               zval_dtor(&overloaded_property->element);
+       }
+
+       /* printf("value is_ref: %d, refcount: %d\n", value->is_ref, value->refcount); */
+       REPLACE_ZVAL_VALUE(object, value, 1);
+       /* printf("object is_ref: %d, refcount: %d\n", (*object)->is_ref, (*object)->refcount); */
+
+       return SUCCESS;
+}
+
+static void overload_call_method(INTERNAL_FUNCTION_PARAMETERS, zend_property_reference *property_reference)
+{
+}
+
+/* {{{ proto void overload(string class_entry)
+    Enables property and method call overloading for a class. */
+PHP_FUNCTION(overload)
+{
+       char *class_entry = NULL;
+       int argc = ZEND_NUM_ARGS();
+       int class_entry_len;
+       zend_class_entry *ce = NULL;
+       oo_class_data oo_data;
+
+       if (zend_parse_parameters(argc TSRMLS_CC, "s/", &class_entry, &class_entry_len) == FAILURE) 
+               return;
+
+       zend_str_tolower(class_entry, class_entry_len);
+       if (zend_hash_find(EG(class_table), class_entry, class_entry_len+1, (void**)&ce) == FAILURE) {
+               php_error(E_WARNING, "%s() was unable to locate class '%s'", get_active_function_name(TSRMLS_C), class_entry);
+               RETURN_FALSE;
+       }
+
+       /* Check if the handlers have already been installed for this class. */
+       if (zend_hash_index_exists(&OOG(overloaded_classes), (long)ce)) {
+               RETURN_TRUE;
+       }
+
+       if (zend_hash_exists(&ce->function_table, GET_HANDLER, sizeof(GET_HANDLER))) {
+               oo_data.handle_property_get = ce->handle_property_get;
+               ce->handle_property_get = overload_get_property;
+       } else
+               oo_data.handle_property_get = NULL;
+
+       if (zend_hash_exists(&ce->function_table, SET_HANDLER, sizeof(SET_HANDLER))) {
+               oo_data.handle_property_set = ce->handle_property_set;
+               ce->handle_property_set = overload_set_property;
+       } else
+               oo_data.handle_property_set = NULL;
+
+       /*
+       oo_data.handle_function_call = ce->handle_function_call;
+       */
+
+       zend_hash_index_update(&OOG(overloaded_classes), (long)ce, &oo_data, sizeof(oo_data), NULL);
+
+       RETURN_TRUE;
+}
+/* }}} */
+
+#endif /* HAVE_OVERLOAD */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/ext/overload/php_overload.h b/ext/overload/php_overload.h
new file mode 100644 (file)
index 0000000..627f1ae
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP version 4.0                                                      |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997, 1998, 1999, 2000, 2001 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:                                                             |
+   |                                                                      |
+   +----------------------------------------------------------------------+
+ */
+
+#ifndef PHP_OVERLOAD_H
+#define PHP_OVERLOAD_H
+
+#if HAVE_OVERLOAD
+
+extern zend_module_entry overload_module_entry;
+#define phpext_overload_ptr &overload_module_entry
+
+#ifdef PHP_WIN32
+#define PHP_OVERLOAD_API __declspec(dllexport)
+#else
+#define PHP_OVERLOAD_API
+#endif
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+
+PHP_MINIT_FUNCTION(overload);
+PHP_MSHUTDOWN_FUNCTION(overload);
+PHP_RINIT_FUNCTION(overload);
+PHP_RSHUTDOWN_FUNCTION(overload);
+PHP_MINFO_FUNCTION(overload);
+
+PHP_FUNCTION(overload);
+
+ZEND_BEGIN_MODULE_GLOBALS(overload)
+       HashTable overloaded_classes;
+ZEND_END_MODULE_GLOBALS(overload)
+
+#ifdef ZTS
+#define OOG(v) TSRMG(overload_globals_id, zend_overload_globals *, v)
+#else
+#define OOG(v) (overload_globals.v)
+#endif
+
+#endif /* HAVE_OVERLOAD */
+
+#endif /* PHP_OVERLOAD_H */
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * indent-tabs-mode: t
+ * End:
+ */