Add Closure::fromCallable().

Add the ability to create closures from callable as part of RFC: https://wiki.php.net/rfc/closurefromcallable
This commit is contained in:
Danack 2016-05-15 17:39:47 +01:00
parent bc63879d46
commit 63ca65daef
5 changed files with 659 additions and 14 deletions

View file

@ -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']);
}
?>

View file

@ -0,0 +1,122 @@
--TEST--
Testing closure() functionality
--FILE--
<?php
include('closureFunction.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===

View file

@ -0,0 +1,215 @@
--TEST--
Testing closure() functionality
--FILE--
<?php
include('closureFunction.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===

View file

@ -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 callabl 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, newthis)
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
};

View file

@ -1633,6 +1633,30 @@ ZEND_METHOD(reflection_function, export)
}
/* }}} */
static zend_function *_find_function_pointer(char *name_str, int name_len) {
char *lcname, *nsname;
zend_function *fptr;
lcname = zend_str_tolower_dup(name_str, name_len);
/* Ignore leading "\" */
nsname = lcname;
if (lcname[0] == '\\') {
nsname = &lcname[1];
name_len--;
}
if ((fptr = zend_hash_str_find_ptr(EG(function_table), nsname, name_len)) == NULL) {
efree(lcname);
return NULL;
}
efree(lcname);
return fptr;
}
/* {{{ proto public void ReflectionFunction::__construct(string name)
Constructor. Throws an Exception in case the given function does not exist */
ZEND_METHOD(reflection_function, __construct)
@ -1656,23 +1680,12 @@ ZEND_METHOD(reflection_function, __construct)
if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "s", &name_str, &name_len) == FAILURE) {
return;
}
lcname = zend_str_tolower_dup(name_str, name_len);
/* Ignore leading "\" */
nsname = lcname;
if (lcname[0] == '\\') {
nsname = &lcname[1];
name_len--;
}
if ((fptr = zend_hash_str_find_ptr(EG(function_table), nsname, name_len)) == NULL) {
efree(lcname);
fptr = _find_function_pointer(name_str, name_len);
if (fptr == NULL) {
zend_throw_exception_ex(reflection_exception_ptr, 0,
"Function %s() does not exist", name_str);
return;
return;
}
efree(lcname);
}
ZVAL_STR_COPY(&name, fptr->common.function_name);
@ -6793,6 +6806,10 @@ static const zend_function_entry reflection_zend_extension_functions[] = {
};
/* }}} */
ZEND_BEGIN_ARG_INFO_EX(arginfo_closure, 0, 0, 1)
ZEND_ARG_INFO(0, callable)
ZEND_END_ARG_INFO()
const zend_function_entry reflection_ext_functions[] = { /* {{{ */
PHP_FE_END
}; /* }}} */