From 93f11d84294d7eaadb9d9fc3c0996ff30279011d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 2 Sep 2022 13:32:50 +0200 Subject: [PATCH 1/3] Fix GH-8932: Provide a way to get the called-scope of closures (#9299) Co-authored-by: Christoph M. Becker --- ext/reflection/php_reflection.c | 22 ++++++ ext/reflection/php_reflection.stub.php | 3 + ext/reflection/php_reflection_arginfo.h | 6 +- ...lectionFunction_getClosureCalledClass.phpt | 70 +++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 ext/reflection/tests/ReflectionFunction_getClosureCalledClass.phpt diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index e6c63b852e0..ae5b0f1ac24 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1633,6 +1633,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 a dynamically created closure for the function */ ZEND_METHOD(ReflectionFunction, getClosure) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index b0bfade520f..31d8d0530c9 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -48,6 +48,9 @@ abstract class ReflectionFunctionAbstract implements Reflector /** @return ReflectionClass|null */ public function getClosureScopeClass() {} + /** @return ReflectionClass|null */ + public function getClosureCalledClass() {} + /** @return string|false */ public function getDocComment() {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 6fdc0c44d22..788485b2dd7 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: 6e98777552147f4a413db16ecd87c9a6931f9c00 */ + * Stub hash: 9309c0d567aae3041255b5f9b9782add9b6ac783 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -27,6 +27,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionFunctionAbstract_getClosureScopeClass arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionFunctionAbstract_getClosureCalledClass arginfo_class_ReflectionFunctionAbstract_inNamespace + #define arginfo_class_ReflectionFunctionAbstract_getDocComment arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionFunctionAbstract_getEndLine arginfo_class_ReflectionFunctionAbstract_inNamespace @@ -501,6 +503,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, isGenerator); ZEND_METHOD(ReflectionFunctionAbstract, isVariadic); ZEND_METHOD(ReflectionFunctionAbstract, getClosureThis); ZEND_METHOD(ReflectionFunctionAbstract, getClosureScopeClass); +ZEND_METHOD(ReflectionFunctionAbstract, getClosureCalledClass); ZEND_METHOD(ReflectionFunctionAbstract, getDocComment); ZEND_METHOD(ReflectionFunctionAbstract, getEndLine); ZEND_METHOD(ReflectionFunctionAbstract, getExtension); @@ -719,6 +722,7 @@ static const zend_function_entry class_ReflectionFunctionAbstract_methods[] = { ZEND_ME(ReflectionFunctionAbstract, isVariadic, arginfo_class_ReflectionFunctionAbstract_isVariadic, 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, getDocComment, arginfo_class_ReflectionFunctionAbstract_getDocComment, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getEndLine, arginfo_class_ReflectionFunctionAbstract_getEndLine, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getExtension, arginfo_class_ReflectionFunctionAbstract_getExtension, 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..3c556c2e37c --- /dev/null +++ b/ext/reflection/tests/ReflectionFunction_getClosureCalledClass.phpt @@ -0,0 +1,70 @@ +--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"; + } +} + +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(); +?> +--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 From db1ef9720925424c549bfd06cc5f434eea1f1173 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 2 Sep 2022 13:14:55 +0200 Subject: [PATCH 2/3] Add tests --- ...lectionFunction_getClosureCalledClass.phpt | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/ext/reflection/tests/ReflectionFunction_getClosureCalledClass.phpt b/ext/reflection/tests/ReflectionFunction_getClosureCalledClass.phpt index 3c556c2e37c..ab06a6472e0 100644 --- a/ext/reflection/tests/ReflectionFunction_getClosureCalledClass.phpt +++ b/ext/reflection/tests/ReflectionFunction_getClosureCalledClass.phpt @@ -19,6 +19,12 @@ class A { public function c() { echo static::class.'->c', "\n"; } + + public function makeClosure() { + return function () { + echo static::class.'::{closure}'."\n"; + }; + } } class B extends A {} @@ -46,6 +52,26 @@ $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) { @@ -68,3 +94,15 @@ object(ReflectionClass)#%d (1) { 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} From 6aedc5eaf003967bbf0ce8b95ebe8fa7766c0a4a Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 2 Sep 2022 13:40:31 +0200 Subject: [PATCH 3/3] [ci skip] NEWS --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 81762e523fe..805dec249ad 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,10 @@ PHP NEWS . Fixed bug #77780 ("Headers already sent..." when previous connection was aborted). (Jakub Zelenka) +- Reflection: + . Fixed bug GH-8932 (ReflectionFunction provides no way to get the called + class of a Closure). (cmb, Nicolas Grekas) + - Streams: . Fixed bug GH-9316 ($http_response_header is wrong for long status line). (cmb, timwolla)