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

Reply via email to