mirror of
https://github.com/php/php-src.git
synced 2024-09-21 09:57:23 +00:00
Added support for __callstatic() magic method. (Sara)
This commit is contained in:
parent
c560a96848
commit
b20ed0d2e0
1
NEWS
1
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)
|
||||
|
||||
|
20
Zend/tests/call_static.phpt
Executable file
20
Zend/tests/call_static.phpt
Executable 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
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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; \
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user