diff options
-rw-r--r-- | Zend/tests/closures/cloure_from_callbable.inc | 187 | ||||
-rw-r--r-- | Zend/tests/closures/cloure_from_callbable_basic.phpt | 122 | ||||
-rw-r--r-- | Zend/tests/closures/cloure_from_callbable_error.phpt | 215 | ||||
-rw-r--r-- | Zend/tests/closures/cloure_from_callbable_reflection.phpt | 48 | ||||
-rw-r--r-- | Zend/zend_closures.c | 104 |
5 files changed, 676 insertions, 0 deletions
diff --git a/Zend/tests/closures/cloure_from_callbable.inc b/Zend/tests/closures/cloure_from_callbable.inc new file mode 100644 index 0000000000..5f0f220974 --- /dev/null +++ b/Zend/tests/closures/cloure_from_callbable.inc @@ -0,0 +1,187 @@ +<?php + +function bar($param1) +{ + return $param1; +} + + +$closure = function($param1) { + return $param1; +}; + +function test($fn) +{ + static $count = 0; + $input = "foo".$count; + $count++; + + $output = $fn($input); + return $input === $output; +} + +class Foo +{ + public static function publicStaticFunction($param1) + { + return $param1; + } + + private static function privateStaticFunction($param1) + { + return $param1; + } + + protected static function protectedStaticFunction($param1) + { + return $param1; + } + + private function privateInstanceFunc($param1) + { + return $param1; + } + + protected function protectedInstanceFunc($param1) + { + return $param1; + } + + + public function publicInstanceFunc($param1) + { + return $param1; + } + + public function closePrivateValid() + { + return Closure::fromCallable([$this, 'privateInstanceFunc']); + } + + public function closePrivateStatic() + { + return Closure::fromCallable([__CLASS__, 'privateStaticFunction']); + } + + public function bar($param1) + { + echo "this is bar\n"; + } + + public function getCallable() + { + return Closure::fromCallable([$this, 'publicInstanceFunc']); + } + + public function getSelfPublicInstance() + { + return Closure::fromCallable([$this, 'publicInstanceFunc']); + } + + public function getSelfColonPublicInstanceMethod() + { + return Closure::fromCallable('self::publicInstanceFunc'); + } +} + + + +class SubFoo extends Foo { + + public function closePrivateStaticInvalid() + { + return Closure::fromCallable([__CLASS__, 'privateStaticFunction']); + } + + + public function closePrivateInvalid() + { + return Closure::fromCallable([$this, 'privateInstanceFunc']); + } + + public function closeProtectedStaticMethod() + { + return Closure::fromCallable([__CLASS__, 'protectedStaticFunction']); + } + + public function closeProtectedValid() + { + return Closure::fromCallable([$this, 'protectedInstanceFunc']); + } + + public function getParentPublicInstanceMethod() + { + return Closure::fromCallable('parent::publicInstanceFunc'); + } + + public function getSelfColonParentPublicInstanceMethod() + { + return Closure::fromCallable('self::publicInstanceFunc'); + } + + + public function getSelfColonParentProtectedInstanceMethod() + { + return Closure::fromCallable('self::protectedInstanceFunc'); + } + + public function getSelfColonParentPrivateInstanceMethod() + { + return Closure::fromCallable('self::privateInstanceFunc'); + } +} + + +class MagicCall +{ + public function __call($name, $arguments) + { + $info = ['__call']; + $info[] = $name; + $info = array_merge($info, $arguments); + return implode(',', $info); + } + + public static function __callStatic($name, $arguments) + { + $info = ['__callStatic']; + $info[] = $name; + $info = array_merge($info, $arguments); + return implode(',', $info); + } +} + + + +class PublicInvokable +{ + public function __invoke($param1) + { + return $param1; + } +} + + +function functionAccessProtected() +{ + $foo = new Foo; + + return Closure::fromCallable([$foo, 'protectedStaticFunction']); +} + +function functionAccessPrivate() +{ + $foo = new Foo; + + return Closure::fromCallable([$foo, 'privateStaticFunction']); +} + + +function functionAccessMethodDoesntExist() +{ + $foo = new Foo; + + return Closure::fromCallable([$foo, 'thisDoesNotExist']); +} + +?> diff --git a/Zend/tests/closures/cloure_from_callbable_basic.phpt b/Zend/tests/closures/cloure_from_callbable_basic.phpt new file mode 100644 index 0000000000..c498a9644b --- /dev/null +++ b/Zend/tests/closures/cloure_from_callbable_basic.phpt @@ -0,0 +1,122 @@ +--TEST-- +Testing closure() functionality +--FILE-- +<?php + +include('cloure_from_callbable.inc'); + +echo 'Access public static function'; +$fn = Closure::fromCallable(['Foo', 'publicStaticFunction']); +echo $fn(" OK".PHP_EOL); + +echo 'Access public static function with different case'; +$fn = Closure::fromCallable(['fOo', 'publicStaticfUNCTION']); +echo $fn(" OK".PHP_EOL); + +echo 'Access public static function with colon scheme'; +$fn = Closure::fromCallable('Foo::publicStaticFunction'); +echo $fn(" OK".PHP_EOL); + +echo 'Access public instance method of object'; +$fn = Closure::fromCallable([new Foo, 'publicInstanceFunc']); +echo $fn(" OK".PHP_EOL); + +echo 'Access public instance method of parent object through parent:: '; +$fn = Closure::fromCallable([new Foo, 'publicInstanceFunc']); +echo $fn(" OK".PHP_EOL); + +echo 'Function that exists'; +$fn = Closure::fromCallable('bar'); +echo $fn(" OK".PHP_EOL); + +echo 'Function that exists with different spelling'; +$fn = Closure::fromCallable('BAR'); +echo $fn(" OK".PHP_EOL); + +echo 'Closure is already a closure'; +$fn = Closure::fromCallable($closure); +echo $fn(" OK".PHP_EOL); + +echo 'Class with public invokable'; +$fn = Closure::fromCallable(new PublicInvokable); +echo $fn(" OK".PHP_EOL); + +echo "Instance return private method as callable"; +$foo = new Foo; +$fn = $foo->closePrivateValid(); +echo $fn(" OK".PHP_EOL); + +echo "Instance return private static method as callable"; +$foo = new Foo; +$fn = $foo->closePrivateStatic(); +echo $fn(" OK".PHP_EOL); + +echo 'Instance return protected static method as callable'; +$subFoo = new SubFoo; +$fn = $subFoo->closeProtectedStaticMethod(); +echo $fn(" OK".PHP_EOL); + +echo 'Subclass closure over parent class protected method'; +$subFoo = new SubFoo; +$fn = $subFoo->closeProtectedValid(); +echo $fn(" OK".PHP_EOL); + +echo 'Subclass closure over parent class static protected method'; +$subFoo = new SubFoo; +$fn = $subFoo->closeProtectedStaticMethod(); +echo $fn(" OK".PHP_EOL); + +echo 'Access public instance method of parent object through "parent::" '; +$subFoo = new SubFoo; +$fn = $subFoo->getParentPublicInstanceMethod(); +echo $fn(" OK".PHP_EOL); + +echo 'Access public instance method of self object through "self::" '; +$foo = new Foo; +$fn = $foo->getSelfColonPublicInstanceMethod(); +echo $fn(" OK".PHP_EOL); + +echo 'Access public instance method of parent object through "self::" to parent method'; +$foo = new SubFoo; +$fn = $foo->getSelfColonParentPublicInstanceMethod(); +echo $fn(" OK".PHP_EOL); + +echo 'Access proteced instance method of parent object through "self::" to parent method'; +$foo = new SubFoo; +$fn = $foo->getSelfColonParentProtectedInstanceMethod(); +echo $fn(" OK".PHP_EOL); + +echo 'MagicCall __call instance method '; +$fn = Closure::fromCallable([new MagicCall, 'nonExistentMethod']); +echo $fn(" OK".PHP_EOL); + +echo 'MagicCall __callStatic static method '; +$fn = Closure::fromCallable(['MagicCall', 'nonExistentMethod']); +echo $fn(" OK".PHP_EOL); + + +?> +===DONE=== +--EXPECT-- + +Access public static function OK +Access public static function with different case OK +Access public static function with colon scheme OK +Access public instance method of object OK +Access public instance method of parent object through parent:: OK +Function that exists OK +Function that exists with different spelling OK +Closure is already a closure OK +Class with public invokable OK +Instance return private method as callable OK +Instance return private static method as callable OK +Instance return protected static method as callable OK +Subclass closure over parent class protected method OK +Subclass closure over parent class static protected method OK +Access public instance method of parent object through "parent::" OK +Access public instance method of self object through "self::" OK +Access public instance method of parent object through "self::" to parent method OK +Access proteced instance method of parent object through "self::" to parent method OK +MagicCall __call instance method __call,nonExistentMethod, OK +MagicCall __callStatic static method __callStatic,nonExistentMethod, OK +===DONE=== diff --git a/Zend/tests/closures/cloure_from_callbable_error.phpt b/Zend/tests/closures/cloure_from_callbable_error.phpt new file mode 100644 index 0000000000..79fbf14757 --- /dev/null +++ b/Zend/tests/closures/cloure_from_callbable_error.phpt @@ -0,0 +1,215 @@ +--TEST-- +Testing closure() functionality +--FILE-- +<?php + +include('cloure_from_callbable.inc'); + +echo 'Cannot access privateInstance method statically'."\n"; +try { + $fn = Closure::fromCallable(['Foo', 'privateInstanceFunc']); + echo "Test failed to fail and return was : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + + +echo 'Cannot access privateInstance method statically with colon scheme'."\n"; +try { + $fn = Closure::fromCallable('Foo::privateInstanceFunc'); + echo "Test failed to fail and return was : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo 'Cannot access privateInstance method'."\n"; +try { + $fn = Closure::fromCallable([new Foo, 'privateInstanceFunc']); + echo "Test failed to fail and return was : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo 'SubClass cannot access private instance method'."\n"; +try { + $fn = Closure::fromCallable([new SubFoo, 'privateInstanceFunc']); + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo 'Cannot access private static function of instance'."\n"; +try { + $fn = Closure::fromCallable([new Foo, 'privateStaticFunction']); + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo 'Cannot access private static method statically'."\n"; +try { + $fn = Closure::fromCallable(['Foo', 'privateStaticFunction']); + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo 'Cannot access private static method statically with colon scheme'."\n"; +try { + $fn = Closure::fromCallable('Foo::privateStaticFunction'); + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo 'Non-existent method should fail'."\n"; +try { + $fn = Closure::fromCallable('Foo::nonExistentFunction'); + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo 'Non-existent class should fail'."\n"; +try { + $fn = Closure::fromCallable(['NonExistentClass', 'foo']); + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo 'Non-existent function should fail'."\n"; +try { + $fn = Closure::fromCallable('thisDoesNotExist'); + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + + +echo 'Subclass cannot closure over parent private instance method'."\n"; +try { + $subFoo = new SubFoo; + $fn = $subFoo->closePrivateInvalid(); + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo 'Subclass cannot closure over parant private static method'."\n"; +try { + $subFoo = new SubFoo; + $fn = $subFoo->closePrivateStaticInvalid(); + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo 'Function scope cannot closure over protected instance method'."\n"; +try { + $fn = functionAccessProtected(); + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo 'Function scope cannot closure over private instance method'."\n"; +try { + $fn = functionAccessPrivate(); + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo 'Access private instance method of parent object through "self::" to parent method'."\n"; +try { + $foo = new SubFoo; + $fn = $foo->getSelfColonParentPrivateInstanceMethod(); + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; +} +catch (\TypeError $te) { + //This is the expected outcome. +} +catch (\Throwable $t) { + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; +} + +echo "OK\n"; + +?> +===DONE=== +--EXPECT-- + +Cannot access privateInstance method statically +Cannot access privateInstance method statically with colon scheme +Cannot access privateInstance method +SubClass cannot access private instance method +Cannot access private static function of instance +Cannot access private static method statically +Cannot access private static method statically with colon scheme +Non-existent method should fail +Non-existent class should fail +Non-existent function should fail +Subclass cannot closure over parent private instance method +Subclass cannot closure over parant private static method +Function scope cannot closure over protected instance method +Function scope cannot closure over private instance method +Access private instance method of parent object through "self::" to parent method +OK +===DONE=== diff --git a/Zend/tests/closures/cloure_from_callbable_reflection.phpt b/Zend/tests/closures/cloure_from_callbable_reflection.phpt new file mode 100644 index 0000000000..0d2dc4980b --- /dev/null +++ b/Zend/tests/closures/cloure_from_callbable_reflection.phpt @@ -0,0 +1,48 @@ +--TEST-- +Imagick::readImage test +--SKIPIF-- +<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?> +--FILE-- +<?php + +class Bar { + public static function staticMethod(Bar $bar, int $int, $none) {} + public static function instanceMethod(Bar $bar, int $int, $none) {} +} + +function foo(Bar $bar, int $int, $none) { + +} + +$fn = function (Bar $bar, int $x, $none) {}; +$bar = new Bar(); + +$callables = [ + 'foo', + $fn, + 'Bar::staticMethod', + [$bar, 'instanceMethod'] +]; + + +foreach ($callables as $callable) { + $closure = Closure::fromCallable($callable); + $refl = new ReflectionFunction($closure); + foreach ($refl->getParameters() as $param) { + if ($param->hasType()) { + $type = $param->getType(); + echo $type->__toString() . "\n"; + } + } +} + +?> +--EXPECTF-- +Bar +int +Bar +int +Bar +int +Bar +int diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index 810ac6a66c..dadf461d71 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -235,6 +235,105 @@ ZEND_METHOD(Closure, bind) } /* }}} */ +static void zend_closure_call_magic(INTERNAL_FUNCTION_PARAMETERS) { + zend_fcall_info fci; + zend_fcall_info_cache fcc; + + memset(&fci, 0, sizeof(zend_fcall_info)); + memset(&fci, 0, sizeof(zend_fcall_info_cache)); + + fci.size = sizeof(zend_fcall_info); + fci.retval = return_value; + + fcc.initialized = 1; + fcc.function_handler = (zend_function *) EX(func)->common.arg_info; + fci.params = (zval*) emalloc(sizeof(zval) * 2); + fci.param_count = 2; + ZVAL_STR(&fci.params[0], EX(func)->common.function_name); + array_init(&fci.params[1]); + zend_copy_parameters_array(ZEND_NUM_ARGS(), &fci.params[1]); + + fci.object = Z_OBJ(EX(This)); + fcc.object = Z_OBJ(EX(This)); + fcc.calling_scope = zend_get_executed_scope(); + + zend_call_function(&fci, &fcc); + + zval_ptr_dtor(&fci.params[0]); + zval_ptr_dtor(&fci.params[1]); + efree(fci.params); +} + + +static int zend_create_closure_from_callable(zval *return_value, zval *callable, char **error) { + zend_fcall_info_cache fcc; + zend_function *mptr; + zval instance; + + if (!zend_is_callable_ex(callable, NULL, 0, NULL, &fcc, error)) { + return FAILURE; + } + + mptr = fcc.function_handler; + if (mptr == NULL) { + return FAILURE; + } + + if (mptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) { + zend_internal_function call; + memset(&call, 0, sizeof(zend_internal_function)); + + call.type = ZEND_INTERNAL_FUNCTION; + call.handler = zend_closure_call_magic; + call.function_name = mptr->common.function_name; + call.arg_info = (zend_internal_arg_info *) mptr->common.prototype; + call.scope = mptr->common.scope; + + zend_free_trampoline(mptr); + mptr = (zend_function *) &call; + } + + ZVAL_OBJ(&instance, fcc.object); + zend_create_closure(return_value, mptr, mptr->common.scope, fcc.object ? fcc.object->ce : NULL, fcc.object ? &instance : NULL); + + return SUCCESS; +} + + +/* {{{ proto Closure Closure::fromCallable(callable callable) + Create a closure from a callable using the current scope. */ +ZEND_METHOD(Closure, fromCallable) +{ + zval *callable; + int success; + char *error = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &callable) == FAILURE) { + return; + } + + if (Z_TYPE_P(callable) == IS_OBJECT && instanceof_function(Z_OBJCE_P(callable), zend_ce_closure)) { + // It's already a closure + RETURN_ZVAL(callable, 1, 0); + } + + // create closure as if it were called from parent scope + EG(current_execute_data) = EX(prev_execute_data); + success = zend_create_closure_from_callable(return_value, callable, &error); + EG(current_execute_data) = execute_data; + + if (success == FAILURE) { + zend_clear_exception(); + if (error) { + zend_throw_exception_ex(zend_ce_type_error, 0, "Failed to create closure from callable: %s", error); + efree(error); + } else { + zend_throw_exception_ex(zend_ce_type_error, 0, "Failed to create closure from callable"); + } + } +} +/* }}} */ + static ZEND_COLD zend_function *zend_closure_get_constructor(zend_object *object) /* {{{ */ { zend_throw_error(NULL, "Instantiation of 'Closure' is not allowed"); @@ -489,11 +588,16 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_call, 0, 0, 1) ZEND_ARG_VARIADIC_INFO(0, parameters) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_fromcallable, 0, 0, 1) + ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + static const zend_function_entry closure_functions[] = { ZEND_ME(Closure, __construct, NULL, ZEND_ACC_PRIVATE) ZEND_ME(Closure, bind, arginfo_closure_bind, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_MALIAS(Closure, bindTo, bind, arginfo_closure_bindto, ZEND_ACC_PUBLIC) ZEND_ME(Closure, call, arginfo_closure_call, ZEND_ACC_PUBLIC) + ZEND_ME(Closure, fromCallable, arginfo_closure_fromcallable, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_FE_END }; |