Added support for __callstatic() magic method. (Sara)

This commit is contained in:
Dmitry Stogov 2007-09-29 08:52:40 +00:00
parent c560a96848
commit b20ed0d2e0
9 changed files with 133 additions and 15 deletions

1
NEWS
View File

@ -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)

20
Zend/tests/call_static.phpt Executable file
View File

@ -0,0 +1,20 @@
--TEST--
__callStatic() Magic method
--FILE--
<?php
class Test
{
static function __callStatic($fname, $args)
{
echo $fname, '() called with ', count($args), " arguments\n";
}
}
call_user_func("Test::Two", 'A', 'B');
call_user_func(array("Test", "Three"), NULL, 0, false);
Test::Four(5, 6, 7, 8);
--EXPECT--
two() called with 2 arguments
three() called with 3 arguments
four() called with 4 arguments

View File

@ -16,6 +16,9 @@ class foo implements ArrayAccess {
function __call($func, $args) {
$GLOBALS["y"] = $func;
}
static function __callStatic($func, $args) {
$GLOBALS["y"] = $func;
}
function offsetGet($index) {
$GLOBALS["y"] = $index;
}
@ -40,6 +43,8 @@ $x->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

View File

@ -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;

View File

@ -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;

View File

@ -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; \

View File

@ -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;

View File

@ -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"

View File

@ -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 (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));
if (!class_name) {
class_name = "";
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 */