Attached is a patch which exports an internals hook in zend_class_entry for fetching function pointers similar to the object hook get_method() available to instance methods.

This patch also exports a userspace hook __call_static() which operates in the fashion of the current __call() magic method, but for static calls.

Wez called for some functionality like this a few weeks ago for a COM wrapper (or something similar), and I noticed there was actually a comment in the engine about how this should eventually be done anyway...

Usage example:

class foo
{
    public static function __call_static($fname, $args)
    {
        echo "foo::{$fname}() called staticly with ",
             count($args), " parameters\n";
    }
}

foo::randomMethod(1,2,3);


I considered setting get_static_method to zend_std_get_static_method() by default (avoiding the if set check during runtime), but all the other hoooks follow this pattern so I went with consistency.

If noone comments to the negative, I'll commit next friday (7/20)...

-Sara

Index: Zend/zend.h
===================================================================
RCS file: /repository/ZendEngine2/zend.h,v
retrieving revision 1.334
diff -u -p -r1.334 zend.h
--- Zend/zend.h 26 Apr 2007 19:08:24 -0000      1.334
+++ Zend/zend.h 14 Jul 2007 00:42:05 -0000
@@ -378,6 +378,7 @@ struct _zend_class_entry {
        union _zend_function *__unset;
        union _zend_function *__isset;
        union _zend_function *__call;
+       union _zend_function *__call_static;
        union _zend_function *__tostring;
        union _zend_function *serialize_func;
        union _zend_function *unserialize_func;
@@ -388,6 +389,7 @@ struct _zend_class_entry {
        zend_object_value (*create_object)(zend_class_entry *class_type 
TSRMLS_DC);
        zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval 
*object, int by_ref TSRMLS_DC);
        int (*interface_gets_implemented)(zend_class_entry *iface, 
zend_class_entry *class_type TSRMLS_DC); /* a class implements this interface */
+       union _zend_function *(*get_static_method)(zend_class_entry *ce, zstr 
method, int method_len TSRMLS_DC);
 
        /* serializer callbacks */
        int (*serialize)(zval *object, int *type, zstr *buffer, zend_uint 
*buf_len, zend_serialize_data *data TSRMLS_DC);
Index: Zend/zend_API.c
===================================================================
RCS file: /repository/ZendEngine2/zend_API.c,v
retrieving revision 1.439
diff -u -p -r1.439 zend_API.c
--- Zend/zend_API.c     13 Jul 2007 23:51:51 -0000      1.439
+++ Zend/zend_API.c     14 Jul 2007 00:42:05 -0000
@@ -2026,6 +2026,9 @@ ZEND_API void zend_check_magic_method_im
        } else if (lcname_len == sizeof(ZEND_CALL_FUNC_NAME) - 1 &&
            ZEND_U_EQUAL(utype, lcname, lcname_len, ZEND_CALL_FUNC_NAME, 
sizeof(ZEND_CALL_FUNC_NAME)-1) && fptr->common.num_args != 2) {
                zend_error(error_type, "Method %v::%s() must take exactly 2 
arguments", ce->name, ZEND_CALL_FUNC_NAME);
+       } else if (lcname_len == sizeof(ZEND_CALL_STATIC_FUNC_NAME) - 1 &&
+           ZEND_U_EQUAL(utype, lcname, lcname_len, ZEND_CALL_STATIC_FUNC_NAME, 
sizeof(ZEND_CALL_STATIC_FUNC_NAME)-1) && fptr->common.num_args != 2) {
+               zend_error(error_type, "Method %v::%s() must take exactly 2 
arguments", ce->name, ZEND_CALL_STATIC_FUNC_NAME);
        } else if (lcname_len == sizeof(ZEND_TOSTRING_FUNC_NAME) - 1 &&
            ZEND_U_EQUAL(utype, lcname, lcname_len, ZEND_TOSTRING_FUNC_NAME, 
sizeof(ZEND_TOSTRING_FUNC_NAME)-1) && fptr->common.num_args != 0) {
                zend_error(error_type, "Method %v::%s() cannot take arguments", 
ce->name, ZEND_TOSTRING_FUNC_NAME);
@@ -2043,7 +2046,7 @@ ZEND_API int zend_register_functions(zen
        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, *__call_static 
= NULL, *__tostring = NULL;
        char *lowercase_name;
        int fname_len;
        zstr lc_class_name = NULL_ZSTR;
@@ -2186,6 +2189,8 @@ ZEND_API int zend_register_functions(zen
                                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_CALL_STATIC_FUNC_NAME)-1) && !memcmp(lowercase_name, 
ZEND_CALL_STATIC_FUNC_NAME, sizeof(ZEND_CALL_STATIC_FUNC_NAME))) {
+                               __call_static = 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))) {
@@ -2226,6 +2231,7 @@ ZEND_API int zend_register_functions(zen
                scope->destructor = dtor;
                scope->clone = clone;
                scope->__call = __call;
+               scope->__call_static = __call_static;
                scope->__tostring = __tostring;
                scope->__get = __get;
                scope->__set = __set;
@@ -2258,6 +2264,12 @@ ZEND_API int zend_register_functions(zen
                        }
                        __call->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC;
                }
+               if (__call_static) {
+                       if (!(__call_static->common.fn_flags & 
ZEND_ACC_STATIC)) {
+                               zend_error(error_type, "Method %v::%v() must be 
static", scope->name, __call->common.function_name);
+                       }
+                       __call->common.fn_flags |= ZEND_ACC_STATIC;
+               }
                if (__tostring) {
                        if (__tostring->common.fn_flags & ZEND_ACC_STATIC) {
                                zend_error(error_type, "Method %v::%v() cannot 
be static", scope->name, __tostring->common.function_name);
@@ -2656,6 +2668,10 @@ static int zend_is_callable_check_func(i
                        retval = (*ce_ptr)->__call != NULL;
                        *fptr_ptr = (*ce_ptr)->__call;
                }
+               if (!*zobj_ptr_ptr && *ce_ptr && (*ce_ptr)->__call_static) {
+                       retval = 1;
+                       *fptr_ptr = (*ce_ptr)->__call_static;
+               }
        } else {
                *fptr_ptr = fptr;
                if (*ce_ptr) {
Index: Zend/zend_API.h
===================================================================
RCS file: /repository/ZendEngine2/zend_API.h,v
retrieving revision 1.289
diff -u -p -r1.289 zend_API.h
--- Zend/zend_API.h     11 Jul 2007 15:17:41 -0000      1.289
+++ Zend/zend_API.h     14 Jul 2007 00:42:05 -0000
@@ -148,6 +148,7 @@ typedef struct _zend_function_entry {
                class_container.unserialize = NULL;                             
                \
                class_container.create_object = NULL;                           
        \
                class_container.interface_gets_implemented = NULL;              
\
+               class_container.get_static_method = NULL;                       
\
                class_container.__call = handle_fcall;                          
        \
                class_container.__tostring = NULL;                              
                \
                class_container.__get = handle_propget;                         
        \
Index: Zend/zend_compile.c
===================================================================
RCS file: /repository/ZendEngine2/zend_compile.c,v
retrieving revision 1.755
diff -u -p -r1.755 zend_compile.c
--- Zend/zend_compile.c 13 Jul 2007 08:19:51 -0000      1.755
+++ Zend/zend_compile.c 14 Jul 2007 00:42:05 -0000
@@ -1222,6 +1222,8 @@ void zend_do_begin_function_declaration(
                                CG(active_class_entry)->clone = (zend_function 
*) CG(active_op_array);
                        } else if ((lcname_len == 
sizeof(ZEND_CALL_FUNC_NAME)-1) && 
(ZEND_U_EQUAL(Z_TYPE(function_name->u.constant), lcname, lcname_len, 
ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)-1))) {
                                CG(active_class_entry)->__call = (zend_function 
*) CG(active_op_array);
+                       } else if ((lcname_len == 
sizeof(ZEND_CALL_STATIC_FUNC_NAME)-1) && 
(ZEND_U_EQUAL(Z_TYPE(function_name->u.constant), lcname, lcname_len, 
ZEND_CALL_STATIC_FUNC_NAME, sizeof(ZEND_CALL_STATIC_FUNC_NAME)-1))) {
+                               CG(active_class_entry)->__call_static = 
(zend_function *) CG(active_op_array);
                        } else if ((lcname_len == sizeof(ZEND_GET_FUNC_NAME)-1) 
&& (ZEND_U_EQUAL(Z_TYPE(function_name->u.constant), lcname, lcname_len, 
ZEND_GET_FUNC_NAME, sizeof(ZEND_GET_FUNC_NAME)-1))) {
                                CG(active_class_entry)->__get = (zend_function 
*) CG(active_op_array);
                        } else if ((lcname_len == sizeof(ZEND_SET_FUNC_NAME)-1) 
&& (ZEND_U_EQUAL(Z_TYPE(function_name->u.constant), lcname, lcname_len, 
ZEND_SET_FUNC_NAME, sizeof(ZEND_SET_FUNC_NAME)-1))) {
@@ -2185,6 +2187,9 @@ static void do_inherit_parent_constructo
        if (!ce->__call) {
                ce->__call = ce->parent->__call;
        }
+       if (!ce->__call_static) {
+               ce->__call_static = ce->parent->__call_static;
+       }
        if (!ce->__tostring) {
                ce->__tostring = ce->parent->__tostring;
        }
@@ -4670,11 +4675,13 @@ ZEND_API void zend_initialize_class_data
                ce->__unset = NULL;
                ce->__isset = NULL;
                ce->__call = NULL;
+               ce->__call_static = NULL;
                ce->__tostring = NULL;
                ce->create_object = NULL;
                ce->get_iterator = NULL;
                ce->iterator_funcs.funcs = NULL;
                ce->interface_gets_implemented = NULL;
+               ce->get_static_method = NULL;
                ce->parent = NULL;
                ce->num_interfaces = 0;
                ce->interfaces = NULL;
Index: Zend/zend_compile.h
===================================================================
RCS file: /repository/ZendEngine2/zend_compile.h,v
retrieving revision 1.360
diff -u -p -r1.360 zend_compile.h
--- Zend/zend_compile.h 12 Jul 2007 09:23:48 -0000      1.360
+++ Zend/zend_compile.h 14 Jul 2007 00:42:05 -0000
@@ -742,6 +742,7 @@ END_EXTERN_C()
 #define ZEND_UNSET_FUNC_NAME        "__unset"
 #define ZEND_ISSET_FUNC_NAME        "__isset"
 #define ZEND_CALL_FUNC_NAME         "__call"
+#define ZEND_CALL_STATIC_FUNC_NAME  "__call_static"
 #define ZEND_TOSTRING_FUNC_NAME     "__tostring"
 #define ZEND_AUTOLOAD_FUNC_NAME     "__autoload"
 
Index: Zend/zend_execute_API.c
===================================================================
RCS file: /repository/ZendEngine2/zend_execute_API.c,v
retrieving revision 1.407
diff -u -p -r1.407 zend_execute_API.c
--- Zend/zend_execute_API.c     12 Jul 2007 09:23:48 -0000      1.407
+++ Zend/zend_execute_API.c     14 Jul 2007 00:42:05 -0000
@@ -881,8 +881,11 @@ int zend_call_function(zend_fcall_info *
                        unsigned int lcname_len;
                        zstr lcname = 
zend_u_str_case_fold(Z_TYPE_P(fci->function_name), fname, fname_len, 1, 
&lcname_len);
 
-                       EX(function_state).function =
-                               zend_std_get_static_method(calling_scope, 
lcname, lcname_len TSRMLS_CC);
+                       if (calling_scope->get_static_method) {
+                               EX(function_state).function = 
calling_scope->get_static_method(calling_scope, lcname, lcname_len TSRMLS_CC);
+                       } else {
+                               EX(function_state).function = 
zend_std_get_static_method(calling_scope, lcname, lcname_len TSRMLS_CC);
+                       }
                        efree(lcname.v);
                        if (check_scope_or_static && EX(function_state).function
                        && !(EX(function_state).function->common.fn_flags & 
ZEND_ACC_STATIC)
Index: Zend/zend_object_handlers.c
===================================================================
RCS file: /repository/ZendEngine2/zend_object_handlers.c,v
retrieving revision 1.186
diff -u -p -r1.186 zend_object_handlers.c
--- Zend/zend_object_handlers.c 12 Jul 2007 10:32:26 -0000      1.186
+++ Zend/zend_object_handlers.c 14 Jul 2007 00:42:05 -0000
@@ -858,7 +858,51 @@ static union _zend_function *zend_std_ge
 }
 /* }}} */
 
-/* This is not (yet?) in the API, but it belongs in the built-in objects 
callbacks */
+ZEND_API void zend_std_call_static_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_CALL_STATIC_FUNC_NAME);
+               RETURN_FALSE;
+       }
+
+       ALLOC_ZVAL(method_name_ptr);
+       INIT_PZVAL(method_name_ptr);
+       ZVAL_TEXT(method_name_ptr, func->function_name, 0); /* no dup - it's a 
copy */
+
+       /* __call_static handler is called with two arguments:
+          method name
+          array of method parameters
+       */
+       zend_call_method_with_2_params(NULL, ce, &ce->__call_static, 
ZEND_CALL_STATIC_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);
+}
+/* }}} */
+
+
 ZEND_API zend_function *zend_std_get_static_method(zend_class_entry *ce, zstr 
function_name_strval, int function_name_strlen TSRMLS_DC) /* {{{ */
 {
        zend_function *fbc;
@@ -866,12 +910,32 @@ ZEND_API zend_function *zend_std_get_sta
        zend_uchar type = UG(unicode)?IS_UNICODE:IS_STRING;
 
        if (zend_u_hash_find(&ce->function_table, type, function_name_strval, 
function_name_strlen+1, (void **) &fbc)==FAILURE) {
-               zstr class_name = ce->name;
+               if (ce->__call_static) {
+                       zend_internal_function *call_static_user_call = 
emalloc(sizeof(zend_internal_function));
+                       call_static_user_call->type = ZEND_INTERNAL_FUNCTION;
+                       call_static_user_call->module = ce->module;
+                       call_static_user_call->handler = 
zend_std_call_static_user_call;
+                       call_static_user_call->arg_info = NULL;
+                       call_static_user_call->num_args = 0;
+                       call_static_user_call->scope = ce;
+                       call_static_user_call->fn_flags = ZEND_ACC_STATIC | 
ZEND_ACC_PUBLIC;
+                       if (UG(unicode)) {
+                               call_static_user_call->function_name.u = 
eustrndup(function_name_strval.u, function_name_strlen);
+                       } else {
+                               call_static_user_call->function_name.s = 
estrndup(function_name_strval.s, function_name_strlen);
+                       }
+                       call_static_user_call->pass_rest_by_reference = 0;
+                       call_static_user_call->return_reference = 
ZEND_RETURN_VALUE;
 
-               if (!class_name.v) {
-                       class_name.u = EMPTY_STR;
+                       return (zend_function *)call_static_user_call;
+               } else {
+                       zstr class_name = ce->name;
+
+                       if (!class_name.v) {
+                               class_name.u = EMPTY_STR;
+                       }
+                       zend_error(E_ERROR, "Call to undefined method 
%R::%R()", type, class_name, type, function_name_strval);
                }
-               zend_error(E_ERROR, "Call to undefined method %R::%R()", type, 
class_name, type, function_name_strval);
        }
 #if MBO_0
        /* right now this function is used for non static method lookup too */
Index: Zend/zend_vm_def.h
===================================================================
RCS file: /repository/ZendEngine2/zend_vm_def.h,v
retrieving revision 1.171
diff -u -p -r1.171 zend_vm_def.h
--- Zend/zend_vm_def.h  12 Jul 2007 09:23:48 -0000      1.171
+++ Zend/zend_vm_def.h  14 Jul 2007 00:42:05 -0000
@@ -1848,7 +1848,11 @@ ZEND_VM_HANDLER(113, ZEND_INIT_STATIC_ME
                        function_name_strval = 
zend_u_str_case_fold(Z_TYPE_P(function_name), Z_UNIVAL_P(function_name), 
Z_UNILEN_P(function_name), 1, &function_name_strlen);
                }
 
-               EX(fbc) = zend_std_get_static_method(ce, function_name_strval, 
function_name_strlen TSRMLS_CC);
+               if (ce->get_static_method) {
+                       EX(fbc) = ce->get_static_method(ce, 
function_name_strval, function_name_strlen TSRMLS_CC);
+               } else {
+                       EX(fbc) = zend_std_get_static_method(ce, 
function_name_strval, function_name_strlen TSRMLS_CC);
+               }
 
                if (!is_const) {
                        efree(function_name_strval.v);

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to