diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index b97e271aa49..4c28c087b61 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1701,6 +1701,28 @@ ZEND_METHOD(ReflectionFunctionAbstract, getClosureScopeClass) } /* }}} */ +/* {{{ Returns the called scope associated to the closure */ +ZEND_METHOD(ReflectionFunctionAbstract, getClosureCalledClass) +{ + reflection_object *intern; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT(); + if (!Z_ISUNDEF(intern->obj)) { + zend_class_entry *called_scope; + zend_function *closure_func; + zend_object *object; + if (Z_OBJ_HANDLER(intern->obj, get_closure) + && Z_OBJ_HANDLER(intern->obj, get_closure)(Z_OBJ(intern->obj), &called_scope, &closure_func, &object, 1) == SUCCESS + && closure_func && (called_scope || closure_func->common.scope)) { + zend_reflection_class_factory(called_scope ? (zend_class_entry *) called_scope : closure_func->common.scope, return_value); + } + } +} +/* }}} */ + /* {{{ Returns an associative array containing the closures lexical scope variables */ ZEND_METHOD(ReflectionFunctionAbstract, getClosureUsedVariables) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 73a71762dba..22223bb0a1c 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -54,6 +54,9 @@ abstract class ReflectionFunctionAbstract implements Reflector /** @tentative-return-type */ public function getClosureScopeClass(): ?ReflectionClass {} + /** @tentative-return-type */ + public function getClosureCalledClass(): ?ReflectionClass {} + public function getClosureUsedVariables(): array {} /** @tentative-return-type */ diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 81ef6b69f15..92f37a67510 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 25b36d66ab7fb88b8d44d51e15e530ebff2e1e2c */ + * Stub hash: a531c9132b4ac3d3196570ae6dda52b8f6a4f488 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -31,6 +31,8 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionFunctionAbstract_getClosureScopeClass, 0, 0, ReflectionClass, 1) ZEND_END_ARG_INFO() +#define arginfo_class_ReflectionFunctionAbstract_getClosureCalledClass arginfo_class_ReflectionFunctionAbstract_getClosureScopeClass + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -615,6 +617,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, isVariadic); ZEND_METHOD(ReflectionFunctionAbstract, isStatic); ZEND_METHOD(ReflectionFunctionAbstract, getClosureThis); ZEND_METHOD(ReflectionFunctionAbstract, getClosureScopeClass); +ZEND_METHOD(ReflectionFunctionAbstract, getClosureCalledClass); ZEND_METHOD(ReflectionFunctionAbstract, getClosureUsedVariables); ZEND_METHOD(ReflectionFunctionAbstract, getDocComment); ZEND_METHOD(ReflectionFunctionAbstract, getEndLine); @@ -861,6 +864,7 @@ static const zend_function_entry class_ReflectionFunctionAbstract_methods[] = { ZEND_ME(ReflectionFunctionAbstract, isStatic, arginfo_class_ReflectionFunctionAbstract_isStatic, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getClosureThis, arginfo_class_ReflectionFunctionAbstract_getClosureThis, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getClosureScopeClass, arginfo_class_ReflectionFunctionAbstract_getClosureScopeClass, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionFunctionAbstract, getClosureCalledClass, arginfo_class_ReflectionFunctionAbstract_getClosureCalledClass, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getClosureUsedVariables, arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getDocComment, arginfo_class_ReflectionFunctionAbstract_getDocComment, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getEndLine, arginfo_class_ReflectionFunctionAbstract_getEndLine, ZEND_ACC_PUBLIC) diff --git a/ext/reflection/tests/ReflectionFunction_getClosureCalledClass.phpt b/ext/reflection/tests/ReflectionFunction_getClosureCalledClass.phpt new file mode 100644 index 00000000000..ab06a6472e0 --- /dev/null +++ b/ext/reflection/tests/ReflectionFunction_getClosureCalledClass.phpt @@ -0,0 +1,108 @@ +--TEST-- +GH-8932 (Provide a way to get the called-scope of closures) +--FILE-- +'.$name, "\n"; + } + + public static function b() { + echo static::class.'::b', "\n"; + } + + + public function c() { + echo static::class.'->c', "\n"; + } + + public function makeClosure() { + return function () { + echo static::class.'::{closure}'."\n"; + }; + } +} + +class B extends A {} + +$c = ['B', 'b']; +$d = \Closure::fromCallable($c); +$r = new \ReflectionFunction($d); +var_dump($r->getClosureCalledClass()); +$d(); + +$c = [new B(), 'c']; +$d = \Closure::fromCallable($c); +$r = new \ReflectionFunction($d); +var_dump($r->getClosureCalledClass()); +$d(); + +$c = ['B', 'd']; +$d = \Closure::fromCallable($c); +$r = new \ReflectionFunction($d); +var_dump($r->getClosureCalledClass()); +$d(); + +$c = [new B(), 'e']; +$d = \Closure::fromCallable($c); +$r = new \ReflectionFunction($d); +var_dump($r->getClosureCalledClass()); +$d(); + +$c = ['A', 'b']; +$d = \Closure::fromCallable($c); +$r = new \ReflectionFunction($d); +var_dump($r->getClosureCalledClass()); +$d(); + +$b = new B(); +$d = $b->makeClosure(); +$r = new \ReflectionFunction($d); +var_dump($r->getClosureCalledClass()); +$d(); + +$d = function () { + echo "{closure}\n"; +}; +$r = new \ReflectionFunction($d); +var_dump($r->getClosureCalledClass()); +$d(); + +?> +--EXPECTF-- +object(ReflectionClass)#%d (1) { + ["name"]=> + string(1) "B" +} +B::b +object(ReflectionClass)#%d (1) { + ["name"]=> + string(1) "B" +} +B->c +object(ReflectionClass)#%d (1) { + ["name"]=> + string(1) "B" +} +B::d +object(ReflectionClass)#%d (1) { + ["name"]=> + string(1) "B" +} +B->e +object(ReflectionClass)#%d (1) { + ["name"]=> + string(1) "A" +} +A::b +object(ReflectionClass)#%d (1) { + ["name"]=> + string(1) "B" +} +B::{closure} +NULL +{closure}