Index: Zend/tests/closure_method_001.phpt =================================================================== --- Zend/tests/closure_method_001.phpt (revision 0) +++ Zend/tests/closure_method_001.phpt (revision 0) @@ -0,0 +1,34 @@ +--TEST-- +Closure as Method 001: Basic test +--FILE-- +cl = function () { + return 7; + }; + } +} + +class B { + public static $cl; + + public static function init() { + self::$cl = static function () { + return 8; + }; + } +} +B::init(); + + +$a = new A(); +echo $a->cl(),"\n"; +echo B::cl(),"\n"; +echo "Done."; +--EXPECTF-- +7 +8 +Done. Index: Zend/tests/closure_method_002.phpt =================================================================== --- Zend/tests/closure_method_002.phpt (revision 0) +++ Zend/tests/closure_method_002.phpt (revision 0) @@ -0,0 +1,67 @@ +--TEST-- +Closure as Method 002: Preference relative to __call(Static) and existing method +--FILE-- +Cl = function () { + return 7; + }; + $this->Cl2 = $this->Cl; + /* dynamic property: */ + $this->Cl7 = function () { + return 11; + }; + } + + public static function init() { + self::$Cl4 = static function () { + return 10; + }; + self::$Cl5 = self::$Cl4; + } + + public function cl() { + return 8; + } + public static function cl4() { + return 9; + } + public function __call($name, $arguments) { + return -1; + } + public static function __callStatic($name, $arguments) { + return -2; + } + + /* should never be called */ + public function __get($name) { + return function () { die("__get called (should not happen)"); }; + } + +} +A::init(); + +$a = new A(); +echo $a->cl(),"\n"; +echo $a->Cl2(),"\n"; +echo $a->cl3(),"\n"; +echo A::cl4(),"\n"; +echo A::Cl5(),"\n"; +echo A::cl6(),"\n"; +echo $a->Cl7(),"\n"; +echo "Done."; +--EXPECTF-- +8 +7 +-1 +9 +10 +-2 +11 +Done. Index: Zend/tests/closure_method_003.phpt =================================================================== --- Zend/tests/closure_method_003.phpt (revision 0) +++ Zend/tests/closure_method_003.phpt (revision 0) @@ -0,0 +1,48 @@ +--TEST-- +Closure as Method 003: Staticness of call, prop and closure (1) +--FILE-- +setDiff( + function () { return 9; } + ); + } +} + +$a = new A; +$b = new B($a); + +echo $a->clStatic(), "\n"; +echo $a->clSame(), "\n"; +echo $a->clDiff(), "\n"; + +echo "Done."; +--EXPECTF-- +7 +8 + +Warning: Closure called as method but bound object differs from containing object in %s on line %d +9 +Done. Index: Zend/tests/closure_method_006.phpt =================================================================== --- Zend/tests/closure_method_006.phpt (revision 0) +++ Zend/tests/closure_method_006.phpt (revision 0) @@ -0,0 +1,49 @@ +--TEST-- +Closure as Method 006: Staticness of call, prop and closure (4) +--FILE-- +clStatic = static function() { + return 7; + }; + $this->clSame = function () { return 8; }; + } + public function setDiff(Closure $cl) { + $this->clDiff = $cl; + } +} + +class B { + public function __construct(A $a) { + $a->setDiff( + function () { return 9; } + ); + } +} + +$a = new A; +$b = new B($a); + +echo $a->clStatic(), "\n"; +echo $a->clSame(), "\n"; +echo $a->clDiff(), "\n"; + +echo "Done."; +--EXPECTF-- +7 +8 + +Warning: Closure called as method but bound object differs from containing object in %s on line %d +9 +Done. Index: Zend/tests/closure_method_007.phpt =================================================================== --- Zend/tests/closure_method_007.phpt (revision 0) +++ Zend/tests/closure_method_007.phpt (revision 0) @@ -0,0 +1,20 @@ +--TEST-- +Closure as Method 007: Staticness of call, prop and closure (5) +--FILE-- +cl = function () { return 7; }; + +echo $a->cl(), "\n"; +echo "Done."; +--EXPECTF-- +Fatal error: Non-static closure called as method but not bound to any object in %s on line %d Index: Zend/tests/closure_method_008.phpt =================================================================== --- Zend/tests/closure_method_008.phpt (revision 0) +++ Zend/tests/closure_method_008.phpt (revision 0) @@ -0,0 +1,19 @@ +--TEST-- +Closure as Method 008: Staticness of call, prop and closure (6) +--FILE-- +cl = static function () { return 7; }; + } +} + +$a = new A; +A::cl(); +echo "Done."; +--EXPECTF-- +Fatal error: Attempt to find a closure in property A::$cl for static method call failed because the property is non-static in %s on line %d Index: Zend/tests/closure_method_009.phpt =================================================================== --- Zend/tests/closure_method_009.phpt (revision 0) +++ Zend/tests/closure_method_009.phpt (revision 0) @@ -0,0 +1,17 @@ +--TEST-- +Closure as Method 009: Lack of property visibility (1), private instance property +--FILE-- +cl = function () { return 7; }; + } +} + +$a = new A; +$a->cl(); +echo "Done."; +--EXPECTF-- +Fatal error: Cannot access private property A::$cl in %s on line %d Index: Zend/tests/closure_method_010.phpt =================================================================== --- Zend/tests/closure_method_010.phpt (revision 0) +++ Zend/tests/closure_method_010.phpt (revision 0) @@ -0,0 +1,18 @@ +--TEST-- +Closure as Method 010: Lack of property visibility (2), shadowed private instance property +--FILE-- +cl = function () { return 7; }; + } +} +class B extends A {} + +$b = new B; +$b->cl(); +echo "Done."; +--EXPECTF-- +Fatal error: Call to undefined method B::cl() in %s on line %d Index: Zend/tests/closure_method_011.phpt =================================================================== --- Zend/tests/closure_method_011.phpt (revision 0) +++ Zend/tests/closure_method_011.phpt (revision 0) @@ -0,0 +1,17 @@ +--TEST-- +Closure as Method 011: Lack of property visibility (3), protected instance property +--FILE-- +cl = function () { return 7; }; + } +} + +$a = new A; +$a->cl(); +echo "Done."; +--EXPECTF-- +Fatal error: Cannot access protected property A::$cl in %s on line %d Index: Zend/tests/closure_method_012.phpt =================================================================== --- Zend/tests/closure_method_012.phpt (revision 0) +++ Zend/tests/closure_method_012.phpt (revision 0) @@ -0,0 +1,17 @@ +--TEST-- +Closure as Method 012: Lack of property visibility (4), private static property +--FILE-- +cl(); +echo "Done."; +--EXPECTF-- +Fatal error: Cannot access private property A::$cl in %s on line %d Index: Zend/tests/closure_method_014.phpt =================================================================== --- Zend/tests/closure_method_014.phpt (revision 0) +++ Zend/tests/closure_method_014.phpt (revision 0) @@ -0,0 +1,24 @@ +--TEST-- +Closure as Method 014: Visibility of protected property +--FILE-- +cl = function () { return 7; }; + } +} +class B extends A { + public function call() { + return $this->cl(); + } +} + +$b = new B; +$b->setClosure(); +echo $b->call(), "\n"; +echo "Done."; +--EXPECTF-- +7 +Done. Index: Zend/zend_object_handlers.c =================================================================== --- Zend/zend_object_handlers.c (revision 302070) +++ Zend/zend_object_handlers.c (working copy) @@ -296,7 +296,7 @@ } else if (property_info) { if (UNEXPECTED(denied_access != 0)) { /* Information was available, but we were denied access. Error out. */ - if (!silent) { + if (!silent || silent == 2) { zend_error_noreturn(E_ERROR, "Cannot access %s property %s::$%s", zend_visibility_string(property_info->flags), ce->name, Z_STRVAL_P(member)); } return NULL; @@ -321,7 +321,8 @@ ZEND_API struct _zend_property_info *zend_get_property_info(zend_class_entry *ce, zval *member, int silent TSRMLS_DC) /* {{{ */ { - return zend_get_property_info_quick(ce, member, silent, NULL TSRMLS_CC); + /* !!silent just in case someone used 2 before it was given special meaning in _quick */ + return zend_get_property_info_quick(ce, member, !!silent, NULL TSRMLS_CC); } /* }}} */ @@ -952,6 +953,106 @@ } /* }}} */ +static zend_function *zend_std_get_closure_as_method(zval **object_ptr, zend_class_entry *ce, char *method_name, int method_len TSRMLS_DC) /* {{{ */ +{ + zval *property, **property_ptr; + zend_property_info *property_info; + + if (ce == NULL) { + ce = Z_OBJCE_PP(object_ptr); + } + + if (object_ptr != NULL) { /* non-static call */ + zval z_method_name = zval_used_for_init; + ZVAL_STRINGL(&z_method_name, method_name, method_len, 0); + if ((property_info = zend_get_property_info_quick(ce, &z_method_name, 2, NULL TSRMLS_CC)) == NULL) { + return NULL; + } else if ((property_info == &EG(std_property_info))) { + /* look for dynamic property */ + if ((Z_OBJ_P(*object_ptr)->properties != NULL) && + zend_hash_find(Z_OBJ_P(*object_ptr)->properties, method_name, method_len +1, (void **) &property_ptr) == SUCCESS) { + property = *property_ptr; + } else { + return NULL; + } + } else if (!(property_info->flags & ZEND_ACC_STATIC)) { + /* optimization opportunity: will call zend_get_property_info again, but not much we can + * do without more profound changes */ + property = zend_std_read_property(*object_ptr, &z_method_name, BP_VAR_R, NULL TSRMLS_CC); + } else { + /* an E_STRICT is typically sent when accessing a static property non-statically, + * but in this case allow because it's interesting to be able to replace a + * closure at class level (for replacing a method for all instances) */ + property_ptr = zend_std_get_static_property(ce, method_name, method_len, 0, NULL TSRMLS_CC); + property = *property_ptr; + } + } else { /* static call */ + + if (zend_hash_find(&ce->properties_info, method_name, method_len + 1, (void **) &property_info) == FAILURE) { + return NULL; + } + if (!(property_info->flags & ZEND_ACC_STATIC)) { + /* static call, but instance property */ + zend_error(E_ERROR, "Attempt to find a closure in property %s::$%s for static method call failed " + "because the property is non-static", ce->name, method_name); + } + property_ptr = zend_std_get_static_property(ce, method_name, method_len, 0, NULL TSRMLS_CC); + property = *property_ptr; + } + + if (Z_TYPE_P(property) == IS_OBJECT && Z_OBJ_HANDLER_P(property, get_closure) != NULL) { + zend_class_entry *ce; /* ignored */ + union _zend_function *closure_func; + zval *bound_obj; + + if (Z_OBJ_HANDLER_P(property, get_closure)(property, &ce, &closure_func, &bound_obj TSRMLS_CC) == SUCCESS) { + if (object_ptr != NULL) { /* non-static call */ + if (closure_func->common.fn_flags & ZEND_ACC_STATIC) { + /* allow non-static call with static closure */ + } else if (bound_obj == NULL) { + /* can't recover because we can't set *object_ptr to NULL + * without returning a static method and we decided we + * do not want automatic binding */ + zend_error(E_ERROR, "Non-static closure called as method but not bound to any object"); + } else if ((Z_OBJ_HANDLE_P(bound_obj) != Z_OBJ_HANDLE_PP(object_ptr)) || + (Z_OBJ_HT_P(bound_obj) != Z_OBJ_HT_PP(object_ptr))) { + zend_error(E_WARNING, "Closure called as method but bound object differs from containing object"); + *object_ptr = bound_obj; /* change $this pointer */ + } + } else if (!(closure_func->common.fn_flags & ZEND_ACC_STATIC)) { + /* static call, non-static closure */ + /* Currently, zend_do_fcall raises an error if a non-static closure + * is being called statically, but only if there's a defined scope. + * Let's be kind and allow non-static closures if there's no scope + * (and consequently no bound object) to get away with an E_STRICT */ + if (closure_func->common.scope == NULL) { + zend_error(E_STRICT, "Non-static closure should not be called as a static method"); + } else { + /* We're using a dummy scope in case a non-scoped closure was rebound + * to an object. This would give a potential confusing error message, + * so raise error here */ + /* FIXME: note the comment above does not apply if the originally + * committed behaviour of always taking the scope of the bound object + * as the new scope is in place. But we're still better dying here + * than in zend_do_fcall */ + zend_error(E_ERROR, "Non-static bound closure should not be called as a static method"); + } + } + + return closure_func; + } else { + zend_error(E_ERROR, "The property %s::$%s is not a closure and cannot be used as a method", + ce->name, method_name); + } + } else { + zend_error(E_ERROR, "The property %s::$%s is not a closure and cannot be used as a method", + ce->name, method_name); + } + + return NULL; +} +/* }}} */ + static union _zend_function *zend_std_get_method(zval **object_ptr, char *method_name, int method_len, const zend_literal *key TSRMLS_DC) /* {{{ */ { zend_function *fbc; @@ -972,9 +1073,16 @@ } if (UNEXPECTED(zend_hash_quick_find(&zobj->ce->function_table, lc_method_name, method_len+1, hash_value, (void **)&fbc) == FAILURE)) { + union _zend_function *closure_func; + if (UNEXPECTED(!key)) { free_alloca(lc_method_name, use_heap); } + + closure_func = zend_std_get_closure_as_method(object_ptr, NULL, method_name, method_len TSRMLS_CC); + if (closure_func != NULL) { + return closure_func; + } if (zobj->ce->__call) { return zend_get_user_call_function(zobj->ce, method_name, method_len); } else { @@ -1128,10 +1236,16 @@ } if (EXPECTED(!fbc) && UNEXPECTED(zend_hash_quick_find(&ce->function_table, lc_function_name, function_name_strlen+1, hash_value, (void **) &fbc)==FAILURE)) { + zend_function *closure_func; if (UNEXPECTED(!key)) { free_alloca(lc_function_name, use_heap); } + closure_func = zend_std_get_closure_as_method(NULL, ce, function_name_strval, function_name_strlen TSRMLS_CC); + if (closure_func != NULL) { + return closure_func; + } + if (ce->__callstatic) { return zend_get_user_callstatic_function(ce, function_name_strval, function_name_strlen); } else if (ce->__call &&