summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Zend/tests/closures/cloure_from_callbable.inc187
-rw-r--r--Zend/tests/closures/cloure_from_callbable_basic.phpt122
-rw-r--r--Zend/tests/closures/cloure_from_callbable_error.phpt215
-rw-r--r--Zend/tests/closures/cloure_from_callbable_reflection.phpt48
-rw-r--r--Zend/zend_closures.c104
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
};