From: Dmitry Stogov Date: Sat, 29 Sep 2007 08:52:40 +0000 (+0000) Subject: Added support for __callstatic() magic method. (Sara) X-Git-Tag: BEFORE_IMPORT_OF_MYSQLND_IN_5_3~62 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b20ed0d2e01861d5691395062e2167c5e9e9e1c9;p=php Added support for __callstatic() magic method. (Sara) --- diff --git a/NEWS b/NEWS index faca0c2468..3299743e77 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ PHP NEWS ?? ??? 20??, PHP 5.3.0 - Added support for namespaces. (Dmitry, Stas) - Added support for Late Static Binding. (Dmitry, Etienne Kneuss) +- Added support for __callstatic() magic method. (Sara) - Added support for dynamic access of static members using $foo::myFunc(). (Etienne Kneuss) diff --git a/Zend/tests/call_static.phpt b/Zend/tests/call_static.phpt new file mode 100755 index 0000000000..e77a158914 --- /dev/null +++ b/Zend/tests/call_static.phpt @@ -0,0 +1,20 @@ +--TEST-- +__callStatic() Magic method +--FILE-- +const_set = 1; echo $y,"\n"; $x->const_call(); echo $y,"\n"; +foo::const_callstatic(); +echo $y,"\n"; $z = $x["const_dim_get"]; echo $y,"\n"; $x["const_dim_set"] = 1; @@ -136,6 +141,7 @@ echo $y,"\n"; const_get const_set const_call +const_callstatic const_dim_get const_dim_set const_dim_isset diff --git a/Zend/zend.h b/Zend/zend.h index 29f3aea103..63b2e51889 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -340,6 +340,7 @@ struct _zend_class_entry { union _zend_function *__unset; union _zend_function *__isset; union _zend_function *__call; + union _zend_function *__callstatic; union _zend_function *__tostring; union _zend_function *serialize_func; union _zend_function *unserialize_func; diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 184812941e..bce509d24a 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1623,6 +1623,16 @@ ZEND_API void zend_check_magic_method_implementation(zend_class_entry *ce, zend_ } else if (ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ce->name, ZEND_CALL_FUNC_NAME); } + } else if (name_len == sizeof(ZEND_CALLSTATIC_FUNC_NAME) - 1 && + !memcmp(lcname, ZEND_CALLSTATIC_FUNC_NAME, sizeof(ZEND_CALLSTATIC_FUNC_NAME)-1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ce->name, ZEND_CALLSTATIC_FUNC_NAME); + } else if (ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ce->name, ZEND_CALLSTATIC_FUNC_NAME); + } + } else if (name_len == sizeof(ZEND_TOSTRING_FUNC_NAME) - 1 && + !memcmp(lcname, ZEND_TOSTRING_FUNC_NAME, sizeof(ZEND_TOSTRING_FUNC_NAME)-1) && fptr->common.num_args != 0) { + zend_error(error_type, "Method %s::%s() cannot take arguments", ce->name, ZEND_TOSTRING_FUNC_NAME); } } @@ -1635,7 +1645,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio int count=0, unload=0; HashTable *target_function_table = function_table; int error_type; - zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__unset = NULL, *__isset = NULL, *__call = NULL, *__tostring = NULL; + zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__unset = NULL, *__isset = NULL, *__call = NULL, *__callstatic = NULL, *__tostring = NULL; char *lowercase_name; int fname_len; char *lc_class_name = NULL; @@ -1748,6 +1758,8 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio clone = reg_function; } else if ((fname_len == sizeof(ZEND_CALL_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME))) { __call = reg_function; + } else if ((fname_len == sizeof(ZEND_CALLSTATIC_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_CALLSTATIC_FUNC_NAME, sizeof(ZEND_CALLSTATIC_FUNC_NAME))) { + __callstatic = reg_function; } else if ((fname_len == sizeof(ZEND_TOSTRING_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_TOSTRING_FUNC_NAME, sizeof(ZEND_TOSTRING_FUNC_NAME))) { __tostring = reg_function; } else if ((fname_len == sizeof(ZEND_GET_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_GET_FUNC_NAME, sizeof(ZEND_GET_FUNC_NAME))) { @@ -1787,6 +1799,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio scope->destructor = dtor; scope->clone = clone; scope->__call = __call; + scope->__callstatic = __callstatic; scope->__tostring = __tostring; scope->__get = __get; scope->__set = __set; @@ -1819,6 +1832,12 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio } __call->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC; } + if (__callstatic) { + if (!(__callstatic->common.fn_flags & ZEND_ACC_STATIC)) { + zend_error(error_type, "Method %s::%s() must be static", scope->name, __callstatic->common.function_name); + } + __callstatic->common.fn_flags |= ZEND_ACC_STATIC; + } if (__tostring) { if (__tostring->common.fn_flags & ZEND_ACC_STATIC) { zend_error(error_type, "Method %s::%s() cannot be static", scope->name, __tostring->common.function_name); @@ -2202,6 +2221,9 @@ static int zend_is_callable_check_func(int check_flags, zval ***zobj_ptr_ptr, ze if (*zobj_ptr_ptr && *ce_ptr && (*ce_ptr)->__call != 0) { retval = (*ce_ptr)->__call != NULL; *fptr_ptr = (*ce_ptr)->__call; + } else if (!*zobj_ptr_ptr && *ce_ptr && (*ce_ptr)->__callstatic) { + retval = 1; + *fptr_ptr = (*ce_ptr)->__callstatic; } } else { *fptr_ptr = fptr; @@ -2397,6 +2419,7 @@ ZEND_API zend_bool zend_make_callable(zval *callable, char **callable_name TSRML ZEND_API int zend_fcall_info_init(zval *callable, zend_fcall_info *fci, zend_fcall_info_cache *fcc TSRMLS_DC) { + int len; zend_class_entry *ce; zend_function *func; zval **obj; @@ -2415,7 +2438,11 @@ ZEND_API int zend_fcall_info_init(zval *callable, zend_fcall_info *fci, zend_fca fci->no_separation = 1; fci->symbol_table = NULL; - if (strlen(func->common.function_name) == sizeof(ZEND_CALL_FUNC_NAME) - 1 && !memcmp(func->common.function_name, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME))) { + len = strlen(func->common.function_name); + if ((len == sizeof(ZEND_CALL_FUNC_NAME) - 1 && + !memcmp(func->common.function_name, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)-1)) || + (len == sizeof(ZEND_CALLSTATIC_FUNC_NAME) - 1 && + !memcmp(func->common.function_name, ZEND_CALLSTATIC_FUNC_NAME, sizeof(ZEND_CALLSTATIC_FUNC_NAME)-1))) { fcc->initialized = 0; fcc->function_handler = NULL; fcc->calling_scope = NULL; diff --git a/Zend/zend_API.h b/Zend/zend_API.h index b4b27c41b4..84d7949aed 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -139,6 +139,7 @@ typedef struct _zend_function_entry { class_container.create_object = NULL; \ class_container.interface_gets_implemented = NULL; \ class_container.__call = handle_fcall; \ + class_container.__callstatic = handle_fcall; \ class_container.__tostring = NULL; \ class_container.__get = handle_propget; \ class_container.__set = handle_propset; \ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index e60df0435b..7c4f4a87fc 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1148,21 +1148,23 @@ void zend_do_begin_function_declaration(znode *function_token, znode *function_n zend_error(E_STRICT, "Redefining already defined constructor for class %s", CG(active_class_entry)->name); } CG(active_class_entry)->constructor = (zend_function *) CG(active_op_array); - } else if ((name_len == sizeof(ZEND_DESTRUCTOR_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_DESTRUCTOR_FUNC_NAME, sizeof(ZEND_DESTRUCTOR_FUNC_NAME)))) { + } else if ((name_len == sizeof(ZEND_DESTRUCTOR_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_DESTRUCTOR_FUNC_NAME, sizeof(ZEND_DESTRUCTOR_FUNC_NAME)-1))) { CG(active_class_entry)->destructor = (zend_function *) CG(active_op_array); - } else if ((name_len == sizeof(ZEND_CLONE_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_CLONE_FUNC_NAME, sizeof(ZEND_CLONE_FUNC_NAME)))) { + } else if ((name_len == sizeof(ZEND_CLONE_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_CLONE_FUNC_NAME, sizeof(ZEND_CLONE_FUNC_NAME)-1))) { CG(active_class_entry)->clone = (zend_function *) CG(active_op_array); - } else if ((name_len == sizeof(ZEND_CALL_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)))) { + } else if ((name_len == sizeof(ZEND_CALL_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)-1))) { CG(active_class_entry)->__call = (zend_function *) CG(active_op_array); - } else if ((name_len == sizeof(ZEND_GET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_GET_FUNC_NAME, sizeof(ZEND_GET_FUNC_NAME)))) { + } else if ((name_len == sizeof(ZEND_CALLSTATIC_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_CALLSTATIC_FUNC_NAME, sizeof(ZEND_CALLSTATIC_FUNC_NAME)-1))) { + CG(active_class_entry)->__callstatic = (zend_function *) CG(active_op_array); + } else if ((name_len == sizeof(ZEND_GET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_GET_FUNC_NAME, sizeof(ZEND_GET_FUNC_NAME)-1))) { CG(active_class_entry)->__get = (zend_function *) CG(active_op_array); - } else if ((name_len == sizeof(ZEND_SET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_SET_FUNC_NAME, sizeof(ZEND_SET_FUNC_NAME)))) { + } else if ((name_len == sizeof(ZEND_SET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_SET_FUNC_NAME, sizeof(ZEND_SET_FUNC_NAME)-1))) { CG(active_class_entry)->__set = (zend_function *) CG(active_op_array); - } else if ((name_len == sizeof(ZEND_UNSET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_UNSET_FUNC_NAME, sizeof(ZEND_UNSET_FUNC_NAME)))) { + } else if ((name_len == sizeof(ZEND_UNSET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_UNSET_FUNC_NAME, sizeof(ZEND_UNSET_FUNC_NAME)-1))) { CG(active_class_entry)->__unset = (zend_function *) CG(active_op_array); - } else if ((name_len == sizeof(ZEND_ISSET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_ISSET_FUNC_NAME, sizeof(ZEND_ISSET_FUNC_NAME)))) { + } else if ((name_len == sizeof(ZEND_ISSET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_ISSET_FUNC_NAME, sizeof(ZEND_ISSET_FUNC_NAME)-1))) { CG(active_class_entry)->__isset = (zend_function *) CG(active_op_array); - } else if ((name_len == sizeof(ZEND_TOSTRING_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_TOSTRING_FUNC_NAME, sizeof(ZEND_TOSTRING_FUNC_NAME)))) { + } else if ((name_len == sizeof(ZEND_TOSTRING_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_TOSTRING_FUNC_NAME, sizeof(ZEND_TOSTRING_FUNC_NAME)-1))) { CG(active_class_entry)->__tostring = (zend_function *) CG(active_op_array); } else if (!(fn_flags & ZEND_ACC_STATIC)) { CG(active_op_array)->fn_flags |= ZEND_ACC_ALLOW_STATIC; @@ -2053,6 +2055,9 @@ static void do_inherit_parent_constructor(zend_class_entry *ce) if (!ce->__call) { ce->__call = ce->parent->__call; } + if (!ce->__callstatic) { + ce->__callstatic = ce->parent->__callstatic; + } if (!ce->__tostring) { ce->__tostring = ce->parent->__tostring; } @@ -4480,6 +4485,7 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify ce->__unset = NULL; ce->__isset = NULL; ce->__call = NULL; + ce->__callstatic = NULL; ce->__tostring = NULL; ce->create_object = NULL; ce->get_iterator = NULL; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 91657d86ee..0bd84019b7 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -704,6 +704,7 @@ END_EXTERN_C() #define ZEND_UNSET_FUNC_NAME "__unset" #define ZEND_ISSET_FUNC_NAME "__isset" #define ZEND_CALL_FUNC_NAME "__call" +#define ZEND_CALLSTATIC_FUNC_NAME "__callstatic" #define ZEND_TOSTRING_FUNC_NAME "__tostring" #define ZEND_AUTOLOAD_FUNC_NAME "__autoload" diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 901c28ce68..5e741a0737 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -824,19 +824,74 @@ static union _zend_function *zend_std_get_method(zval **object_ptr, char *method return fbc; } +ZEND_API void zend_std_callstatic_user_call(INTERNAL_FUNCTION_PARAMETERS) /* {{{ */ +{ + zend_internal_function *func = (zend_internal_function *)EG(function_state_ptr)->function; + zval *method_name_ptr, *method_args_ptr; + zval *method_result_ptr = NULL; + zend_class_entry *ce = EG(scope); + + ALLOC_ZVAL(method_args_ptr); + INIT_PZVAL(method_args_ptr); + array_init(method_args_ptr); + + if (zend_copy_parameters_array(ZEND_NUM_ARGS(), method_args_ptr TSRMLS_CC) == FAILURE) { + zval_dtor(method_args_ptr); + zend_error(E_ERROR, "Cannot get arguments for " ZEND_CALLSTATIC_FUNC_NAME); + RETURN_FALSE; + } + + ALLOC_ZVAL(method_name_ptr); + INIT_PZVAL(method_name_ptr); + ZVAL_STRING(method_name_ptr, func->function_name, 0); /* no dup - it's a copy */ + + /* __callStatic handler is called with two arguments: + method name + array of method parameters + */ + zend_call_method_with_2_params(NULL, ce, &ce->__callstatic, ZEND_CALLSTATIC_FUNC_NAME, &method_result_ptr, method_name_ptr, method_args_ptr); + + if (method_result_ptr) { + if (method_result_ptr->is_ref || method_result_ptr->refcount > 1) { + RETVAL_ZVAL(method_result_ptr, 1, 1); + } else { + RETVAL_ZVAL(method_result_ptr, 0, 1); + } + } + + /* now destruct all auxiliaries */ + zval_ptr_dtor(&method_args_ptr); + zval_ptr_dtor(&method_name_ptr); + + /* destruct the function also, then - we have allocated it in get_method */ + efree(func); +} +/* }}} */ /* This is not (yet?) in the API, but it belongs in the built-in objects callbacks */ ZEND_API zend_function *zend_std_get_static_method(zend_class_entry *ce, char *function_name_strval, int function_name_strlen TSRMLS_DC) { zend_function *fbc; - if (zend_hash_find(&ce->function_table, function_name_strval, function_name_strlen+1, (void **) &fbc)==FAILURE) { - char *class_name = ce->name; - - if (!class_name) { - class_name = ""; + if (zend_hash_find(&ce->function_table, function_name_strval, function_name_strlen + 1, (void **) &fbc)==FAILURE) { + if (ce->__callstatic) { + zend_internal_function *callstatic_user_call = emalloc(sizeof(zend_internal_function)); + + callstatic_user_call->type = ZEND_INTERNAL_FUNCTION; + callstatic_user_call->module = ce->module; + callstatic_user_call->handler = zend_std_callstatic_user_call; + callstatic_user_call->arg_info = NULL; + callstatic_user_call->num_args = 0; + callstatic_user_call->scope = ce; + callstatic_user_call->fn_flags = ZEND_ACC_STATIC | ZEND_ACC_PUBLIC; + callstatic_user_call->function_name = estrndup(function_name_strval, function_name_strlen); + callstatic_user_call->pass_rest_by_reference = 0; + callstatic_user_call->return_reference = ZEND_RETURN_VALUE; + + return (zend_function *)callstatic_user_call; + } else { + zend_error(E_ERROR, "Call to undefined method %s::%s()", ce->name ? ce->name : "", function_name_strval); } - zend_error(E_ERROR, "Call to undefined method %s::%s()", class_name, function_name_strval); } if (fbc->op_array.fn_flags & ZEND_ACC_PUBLIC) { /* No further checks necessary, most common case */