]> granicus.if.org Git - php/commitdiff
Added support for __callstatic() magic method. (Sara)
authorDmitry Stogov <dmitry@php.net>
Sat, 29 Sep 2007 08:52:40 +0000 (08:52 +0000)
committerDmitry Stogov <dmitry@php.net>
Sat, 29 Sep 2007 08:52:40 +0000 (08:52 +0000)
NEWS
Zend/tests/call_static.phpt [new file with mode: 0755]
Zend/tests/object_handlers.phpt
Zend/zend.h
Zend/zend_API.c
Zend/zend_API.h
Zend/zend_compile.c
Zend/zend_compile.h
Zend/zend_object_handlers.c

diff --git a/NEWS b/NEWS
index faca0c24686eb7dddc32b44cdd2a1fc34ffb1153..3299743e773cb5ea05a14a5a7c2b71b7d9dac781 100644 (file)
--- 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 (executable)
index 0000000..e77a158
--- /dev/null
@@ -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
index c14f6047491166b11211ab558d26bcdab7a47045..3e8dfcc537062b10d45f852a89b6a9fa2d43b45b 100755 (executable)
@@ -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
index 29f3aea1039b2dd540250d64db3bb12bfa12bf43..63b2e51889e11697b880d19070bd06cb2a50eb3e 100644 (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;
index 184812941e602aa3a8266df2cfc3074daf7d33dc..bce509d24adfabc1d2c4b72b3285b98fb0e73fea 100644 (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;
index b4b27c41b4993b529430eb2d9036f04c5ab9eede..84d7949aed90742220158d8aa27cf8e9eeffac72 100644 (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;                                 \
index e60df0435b20d7f1f276e4ce8be213b377c1db5d..7c4f4a87fc421dc7aeaac17e9d94030e9d19e339 100644 (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;
index 91657d86ee5527d1b9be9a627ab1c60e605034e6..0bd84019b774bc3721de6af1a1c26084dadb5a83 100644 (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"
 
index 901c28ce689710d180200c0a683e631562876a34..5e741a0737c5df5644a9ab64f0ffe70538c20694 100644 (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 (!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 */