From: Andrei Zmievski Date: Mon, 15 Oct 2001 20:32:56 +0000 (+0000) Subject: Adding user-space object overloading extension. X-Git-Tag: POST_PARAMETER_PARSING_API~105 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=88d4de1da9c8a5d54d054c834e89ed175f7dfe70;p=php Adding user-space object overloading extension. --- diff --git a/NEWS b/NEWS index 2f35020f84..80ffb370f6 100644 --- 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 index 0000000000..31a2a57316 --- /dev/null +++ b/ext/overload/CREDITS @@ -0,0 +1,2 @@ +overload +Andrei Zmievski diff --git a/ext/overload/EXPERIMENTAL b/ext/overload/EXPERIMENTAL new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/overload/Makefile.in b/ext/overload/Makefile.in new file mode 100644 index 0000000000..0991be07fc --- /dev/null +++ b/ext/overload/Makefile.in @@ -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 index 0000000000..06c22ad89e --- /dev/null +++ b/ext/overload/README @@ -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 index 0000000000..073d2c1902 --- /dev/null +++ b/ext/overload/config.m4 @@ -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 index 0000000000..c3bc31045a --- /dev/null +++ b/ext/overload/overload.c @@ -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 index 0000000000..627f1ae9df --- /dev/null +++ b/ext/overload/php_overload.h @@ -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: + */