I have finished a patch that implements a working version of late static binding.
I used the notes from the Paris PDM as my guidelines for implementation. (http://www.php.net/~derick/meeting-notes.html#late-static-binding-using-this-without-or-perhaps-with-a-different-name) As requested in the notes I reused 'static::'. I also wrote a few tests not only to test the new functionality but also to make sure 'static' can't be inherited or extended. I also added a new function get_caller_class() which returns the name of the class that static:: would represent. (borrowing from PDM notes) In php5.* the following script outputs "A::static2": <?php class A { static function staticA() { self::static2(); } static function static2() { echo "A::static2\n"; } } class B extends A { static function static2() { echo "B::static2\n"; } } B::staticA(); ?> This has somewhat recently been highlighted by different developers to be somewhat problematic behavior in creating user friendly APIs. If you want to see a possible use for it you need look no further than the example ZActiveRecord API that was used in their webcast with php|arch. (http://blog.joshuaeichorn.com/archives/2006/01/09/zactiverecord-cant-work/) Currently the code laid out there is impossible short of some ugly use of debug_backtrace() and file parsing :/. This patch of course would allow that kind of code too exist. In a small example based on the one I gave earlier you could change the code too the following and have it print "B::static2": <?php class A { static function staticA() { static::static2(); } static function static2() { echo "A::static2\n"; } } class B extends A { static function static2() { echo "B::static2\n"; } } B::staticA(); ?> As far as current userland code impact, there is very little as far as I can tell. No keywords have been added, just another use for an existing one. No changes were made to self:: or parent:: so the change should be pretty transparent. The only thing that I see remotely causing any issues would be the new function (get_caller_class().) I added that just to complete the set so to speak. ---------- Mike Lively (ds- on irc.efnet.org) "Our Schwartz is bigger than yours"
diff -Naur php-src/tests/classes/caller_001.phpt php-src-new/tests/classes/caller_001.phpt --- php-src/tests/classes/caller_001.phpt 1969-12-31 16:00:00.000000000 -0800 +++ php-src-new/tests/classes/caller_001.phpt 2006-02-23 09:22:14.000000000 -0800 @@ -0,0 +1,61 @@ +--TEST-- +ZE2 Late Static Binding in a static function +--FILE-- +<?php + +class TestClass { + protected static $staticVar = 'TestClassStatic'; + const CLASS_CONST = 'TestClassConst'; + + protected static function staticFunction() { + return 'TestClassFunction'; + } + + public static function testStaticVar() { + return static::$staticVar; + } + + public static function testClassConst() { + return static::CLASS_CONST; + } + + public static function testStaticFunction() { + return static::staticFunction(); + } +} + +class ChildClass1 extends TestClass { + protected static $staticVar = 'ChildClassStatic'; + const CLASS_CONST = 'ChildClassConst'; + + protected static function staticFunction() { + return 'ChildClassFunction'; + } +} + +class ChildClass2 extends TestClass {} + +echo TestClass::testStaticVar() . "\n"; +echo TestClass::testClassConst() . "\n"; +echo TestClass::testStaticFunction() . "\n"; + +echo ChildClass1::testStaticVar() . "\n"; +echo ChildClass1::testClassConst() . "\n"; +echo ChildClass1::testStaticFunction() . "\n"; + +echo ChildClass2::testStaticVar() . "\n"; +echo ChildClass2::testClassConst() . "\n"; +echo ChildClass2::testStaticFunction() . "\n"; +?> +==DONE== +--EXPECTF-- +TestClassStatic +TestClassConst +TestClassFunction +ChildClassStatic +ChildClassConst +ChildClassFunction +TestClassStatic +TestClassConst +TestClassFunction +==DONE== diff -Naur php-src/tests/classes/caller_002.phpt php-src-new/tests/classes/caller_002.phpt --- php-src/tests/classes/caller_002.phpt 1969-12-31 16:00:00.000000000 -0800 +++ php-src-new/tests/classes/caller_002.phpt 2006-02-23 09:22:14.000000000 -0800 @@ -0,0 +1,66 @@ +--TEST-- +ZE2 Late Static Binding in an instance function +--FILE-- +<?php + +class TestClass { + protected static $staticVar = 'TestClassStatic'; + const CLASS_CONST = 'TestClassConst'; + + protected static function staticFunction() { + return 'TestClassFunction'; + } + + public function testStaticVar() { + return static::$staticVar; + } + + public function testClassConst() { + return static::CLASS_CONST; + } + + public function testStaticFunction() { + return static::staticFunction(); + } +} + +class ChildClass1 extends TestClass { + protected static $staticVar = 'ChildClassStatic'; + const CLASS_CONST = 'ChildClassConst'; + + protected static function staticFunction() { + return 'ChildClassFunction'; + } +} + +class ChildClass2 extends TestClass {} + +$testClass = new TestClass(); +$childClass1 = new ChildClass1(); +$childClass2 = new ChildClass2(); + + +echo $testClass->testStaticVar() . "\n"; +echo $testClass->testClassConst() . "\n"; +echo $testClass->testStaticFunction() . "\n"; + +echo $childClass1->testStaticVar() . "\n"; +echo $childClass1->testClassConst() . "\n"; +echo $childClass1->testStaticFunction() . "\n"; + +echo $childClass2->testStaticVar() . "\n"; +echo $childClass2->testClassConst() . "\n"; +echo $childClass2->testStaticFunction() . "\n"; +?> +==DONE== +--EXPECTF-- +TestClassStatic +TestClassConst +TestClassFunction +ChildClassStatic +ChildClassConst +ChildClassFunction +TestClassStatic +TestClassConst +TestClassFunction +==DONE== diff -Naur php-src/tests/classes/caller_003.phpt php-src-new/tests/classes/caller_003.phpt --- php-src/tests/classes/caller_003.phpt 1969-12-31 16:00:00.000000000 -0800 +++ php-src-new/tests/classes/caller_003.phpt 2006-02-23 09:22:14.000000000 -0800 @@ -0,0 +1,24 @@ +--TEST-- +ZE2 creating a new class with 'static' +--FILE-- +<?php + +class TestClass { + public static function createInstance() { + return new static(); + } +} + +class ChildClass extends TestClass {} + +$testClass = TestClass::createInstance(); +$childClass = ChildClass::createInstance(); + +echo get_class($testClass) . "\n"; +echo get_class($childClass) . "\n"; +?> +==DONE== +--EXPECTF-- +TestClass +ChildClass +==DONE== diff -Naur php-src/tests/classes/caller_004.phpt php-src-new/tests/classes/caller_004.phpt --- php-src/tests/classes/caller_004.phpt 1969-12-31 16:00:00.000000000 -0800 +++ php-src-new/tests/classes/caller_004.phpt 2006-02-23 09:22:14.000000000 -0800 @@ -0,0 +1,21 @@ +--TEST-- +ZE2 testing get_called_class() +--FILE-- +<?php + +class TestClass { + public static function getClassName() { + return get_caller_class(); + } +} + +class ChildClass extends TestClass {} + +echo TestClass::getClassName() . "\n"; +echo ChildClass::getClassName() . "\n"; +?> +==DONE== +--EXPECTF-- +TestClass +ChildClass +==DONE== diff -Naur php-src/tests/classes/caller_005.phpt php-src-new/tests/classes/caller_005.phpt --- php-src/tests/classes/caller_005.phpt 1969-12-31 16:00:00.000000000 -0800 +++ php-src-new/tests/classes/caller_005.phpt 2006-02-23 09:22:14.000000000 -0800 @@ -0,0 +1,51 @@ +--TEST-- +ZE2 stacking static callers +--FILE-- +<?php + +class TestA { + public static function test() { + echo get_class(new static()) . "\n"; + TestB::test(); + echo get_class(new static()) . "\n"; + TestC::test(); + echo get_class(new static()) . "\n"; + TestBB::test(); + echo get_class(new static()) . "\n"; + } +} + +class TestB { + public static function test() { + echo get_class(new static()) . "\n"; + TestC::test(); + echo get_class(new static()) . "\n"; + } +} + +class TestC { + public static function test() { + echo get_class(new static()) . "\n"; + } +} + +class TestBB extends TestB { +} + +TestA::test(); + +?> +==DONE== +--EXPECTF-- +TestA +TestB +TestC +TestB +TestA +TestC +TestA +TestBB +TestC +TestBB +TestA +==DONE== diff -Naur php-src/tests/classes/caller_006.phpt php-src-new/tests/classes/caller_006.phpt --- php-src/tests/classes/caller_006.phpt 1969-12-31 16:00:00.000000000 -0800 +++ php-src-new/tests/classes/caller_006.phpt 2006-02-23 09:22:14.000000000 -0800 @@ -0,0 +1,12 @@ +--TEST-- +ZE2 ensuring extending 'static' is not allowed +--FILE-- +<?php + +class Foo extends static { +} + +?> +==DONE== +--EXPECTF-- +Fatal error: Cannot use 'static' as class name as it is reserved in %s on line %d diff -Naur php-src/tests/classes/caller_007.phpt php-src-new/tests/classes/caller_007.phpt --- php-src/tests/classes/caller_007.phpt 1969-12-31 16:00:00.000000000 -0800 +++ php-src-new/tests/classes/caller_007.phpt 2006-02-23 09:22:14.000000000 -0800 @@ -0,0 +1,12 @@ +--TEST-- +ZE2 ensuring implementing 'static' is not allowed +--FILE-- +<?php + +class Foo implements static { +} + +?> +==DONE== +--EXPECTF-- +Fatal error: Cannot use 'static' as interface name as it is reserved in %s on line %d diff -Naur php-src/ZendEngine2/zend_builtin_functions.c php-src-new/ZendEngine2/zend_builtin_functions.c --- php-src/ZendEngine2/zend_builtin_functions.c 2006-02-22 02:02:15.000000000 -0800 +++ php-src-new/ZendEngine2/zend_builtin_functions.c 2006-02-23 09:22:14.000000000 -0800 @@ -41,6 +41,7 @@ static ZEND_FUNCTION(define); static ZEND_FUNCTION(defined); static ZEND_FUNCTION(get_class); +static ZEND_FUNCTION(get_caller_class); static ZEND_FUNCTION(get_parent_class); static ZEND_FUNCTION(method_exists); static ZEND_FUNCTION(property_exists); @@ -101,6 +102,7 @@ ZEND_FE(define, NULL) ZEND_FE(defined, NULL) ZEND_FE(get_class, NULL) + ZEND_FE(get_caller_class, NULL) ZEND_FE(get_parent_class, NULL) ZEND_FE(method_exists, NULL) ZEND_FE(property_exists, NULL) @@ -592,6 +594,22 @@ } /* }}} */ +/* {{{ proto string get_caller_class() + Retrieves the class that was used to call a static function */ +ZEND_FUNCTION(get_caller_class) +{ + if (!ZEND_NUM_ARGS()) { + if (EG(active_op_array) && EG(active_op_array)->caller_scope) { + RETURN_TEXTL(EG(active_op_array)->caller_scope->name, + EG(active_op_array)->caller_scope->name_length, 1); + } else { + zend_error(E_ERROR, "get_caller_class() called from outside a class"); + } + } else { + ZEND_WRONG_PARAM_COUNT(); + } +} +/* }}} */ /* {{{ proto string get_parent_class([mixed object]) Retrieves the parent class name for object or class or current scope. */ diff -Naur php-src/ZendEngine2/zend_compile.c php-src-new/ZendEngine2/zend_compile.c --- php-src/ZendEngine2/zend_compile.c 2006-02-22 02:56:52.000000000 -0800 +++ php-src-new/ZendEngine2/zend_compile.c 2006-02-23 09:22:14.000000000 -0800 @@ -1514,6 +1514,7 @@ switch (fetch_type) { case ZEND_FETCH_CLASS_SELF: case ZEND_FETCH_CLASS_PARENT: + case ZEND_FETCH_CLASS_STATIC: SET_UNUSED(opline->op2); opline->extended_value = fetch_type; zval_dtor(&class_name->u.constant); @@ -2835,7 +2836,9 @@ if ((lcname_len == sizeof("self")-1 && ZEND_U_EQUAL(Z_TYPE(class_name->u.constant), lcname, lcname_len, "self", sizeof("self")-1)) || (lcname_len == sizeof("parent")-1 && - ZEND_U_EQUAL(Z_TYPE(class_name->u.constant), lcname, lcname_len, "parent", sizeof("parent")-1))) { + ZEND_U_EQUAL(Z_TYPE(class_name->u.constant), lcname, lcname_len, "parent", sizeof("parent")-1)) || + (lcname_len == sizeof("static")-1 && + ZEND_U_EQUAL(Z_TYPE(class_name->u.constant), lcname, lcname_len, "static", sizeof("static")-1))) { efree(lcname.v); zend_error(E_COMPILE_ERROR, "Cannot use '%R' as class name as it is reserved", Z_TYPE(class_name->u.constant), Z_UNIVAL(class_name->u.constant)); } @@ -2857,6 +2860,9 @@ case ZEND_FETCH_CLASS_PARENT: zend_error(E_COMPILE_ERROR, "Cannot use 'parent' as class name as it is reserved"); break; + case ZEND_FETCH_CLASS_STATIC: + zend_error(E_COMPILE_ERROR, "Cannot use 'static' as class name as it is reserved"); + break; default: break; } @@ -2966,6 +2972,9 @@ case ZEND_FETCH_CLASS_PARENT: zend_error(E_COMPILE_ERROR, "Cannot use 'parent' as interface name as it is reserved"); break; + case ZEND_FETCH_CLASS_STATIC: + zend_error(E_COMPILE_ERROR, "Cannot use 'static' as interface name as it is reserved"); + break; default: if (CG(active_op_array)->last > 0) { opline = &CG(active_op_array)->opcodes[CG(active_op_array)->last-1]; @@ -4282,6 +4291,9 @@ } else if ((class_name_len == sizeof("parent")-1) && ZEND_U_EQUAL(type, class_name, class_name_len, "parent", sizeof("parent")-1)) { return ZEND_FETCH_CLASS_PARENT; + } else if ((class_name_len == sizeof("static")-1) && + ZEND_U_EQUAL(type, class_name, class_name_len, "static", sizeof("static")-1)) { + return ZEND_FETCH_CLASS_STATIC; } else { return ZEND_FETCH_CLASS_DEFAULT; } diff -Naur php-src/ZendEngine2/zend_compile.h php-src-new/ZendEngine2/zend_compile.h --- php-src/ZendEngine2/zend_compile.h 2006-02-21 12:12:41.000000000 -0800 +++ php-src-new/ZendEngine2/zend_compile.h 2006-02-23 09:22:14.000000000 -0800 @@ -175,6 +175,7 @@ zend_uchar type; zstr function_name; zend_class_entry *scope; + zend_class_entry *caller_scope; /* to support late static binding */ zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; @@ -229,6 +230,7 @@ zend_uchar type; zstr function_name; zend_class_entry *scope; + zend_class_entry *caller_scope; /* to support late static binding */ zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; @@ -251,6 +253,7 @@ zend_uchar type; /* never used */ zstr function_name; zend_class_entry *scope; + zend_class_entry *caller_scope; /* to support late static binding */ zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; @@ -602,6 +605,7 @@ #define ZEND_FETCH_CLASS_GLOBAL 4 #define ZEND_FETCH_CLASS_AUTO 5 #define ZEND_FETCH_CLASS_INTERFACE 6 +#define ZEND_FETCH_CLASS_STATIC 7 #define ZEND_FETCH_CLASS_NO_AUTOLOAD 0x80 /* variable parsing type (compile-time) */ diff -Naur php-src/ZendEngine2/zend_execute_API.c php-src-new/ZendEngine2/zend_execute_API.c --- php-src/ZendEngine2/zend_execute_API.c 2006-02-22 00:54:02.000000000 -0800 +++ php-src-new/ZendEngine2/zend_execute_API.c 2006-02-23 09:22:14.000000000 -0800 @@ -704,6 +704,16 @@ ce = &(EG(active_op_array)->scope->parent); found = (*ce != NULL?SUCCESS:FAILURE); fci->object_pp = EG(This)?&EG(This):NULL; + } else if (EG(active_op_array) && + Z_UNILEN_PP(fci->object_pp) == sizeof("static")-1 && + ZEND_U_EQUAL(Z_TYPE_PP(fci->object_pp), Z_UNIVAL_PP(fci->object_pp), Z_UNILEN_PP(fci->object_pp), "static", sizeof("static")-1)) { + + if (!EG(active_op_array)->caller_scope) { + zend_error(E_ERROR, "Cannot access static:: when no called scope is active"); + } + ce = &(EG(active_op_array)->caller_scope); + found = (*ce != NULL?SUCCESS:FAILURE); + fci->object_pp = EG(This)?&EG(This):NULL; } else { zend_class_entry *scope; scope = EG(active_op_array) ? EG(active_op_array)->scope : NULL; @@ -771,6 +781,9 @@ } else if (calling_scope && clen == sizeof("parent") - 1 && ZEND_U_EQUAL(Z_TYPE_P(fci->function_name), lcname, clen, "parent", sizeof("parent")-1)) { ce_child = EG(active_op_array) && EG(active_op_array)->scope ? EG(scope)->parent : NULL; + } else if (calling_scope && clen == sizeof("static") - 1 && + ZEND_U_EQUAL(Z_TYPE_P(fci->function_name), lcname, clen, "static", sizeof("static")-1)) { + ce_child = EG(active_op_array) && EG(active_op_array)->caller_scope ? EG(active_op_array)->caller_scope : NULL; } else if (zend_u_lookup_class(Z_TYPE_P(fci->function_name), Z_UNIVAL_P(fci->function_name), clen, &pce TSRMLS_CC) == SUCCESS) { ce_child = *pce; } @@ -1518,6 +1531,11 @@ zend_error(E_ERROR, "Cannot access parent:: when current class scope has no parent"); } return EG(scope)->parent; + case ZEND_FETCH_CLASS_STATIC: + if (!EG(active_op_array) || !EG(active_op_array)->caller_scope) { + zend_error(E_ERROR, "Cannot access static:: when no called scope is active"); + } + return EG(active_op_array)->caller_scope; case ZEND_FETCH_CLASS_AUTO: { fetch_type = zend_get_class_fetch_type(type, class_name, class_name_len); if (fetch_type!=ZEND_FETCH_CLASS_DEFAULT) { diff -Naur php-src/ZendEngine2/zend_language_parser.y php-src-new/ZendEngine2/zend_language_parser.y --- php-src/ZendEngine2/zend_language_parser.y 2006-02-20 11:03:43.000000000 -0800 +++ php-src-new/ZendEngine2/zend_language_parser.y 2006-02-23 09:22:14.000000000 -0800 @@ -642,10 +642,12 @@ fully_qualified_class_name: T_STRING { zend_do_fetch_class(&$$, &$1 TSRMLS_CC); } + | T_STATIC { znode static_string; static_string.op_type = IS_CONST; static_string.u.constant.type = IS_STRING; static_string.u.constant.value.str.val = (char *)estrndup("static", 6); static_string.u.constant.value.str.len = 6; zend_do_fetch_class(&$$, &static_string TSRMLS_CC); } ; class_name_reference: T_STRING { zend_do_fetch_class(&$$, &$1 TSRMLS_CC); } + | T_STATIC { znode static_string; static_string.op_type = IS_CONST; static_string.u.constant.type = IS_STRING; static_string.u.constant.value.str.val = (char *)estrndup("static", 6); static_string.u.constant.value.str.len = 6; zend_do_fetch_class(&$$, &static_string TSRMLS_CC); } | dynamic_class_name_reference { zend_do_end_variable_parse(BP_VAR_R, 0 TSRMLS_CC); zend_do_fetch_class(&$$, &$1 TSRMLS_CC); } ; diff -Naur php-src/ZendEngine2/zend_object_handlers.c php-src-new/ZendEngine2/zend_object_handlers.c --- php-src/ZendEngine2/zend_object_handlers.c 2006-02-21 12:12:41.000000000 -0800 +++ php-src-new/ZendEngine2/zend_object_handlers.c 2006-02-23 09:22:14.000000000 -0800 @@ -756,6 +756,7 @@ call_user_call->arg_info = NULL; call_user_call->num_args = 0; call_user_call->scope = zobj->ce; + call_user_call->caller_scope = zobj->ce; call_user_call->fn_flags = 0; if (UG(unicode)) { call_user_call->function_name.u = eustrndup(method_name.u, method_len); @@ -772,6 +773,7 @@ } /* Check access level */ + fbc->op_array.caller_scope = zobj->ce; if (fbc->op_array.fn_flags & ZEND_ACC_PUBLIC) { /* Ensure that we haven't overridden a private function and end up calling * the overriding public function... @@ -830,6 +832,7 @@ zend_error(E_ERROR, "Cannot call non static method %v::%v() without object", ZEND_FN_SCOPE_NAME(fbc), fbc->common.function_name); } #endif + fbc->common.caller_scope = ce; if (fbc->op_array.fn_flags & ZEND_ACC_PUBLIC) { /* No further checks necessary, most common case */ } else if (fbc->op_array.fn_flags & ZEND_ACC_PRIVATE) { diff -Naur php-src/ZendEngine2/zend_opcode.c php-src-new/ZendEngine2/zend_opcode.c --- php-src/ZendEngine2/zend_opcode.c 2006-02-21 12:12:41.000000000 -0800 +++ php-src-new/ZendEngine2/zend_opcode.c 2006-02-23 09:22:14.000000000 -0800 @@ -86,6 +86,7 @@ op_array->required_num_args = 0; op_array->scope = NULL; + op_array->caller_scope = NULL; op_array->brk_cont_array = NULL; op_array->try_catch_array = NULL;
-- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php