mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
restore $this support for closures to its former glory
This commit is contained in:
parent
5a211da7af
commit
c93a4f192b
23 changed files with 718 additions and 28 deletions
1
NEWS
1
NEWS
|
@ -11,6 +11,7 @@
|
|||
ReflectionExtension::isPersistent(). (Johannes)
|
||||
- Added ReflectionZendExtension class. (Johannes)
|
||||
- Added command line option --rz to CLI. (Johannes)
|
||||
- Added closure $this support back. (Stas)
|
||||
|
||||
- default_charset if not specified is now UTF-8 instead of ISO-8859-1. (Rasmus)
|
||||
|
||||
|
|
74
Zend/tests/closure_005.phpt
Normal file
74
Zend/tests/closure_005.phpt
Normal file
|
@ -0,0 +1,74 @@
|
|||
--TEST--
|
||||
Closure 005: Lambda inside class, lifetime of $this
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
private $x;
|
||||
|
||||
function __construct($x) {
|
||||
$this->x = $x;
|
||||
}
|
||||
|
||||
function __destruct() {
|
||||
echo "Destroyed\n";
|
||||
}
|
||||
|
||||
function getIncer($val) {
|
||||
return function() use ($val) {
|
||||
$this->x += $val;
|
||||
};
|
||||
}
|
||||
|
||||
function getPrinter() {
|
||||
return function() {
|
||||
echo $this->x."\n";
|
||||
};
|
||||
}
|
||||
|
||||
function getError() {
|
||||
return static function() {
|
||||
echo $this->x."\n";
|
||||
};
|
||||
}
|
||||
|
||||
function printX() {
|
||||
echo $this->x."\n";
|
||||
}
|
||||
}
|
||||
|
||||
$a = new A(3);
|
||||
$incer = $a->getIncer(2);
|
||||
$printer = $a->getPrinter();
|
||||
$error = $a->getError();
|
||||
|
||||
$a->printX();
|
||||
$printer();
|
||||
$incer();
|
||||
$a->printX();
|
||||
$printer();
|
||||
|
||||
unset($a);
|
||||
|
||||
$incer();
|
||||
$printer();
|
||||
|
||||
unset($incer);
|
||||
$printer();
|
||||
|
||||
unset($printer);
|
||||
|
||||
$error();
|
||||
|
||||
echo "Done\n";
|
||||
?>
|
||||
--EXPECTF--
|
||||
3
|
||||
3
|
||||
5
|
||||
5
|
||||
7
|
||||
7
|
||||
Destroyed
|
||||
|
||||
Fatal error: Using $this when not in object context in %sclosure_005.php on line 28
|
38
Zend/tests/closure_007.phpt
Normal file
38
Zend/tests/closure_007.phpt
Normal file
|
@ -0,0 +1,38 @@
|
|||
--TEST--
|
||||
Closure 007: Nested lambdas in classes
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
private $x = 0;
|
||||
|
||||
function getClosureGetter () {
|
||||
return function () {
|
||||
return function () {
|
||||
$this->x++;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function printX () {
|
||||
echo $this->x."\n";
|
||||
}
|
||||
}
|
||||
|
||||
$a = new A;
|
||||
$a->printX();
|
||||
$getClosure = $a->getClosureGetter();
|
||||
$a->printX();
|
||||
$closure = $getClosure();
|
||||
$a->printX();
|
||||
$closure();
|
||||
$a->printX();
|
||||
|
||||
echo "Done\n";
|
||||
?>
|
||||
--EXPECT--
|
||||
0
|
||||
0
|
||||
0
|
||||
1
|
||||
Done
|
|
@ -23,16 +23,18 @@ var_dump($y()->test);
|
|||
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(foo)#%d (%d) {
|
||||
object(foo)#%d (2) {
|
||||
["test":"foo":private]=>
|
||||
int(3)
|
||||
["a"]=>
|
||||
object(Closure)#%d (1) {
|
||||
object(Closure)#%d (2) {
|
||||
["static"]=>
|
||||
array(1) {
|
||||
["a"]=>
|
||||
*RECURSION*
|
||||
}
|
||||
["this"]=>
|
||||
*RECURSION*
|
||||
}
|
||||
}
|
||||
bool(true)
|
||||
|
|
|
@ -1,16 +1,26 @@
|
|||
--TEST--
|
||||
Closure 024: Trying to clone the Closure object
|
||||
Closure 024: Clone the Closure object
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$a = function () {
|
||||
return clone function () {
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
$a = 1;
|
||||
$c = function($add) use(&$a) { return $a+$add; };
|
||||
|
||||
$a();
|
||||
$cc = clone $c;
|
||||
|
||||
echo $c(10)."\n";
|
||||
echo $cc(10)."\n";
|
||||
|
||||
$a++;
|
||||
|
||||
echo $c(10)."\n";
|
||||
echo $cc(10)."\n";
|
||||
|
||||
echo "Done.\n";
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Trying to clone an uncloneable object of class Closure in %s on line %d
|
||||
11
|
||||
11
|
||||
12
|
||||
12
|
||||
Done.
|
|
@ -32,7 +32,9 @@ object(foo)#%d (1) {
|
|||
["a"]=>
|
||||
array(1) {
|
||||
[0]=>
|
||||
object(Closure)#%d (0) {
|
||||
object(Closure)#%d (1) {
|
||||
["this"]=>
|
||||
*RECURSION*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +43,12 @@ int(1)
|
|||
string(1) "a"
|
||||
array(1) {
|
||||
[0]=>
|
||||
object(Closure)#%d (0) {
|
||||
object(Closure)#%d (1) {
|
||||
["this"]=>
|
||||
object(foo)#%d (1) {
|
||||
["a"]=>
|
||||
*RECURSION*
|
||||
}
|
||||
}
|
||||
}
|
||||
int(1)
|
||||
|
|
33
Zend/tests/closure_036.phpt
Executable file
33
Zend/tests/closure_036.phpt
Executable file
|
@ -0,0 +1,33 @@
|
|||
--TEST--
|
||||
Closure 036: Rebinding closures
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
private $x;
|
||||
|
||||
public function __construct($v) {
|
||||
$this->x = $v;
|
||||
}
|
||||
|
||||
public function getIncrementor() {
|
||||
return function() { return ++$this->x; };
|
||||
}
|
||||
}
|
||||
|
||||
$a = new A(0);
|
||||
$b = new A(10);
|
||||
|
||||
$ca = $a->getIncrementor();
|
||||
$cb = $ca->bindTo($b);
|
||||
$cb2 = Closure::bind($b, $ca);
|
||||
|
||||
var_dump($ca());
|
||||
var_dump($cb());
|
||||
var_dump($cb2());
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
int(1)
|
||||
int(11)
|
||||
int(12)
|
|
@ -37,6 +37,7 @@
|
|||
typedef struct _zend_closure {
|
||||
zend_object std;
|
||||
zend_function func;
|
||||
zval *this_ptr;
|
||||
HashTable *debug_info;
|
||||
} zend_closure;
|
||||
|
||||
|
@ -75,6 +76,39 @@ ZEND_METHOD(Closure, __invoke) /* {{{ */
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ proto Closure Closure::bindTo(object $to)
|
||||
Bind a closure to another object */
|
||||
ZEND_METHOD(Closure, bindTo) /* {{{ */
|
||||
{
|
||||
zval *newthis;
|
||||
zend_closure *closure = (zend_closure *)zend_object_store_get_object(getThis() TSRMLS_CC);
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o!", &newthis) == FAILURE) {
|
||||
RETURN_NULL();
|
||||
}
|
||||
|
||||
zend_create_closure(return_value, &closure->func, newthis?Z_OBJCE_P(newthis):NULL, newthis TSRMLS_CC);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ proto Closure Closure::bind(object $to, Closure $old)
|
||||
Create a closure to with binding to another object */
|
||||
ZEND_METHOD(Closure, bind) /* {{{ */
|
||||
{
|
||||
zval *newthis, *zclosure;
|
||||
zend_closure *closure;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o!O", &newthis, &zclosure, zend_ce_closure) == FAILURE) {
|
||||
RETURN_NULL();
|
||||
}
|
||||
|
||||
closure = (zend_closure *)zend_object_store_get_object(zclosure TSRMLS_CC);
|
||||
|
||||
zend_create_closure(return_value, &closure->func, newthis?Z_OBJCE_P(newthis):NULL, newthis TSRMLS_CC);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
|
||||
static zend_function *zend_closure_get_constructor(zval *object TSRMLS_DC) /* {{{ */
|
||||
{
|
||||
zend_error(E_RECOVERABLE_ERROR, "Instantiation of 'Closure' is not allowed");
|
||||
|
@ -111,6 +145,13 @@ ZEND_API const zend_function *zend_get_closure_method_def(zval *obj TSRMLS_DC) /
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
ZEND_API zval* zend_get_closure_this_ptr(zval *obj TSRMLS_DC) /* {{{ */
|
||||
{
|
||||
zend_closure *closure = (zend_closure *)zend_object_store_get_object(obj TSRMLS_CC);
|
||||
return closure->this_ptr;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static zend_function *zend_closure_get_method(zval **object_ptr, char *method_name, int method_len TSRMLS_DC) /* {{{ */
|
||||
{
|
||||
char *lc_name;
|
||||
|
@ -125,7 +166,7 @@ static zend_function *zend_closure_get_method(zval **object_ptr, char *method_na
|
|||
return zend_get_closure_invoke_method(*object_ptr TSRMLS_CC);
|
||||
}
|
||||
free_alloca(lc_name, use_heap);
|
||||
return NULL;
|
||||
return std_object_handlers.get_method(object_ptr, method_name, method_len TSRMLS_CC);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
|
@ -187,6 +228,10 @@ static void zend_closure_free_storage(void *object TSRMLS_DC) /* {{{ */
|
|||
efree(closure->debug_info);
|
||||
}
|
||||
|
||||
if (closure->this_ptr) {
|
||||
zval_ptr_dtor(&closure->this_ptr);
|
||||
}
|
||||
|
||||
efree(closure);
|
||||
}
|
||||
/* }}} */
|
||||
|
@ -208,6 +253,17 @@ static zend_object_value zend_closure_new(zend_class_entry *class_type TSRMLS_DC
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
static zend_object_value zend_closure_clone(zval *zobject TSRMLS_DC) /* {{{ */
|
||||
{
|
||||
zend_closure *closure = (zend_closure *)zend_object_store_get_object(zobject TSRMLS_CC);
|
||||
zval result;
|
||||
|
||||
zend_create_closure(&result, &closure->func, closure->func.common.scope, closure->this_ptr);
|
||||
return Z_OBJVAL(result);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
|
||||
int zend_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC) /* {{{ */
|
||||
{
|
||||
zend_closure *closure;
|
||||
|
@ -219,10 +275,17 @@ int zend_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, zend_function
|
|||
closure = (zend_closure *)zend_object_store_get_object(obj TSRMLS_CC);
|
||||
*fptr_ptr = &closure->func;
|
||||
|
||||
if (zobj_ptr) {
|
||||
*zobj_ptr = NULL;
|
||||
if (closure->this_ptr) {
|
||||
if (zobj_ptr) {
|
||||
*zobj_ptr = closure->this_ptr;
|
||||
}
|
||||
*ce_ptr = Z_OBJCE_P(closure->this_ptr);
|
||||
} else {
|
||||
if (zobj_ptr) {
|
||||
*zobj_ptr = NULL;
|
||||
}
|
||||
*ce_ptr = closure->func.common.scope;
|
||||
}
|
||||
*ce_ptr = NULL;
|
||||
return SUCCESS;
|
||||
}
|
||||
/* }}} */
|
||||
|
@ -248,6 +311,11 @@ static HashTable *zend_closure_get_debug_info(zval *object, int *is_temp TSRMLS_
|
|||
zend_symtable_update(closure->debug_info, "static", sizeof("static"), (void *) &val, sizeof(zval *), NULL);
|
||||
}
|
||||
|
||||
if (closure->this_ptr) {
|
||||
Z_ADDREF_P(closure->this_ptr);
|
||||
zend_symtable_update(closure->debug_info, "this", sizeof("this"), (void *) &closure->this_ptr, sizeof(zval *), NULL);
|
||||
}
|
||||
|
||||
if (arg_info) {
|
||||
zend_uint i, required = closure->func.common.required_num_args;
|
||||
|
||||
|
@ -288,8 +356,19 @@ ZEND_METHOD(Closure, __construct)
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_bindto, 0, 0, 0)
|
||||
ZEND_ARG_INFO(0, newthis)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_bind, 0, 0, 0)
|
||||
ZEND_ARG_INFO(0, newthis)
|
||||
ZEND_ARG_INFO(0, closure)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
static const zend_function_entry closure_functions[] = {
|
||||
ZEND_ME(Closure, __construct, NULL, ZEND_ACC_PRIVATE)
|
||||
ZEND_ME(Closure, bindTo, arginfo_closure_bindto, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Closure, bind, arginfo_closure_bind, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
|
||||
{NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
|
@ -313,7 +392,7 @@ void zend_register_closure_ce(TSRMLS_D) /* {{{ */
|
|||
closure_handlers.has_property = zend_closure_has_property;
|
||||
closure_handlers.unset_property = zend_closure_unset_property;
|
||||
closure_handlers.compare_objects = zend_closure_compare_objects;
|
||||
closure_handlers.clone_obj = NULL;
|
||||
closure_handlers.clone_obj = zend_closure_clone;
|
||||
closure_handlers.get_debug_info = zend_closure_get_debug_info;
|
||||
closure_handlers.get_closure = zend_closure_get_closure;
|
||||
}
|
||||
|
@ -356,7 +435,7 @@ static int zval_copy_static_var(zval **p TSRMLS_DC, int num_args, va_list args,
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
ZEND_API void zend_create_closure(zval *res, zend_function *func TSRMLS_DC) /* {{{ */
|
||||
ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zval *this_ptr TSRMLS_DC) /* {{{ */
|
||||
{
|
||||
zend_closure *closure;
|
||||
|
||||
|
@ -375,9 +454,39 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func TSRMLS_DC) /* {
|
|||
zend_hash_apply_with_arguments(static_variables TSRMLS_CC, (apply_func_args_t)zval_copy_static_var, 1, closure->func.op_array.static_variables);
|
||||
}
|
||||
(*closure->func.op_array.refcount)++;
|
||||
} else {
|
||||
/* verify that we aren't binding internal function to a wrong scope */
|
||||
if(func->common.scope != NULL) {
|
||||
if(scope && !instanceof_function(scope, func->common.scope TSRMLS_CC)) {
|
||||
zend_error(E_WARNING, "Cannot bind function %s::%s to scope class %s", func->common.scope->name, func->common.function_name, scope->name);
|
||||
scope = NULL;
|
||||
}
|
||||
if(scope && this_ptr && (func->common.fn_flags & ZEND_ACC_STATIC) == 0 &&
|
||||
!instanceof_function(Z_OBJCE_P(this_ptr), closure->func.common.scope TSRMLS_CC)) {
|
||||
zend_error(E_WARNING, "Cannot bind function %s::%s to object of class %s", func->common.scope->name, func->common.function_name, Z_OBJCE_P(this_ptr)->name);
|
||||
scope = NULL;
|
||||
this_ptr = NULL;
|
||||
}
|
||||
} else {
|
||||
/* if it's a free function, we won't set scope & this since they're meaningless */
|
||||
this_ptr = NULL;
|
||||
scope = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
closure->func.common.scope = NULL;
|
||||
closure->func.common.scope = scope;
|
||||
if (scope) {
|
||||
closure->func.common.fn_flags |= ZEND_ACC_PUBLIC;
|
||||
if (this_ptr && (closure->func.common.fn_flags & ZEND_ACC_STATIC) == 0) {
|
||||
closure->this_ptr = this_ptr;
|
||||
Z_ADDREF_P(this_ptr);
|
||||
} else {
|
||||
closure->func.common.fn_flags |= ZEND_ACC_STATIC;
|
||||
closure->this_ptr = NULL;
|
||||
}
|
||||
} else {
|
||||
closure->this_ptr = NULL;
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
|
|
|
@ -30,9 +30,10 @@ void zend_register_closure_ce(TSRMLS_D);
|
|||
|
||||
extern ZEND_API zend_class_entry *zend_ce_closure;
|
||||
|
||||
ZEND_API void zend_create_closure(zval *res, zend_function *op_array TSRMLS_DC);
|
||||
ZEND_API void zend_create_closure(zval *res, zend_function *op_array, zend_class_entry *scope, zval *this_ptr TSRMLS_DC);
|
||||
ZEND_API zend_function *zend_get_closure_invoke_method(zval *obj TSRMLS_DC);
|
||||
ZEND_API const zend_function *zend_get_closure_method_def(zval *obj TSRMLS_DC);
|
||||
ZEND_API zval* zend_get_closure_this_ptr(zval *obj TSRMLS_DC);
|
||||
|
||||
END_EXTERN_C()
|
||||
|
||||
|
|
|
@ -1403,7 +1403,7 @@ void zend_do_begin_function_declaration(znode *function_token, znode *function_n
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference TSRMLS_DC) /* {{{ */
|
||||
void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference, int is_static TSRMLS_DC) /* {{{ */
|
||||
{
|
||||
znode function_name;
|
||||
zend_op_array *current_op_array = CG(active_op_array);
|
||||
|
@ -1423,6 +1423,9 @@ void zend_do_begin_lambda_function_declaration(znode *result, znode *function_to
|
|||
zval_dtor(¤t_op->op2.u.constant);
|
||||
ZVAL_LONG(¤t_op->op2.u.constant, zend_hash_func(Z_STRVAL(current_op->op1.u.constant), Z_STRLEN(current_op->op1.u.constant)));
|
||||
current_op->result = *result;
|
||||
if (is_static) {
|
||||
CG(active_op_array)->fn_flags |= ZEND_ACC_STATIC;
|
||||
}
|
||||
CG(active_op_array)->fn_flags |= ZEND_ACC_CLOSURE;
|
||||
}
|
||||
/* }}} */
|
||||
|
|
|
@ -434,7 +434,7 @@ void zend_do_end_function_call(znode *function_name, znode *result, const znode
|
|||
void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC);
|
||||
void zend_do_handle_exception(TSRMLS_D);
|
||||
|
||||
void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference TSRMLS_DC);
|
||||
void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference, int is_static TSRMLS_DC);
|
||||
void zend_do_fetch_lexical_variable(znode *varname, zend_bool is_ref TSRMLS_DC);
|
||||
|
||||
void zend_do_try(znode *try_token TSRMLS_DC);
|
||||
|
|
|
@ -647,8 +647,10 @@ expr_without_variable:
|
|||
| T_ARRAY '(' array_pair_list ')' { $$ = $3; }
|
||||
| '`' backticks_expr '`' { zend_do_shell_exec(&$$, &$2 TSRMLS_CC); }
|
||||
| T_PRINT expr { zend_do_print(&$$, &$2 TSRMLS_CC); }
|
||||
| function is_reference '(' { zend_do_begin_lambda_function_declaration(&$$, &$1, $2.op_type TSRMLS_CC); }
|
||||
| function is_reference '(' { zend_do_begin_lambda_function_declaration(&$$, &$1, $2.op_type, 0 TSRMLS_CC); }
|
||||
parameter_list ')' lexical_vars '{' inner_statement_list '}' { zend_do_end_function_declaration(&$1 TSRMLS_CC); $$ = $4; }
|
||||
| T_STATIC function is_reference '(' { zend_do_begin_lambda_function_declaration(&$$, &$2, $3.op_type, 1 TSRMLS_CC); }
|
||||
parameter_list ')' lexical_vars '{' inner_statement_list '}' { zend_do_end_function_declaration(&$2 TSRMLS_CC); $$ = $5; }
|
||||
;
|
||||
|
||||
function:
|
||||
|
|
|
@ -4414,7 +4414,7 @@ ZEND_VM_HANDLER(153, ZEND_DECLARE_LAMBDA_FUNCTION, CONST, CONST)
|
|||
zend_error_noreturn(E_ERROR, "Base lambda function for closure not found");
|
||||
}
|
||||
|
||||
zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array TSRMLS_CC);
|
||||
zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array, EG(scope), EG(This) TSRMLS_CC);
|
||||
|
||||
ZEND_VM_NEXT_OPCODE();
|
||||
}
|
||||
|
|
|
@ -2947,7 +2947,7 @@ static int ZEND_FASTCALL ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_CONST_HANDLER(
|
|||
zend_error_noreturn(E_ERROR, "Base lambda function for closure not found");
|
||||
}
|
||||
|
||||
zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array TSRMLS_CC);
|
||||
zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array, EG(scope), EG(This) TSRMLS_CC);
|
||||
|
||||
ZEND_VM_NEXT_OPCODE();
|
||||
}
|
||||
|
|
|
@ -323,7 +323,7 @@ static zval * reflection_instantiate(zend_class_entry *pce, zval *object TSRMLS_
|
|||
}
|
||||
|
||||
static void _const_string(string *str, char *name, zval *value, char *indent TSRMLS_DC);
|
||||
static void _function_string(string *str, zend_function *fptr, zend_class_entry *scope, char *indent TSRMLS_DC);
|
||||
static void _function_string(string *str, zend_function *fptr, zend_class_entry *scope, char* indent TSRMLS_DC);
|
||||
static void _property_string(string *str, zend_property_info *prop, char *prop_name, char* indent TSRMLS_DC);
|
||||
static void _class_string(string *str, zend_class_entry *ce, zval *obj, char *indent TSRMLS_DC);
|
||||
static void _extension_string(string *str, zend_module_entry *module, char *indent TSRMLS_DC);
|
||||
|
@ -1608,6 +1608,44 @@ ZEND_METHOD(reflection_function, isClosure)
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ proto public bool ReflectionFunction::getClosureThis()
|
||||
Returns this pointer bound to closure */
|
||||
ZEND_METHOD(reflection_function, getClosureThis)
|
||||
{
|
||||
reflection_object *intern;
|
||||
zend_function *fptr;
|
||||
zval* closure_this;
|
||||
|
||||
if (zend_parse_parameters_none() == FAILURE) {
|
||||
return;
|
||||
}
|
||||
GET_REFLECTION_OBJECT_PTR(fptr);
|
||||
if (intern->obj) {
|
||||
closure_this = zend_get_closure_this_ptr(intern->obj TSRMLS_CC);
|
||||
if (closure_this) {
|
||||
RETURN_ZVAL(closure_this, 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ proto public mixed ReflectionFunction::getClosure()
|
||||
Returns a dynamically created closure for the function */
|
||||
ZEND_METHOD(reflection_function, getClosure)
|
||||
{
|
||||
reflection_object *intern;
|
||||
zend_function *fptr;
|
||||
|
||||
if (zend_parse_parameters_none() == FAILURE) {
|
||||
return;
|
||||
}
|
||||
GET_REFLECTION_OBJECT_PTR(fptr);
|
||||
|
||||
zend_create_closure(return_value, fptr, NULL, NULL TSRMLS_CC);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
|
||||
/* {{{ proto public bool ReflectionFunction::isInternal()
|
||||
Returns whether this is an internal function */
|
||||
ZEND_METHOD(reflection_function, isInternal)
|
||||
|
@ -2066,8 +2104,8 @@ ZEND_METHOD(reflection_parameter, __construct)
|
|||
&& memcmp(lcname, ZEND_INVOKE_FUNC_NAME, sizeof(ZEND_INVOKE_FUNC_NAME)-1) == 0
|
||||
&& (fptr = zend_get_closure_invoke_method(*classref TSRMLS_CC)) != NULL)
|
||||
{
|
||||
/* nothign to do. don't set is_closure since is the invoke handler,
|
||||
not the closure itself */
|
||||
/* nothing to do. don't set is_closure since is the invoke handler,
|
||||
- not the closure itself */
|
||||
} else if (zend_hash_find(&ce->function_table, lcname, lcname_len + 1, (void **) &fptr) == FAILURE) {
|
||||
efree(lcname);
|
||||
zend_throw_exception_ex(reflection_exception_ptr, 0 TSRMLS_CC,
|
||||
|
@ -2559,6 +2597,41 @@ ZEND_METHOD(reflection_method, __toString)
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ proto public mixed ReflectionMethod::getClosure([mixed object])
|
||||
Invokes the function */
|
||||
ZEND_METHOD(reflection_method, getClosure)
|
||||
{
|
||||
reflection_object *intern;
|
||||
zval *obj;
|
||||
zend_function *mptr;
|
||||
|
||||
METHOD_NOTSTATIC(reflection_method_ptr);
|
||||
GET_REFLECTION_OBJECT_PTR(mptr);
|
||||
|
||||
if (mptr->common.fn_flags & ZEND_ACC_STATIC) {
|
||||
zend_create_closure(return_value, mptr, mptr->common.scope, NULL TSRMLS_CC);
|
||||
} else {
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &obj) == FAILURE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!instanceof_function(Z_OBJCE_P(obj), mptr->common.scope TSRMLS_CC)) {
|
||||
_DO_THROW("Given object is not an instance of the class this method was declared in");
|
||||
/* Returns from this function */
|
||||
}
|
||||
|
||||
/* This is an original closure object and __invoke is to be called. */
|
||||
if (Z_OBJCE_P(obj) == zend_ce_closure && mptr->type == ZEND_INTERNAL_FUNCTION &&
|
||||
(mptr->internal_function.fn_flags & ZEND_ACC_CALL_VIA_HANDLER) != 0)
|
||||
{
|
||||
RETURN_ZVAL(obj, 1, 0);
|
||||
} else {
|
||||
zend_create_closure(return_value, mptr, mptr->common.scope, obj TSRMLS_CC);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ proto public mixed ReflectionMethod::invoke(mixed object, mixed* args)
|
||||
Invokes the method. */
|
||||
ZEND_METHOD(reflection_method, invoke)
|
||||
|
@ -5261,6 +5334,7 @@ static const zend_function_entry reflection_function_abstract_functions[] = {
|
|||
ZEND_ME(reflection_function, isDeprecated, arginfo_reflection__void, 0)
|
||||
ZEND_ME(reflection_function, isInternal, arginfo_reflection__void, 0)
|
||||
ZEND_ME(reflection_function, isUserDefined, arginfo_reflection__void, 0)
|
||||
ZEND_ME(reflection_function, getClosureThis, arginfo_reflection__void, 0)
|
||||
ZEND_ME(reflection_function, getDocComment, arginfo_reflection__void, 0)
|
||||
ZEND_ME(reflection_function, getEndLine, arginfo_reflection__void, 0)
|
||||
ZEND_ME(reflection_function, getExtension, arginfo_reflection__void, 0)
|
||||
|
@ -5285,6 +5359,7 @@ static const zend_function_entry reflection_function_functions[] = {
|
|||
ZEND_ME(reflection_function, isDisabled, arginfo_reflection__void, 0)
|
||||
ZEND_ME(reflection_function, invoke, arginfo_reflection_function_invoke, 0)
|
||||
ZEND_ME(reflection_function, invokeArgs, arginfo_reflection_function_invokeArgs, 0)
|
||||
ZEND_ME(reflection_function, getClosure, arginfo_reflection__void, 0)
|
||||
{NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
|
@ -5313,6 +5388,10 @@ ZEND_BEGIN_ARG_INFO(arginfo_reflection_method_setAccessible, 0)
|
|||
ZEND_ARG_INFO(0, value)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO(arginfo_reflection_method_getClosure, 0)
|
||||
ZEND_ARG_INFO(0, object)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
static const zend_function_entry reflection_method_functions[] = {
|
||||
ZEND_ME(reflection_method, export, arginfo_reflection_method_export, ZEND_ACC_STATIC|ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(reflection_method, __construct, arginfo_reflection_method___construct, 0)
|
||||
|
@ -5325,6 +5404,7 @@ static const zend_function_entry reflection_method_functions[] = {
|
|||
ZEND_ME(reflection_method, isStatic, arginfo_reflection__void, 0)
|
||||
ZEND_ME(reflection_method, isConstructor, arginfo_reflection__void, 0)
|
||||
ZEND_ME(reflection_method, isDestructor, arginfo_reflection__void, 0)
|
||||
ZEND_ME(reflection_method, getClosure, arginfo_reflection_method_getClosure, 0)
|
||||
ZEND_ME(reflection_method, getModifiers, arginfo_reflection__void, 0)
|
||||
ZEND_ME(reflection_method, invoke, arginfo_reflection_method_invoke, 0)
|
||||
ZEND_ME(reflection_method, invokeArgs, arginfo_reflection_method_invokeArgs, 0)
|
||||
|
|
17
ext/reflection/tests/ReflectionFunction_getClosureThis.phpt
Executable file
17
ext/reflection/tests/ReflectionFunction_getClosureThis.phpt
Executable file
|
@ -0,0 +1,17 @@
|
|||
--TEST--
|
||||
Reflection::getClosureThis()
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300) {
|
||||
print 'skip';
|
||||
}
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
$closure = function($param) { return "this is a closure"; };
|
||||
$rf = new ReflectionFunction($closure);
|
||||
var_dump($rf->getClosureThis());
|
||||
echo "Done!\n";
|
||||
--EXPECTF--
|
||||
NULL
|
||||
Done!
|
|
@ -0,0 +1,37 @@
|
|||
--TEST--
|
||||
Test ReflectionFunction::getClosure() function : basic functionality
|
||||
--FILE--
|
||||
<?php
|
||||
/* Prototype : public mixed ReflectionFunction::getClosure()
|
||||
* Description: Returns a dynamically created closure for the function
|
||||
* Source code: ext/reflection/php_reflection.c
|
||||
* Alias to functions:
|
||||
*/
|
||||
|
||||
echo "*** Testing ReflectionFunction::getClosure() : basic functionality ***\n";
|
||||
|
||||
function foo()
|
||||
{
|
||||
var_dump( "Inside foo function" );
|
||||
}
|
||||
|
||||
function bar( $arg )
|
||||
{
|
||||
var_dump( "Arg is " . $arg );
|
||||
}
|
||||
|
||||
$func = new ReflectionFunction( 'foo' );
|
||||
$closure = $func->getClosure();
|
||||
$closure();
|
||||
|
||||
$func = new ReflectionFunction( 'bar' );
|
||||
$closure = $func->getClosure();
|
||||
$closure( 'succeeded' );
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECTF--
|
||||
*** Testing ReflectionFunction::getClosure() : basic functionality ***
|
||||
string(19) "Inside foo function"
|
||||
string(16) "Arg is succeeded"
|
||||
===DONE===
|
|
@ -0,0 +1,27 @@
|
|||
--TEST--
|
||||
Test ReflectionFunction::getClosure() function : error functionality
|
||||
--FILE--
|
||||
<?php
|
||||
/* Prototype : public mixed ReflectionFunction::getClosure()
|
||||
* Description: Returns a dynamically created closure for the function
|
||||
* Source code: ext/reflection/php_reflection.c
|
||||
* Alias to functions:
|
||||
*/
|
||||
|
||||
echo "*** Testing ReflectionFunction::getClosure() : error conditions ***\n";
|
||||
|
||||
function foo()
|
||||
{
|
||||
var_dump( "Inside foo function" );
|
||||
}
|
||||
|
||||
$func = new ReflectionFunction( 'foo' );
|
||||
$closure = $func->getClosure('bar');
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECTF--
|
||||
*** Testing ReflectionFunction::getClosure() : error conditions ***
|
||||
|
||||
Warning: ReflectionFunction::getClosure() expects exactly 0 parameters, 1 given in %s on line %d
|
||||
===DONE===
|
53
ext/reflection/tests/ReflectionMethod_getClosureThis.phpt
Executable file
53
ext/reflection/tests/ReflectionMethod_getClosureThis.phpt
Executable file
|
@ -0,0 +1,53 @@
|
|||
--TEST--
|
||||
Reflection::getClosureThis()
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300) {
|
||||
print 'skip';
|
||||
}
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
class StaticExample
|
||||
{
|
||||
static function foo()
|
||||
{
|
||||
var_dump( "Static Example class, Hello World!" );
|
||||
}
|
||||
}
|
||||
|
||||
class Example
|
||||
{
|
||||
public $bar = 42;
|
||||
public function foo()
|
||||
{
|
||||
var_dump( "Example class, bar: " . $this->bar );
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize classes
|
||||
$class = new ReflectionClass( 'Example' );
|
||||
$staticclass = new ReflectionClass( 'StaticExample' );
|
||||
$object = new Example();
|
||||
|
||||
$method = $staticclass->getMethod( 'foo' );
|
||||
$closure = $method->getClosure();
|
||||
$rf = new ReflectionFunction($closure);
|
||||
|
||||
var_dump($rf->getClosureThis());
|
||||
|
||||
$method = $class->getMethod( 'foo' );
|
||||
|
||||
$closure = $method->getClosure( $object );
|
||||
$rf = new ReflectionFunction($closure);
|
||||
|
||||
var_dump($rf->getClosureThis());
|
||||
|
||||
echo "Done!\n";
|
||||
--EXPECTF--
|
||||
NULL
|
||||
object(Example)#%d (1) {
|
||||
["bar"]=>
|
||||
int(42)
|
||||
}
|
||||
Done!
|
55
ext/reflection/tests/ReflectionMethod_getClosure_basic.phpt
Normal file
55
ext/reflection/tests/ReflectionMethod_getClosure_basic.phpt
Normal file
|
@ -0,0 +1,55 @@
|
|||
--TEST--
|
||||
Test ReflectionMethod::getClosure() function : basic functionality
|
||||
--FILE--
|
||||
<?php
|
||||
/* Prototype : public mixed ReflectionFunction::getClosure()
|
||||
* Description: Returns a dynamically created closure for the method
|
||||
* Source code: ext/reflection/php_reflection.c
|
||||
* Alias to functions:
|
||||
*/
|
||||
|
||||
echo "*** Testing ReflectionMethod::getClosure() : basic functionality ***\n";
|
||||
|
||||
class StaticExample
|
||||
{
|
||||
static function foo()
|
||||
{
|
||||
var_dump( "Static Example class, Hello World!" );
|
||||
}
|
||||
}
|
||||
|
||||
class Example
|
||||
{
|
||||
public $bar = 42;
|
||||
public function foo()
|
||||
{
|
||||
var_dump( "Example class, bar: " . $this->bar );
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize classes
|
||||
$class = new ReflectionClass( 'Example' );
|
||||
$staticclass = new ReflectionClass( 'StaticExample' );
|
||||
$object = new Example();
|
||||
$fakeobj = new StdClass();
|
||||
|
||||
|
||||
$method = $staticclass->getMethod( 'foo' );
|
||||
$closure = $method->getClosure();
|
||||
$closure();
|
||||
|
||||
$method = $class->getMethod( 'foo' );
|
||||
|
||||
$closure = $method->getClosure( $object );
|
||||
$closure();
|
||||
$object->bar = 34;
|
||||
$closure();
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECTF--
|
||||
*** Testing ReflectionMethod::getClosure() : basic functionality ***
|
||||
string(34) "Static Example class, Hello World!"
|
||||
string(22) "Example class, bar: 42"
|
||||
string(22) "Example class, bar: 34"
|
||||
===DONE===
|
73
ext/reflection/tests/ReflectionMethod_getClosure_error.phpt
Normal file
73
ext/reflection/tests/ReflectionMethod_getClosure_error.phpt
Normal file
|
@ -0,0 +1,73 @@
|
|||
--TEST--
|
||||
Test ReflectionMethod::getClosure() function : error functionality
|
||||
--FILE--
|
||||
<?php
|
||||
/* Prototype : public mixed ReflectionFunction::getClosure()
|
||||
* Description: Returns a dynamically created closure for the method
|
||||
* Source code: ext/reflection/php_reflection.c
|
||||
* Alias to functions:
|
||||
*/
|
||||
|
||||
echo "*** Testing ReflectionMethod::getClosure() : error conditions ***\n";
|
||||
|
||||
class StaticExample
|
||||
{
|
||||
static function foo()
|
||||
{
|
||||
var_dump( "Static Example class, Hello World!" );
|
||||
}
|
||||
}
|
||||
|
||||
class Example
|
||||
{
|
||||
public $bar = 42;
|
||||
public function foo()
|
||||
{
|
||||
var_dump( "Example class, bar: " . $this->bar );
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize classes
|
||||
$class = new ReflectionClass( 'Example' );
|
||||
$staticclass = new ReflectionClass( 'StaticExample' );
|
||||
$method = $class->getMethod( 'foo' );
|
||||
$staticmethod = $staticclass->getMethod( 'foo' );
|
||||
$object = new Example();
|
||||
$fakeobj = new StdClass();
|
||||
|
||||
echo "\n-- Testing ReflectionMethod::getClosure() function with more than expected no. of arguments --\n";
|
||||
var_dump( $staticmethod->getClosure( 'foobar' ) );
|
||||
var_dump( $staticmethod->getClosure( 'foo', 'bar' ) );
|
||||
var_dump( $method->getClosure( $object, 'foobar' ) );
|
||||
|
||||
echo "\n-- Testing ReflectionMethod::getClosure() function with Zero arguments --\n";
|
||||
$closure = $method->getClosure();
|
||||
|
||||
echo "\n-- Testing ReflectionMethod::getClosure() function with Zero arguments --\n";
|
||||
try {
|
||||
var_dump( $method->getClosure( $fakeobj ) );
|
||||
} catch( Exception $e ) {
|
||||
var_dump( $e->getMessage() );
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECTF--
|
||||
*** Testing ReflectionMethod::getClosure() : error conditions ***
|
||||
|
||||
-- Testing ReflectionMethod::getClosure() function with more than expected no. of arguments --
|
||||
object(Closure)#%d (0) {
|
||||
}
|
||||
object(Closure)#%d (0) {
|
||||
}
|
||||
|
||||
Warning: ReflectionMethod::getClosure() expects exactly 1 parameter, 2 given in %s on line %d
|
||||
NULL
|
||||
|
||||
-- Testing ReflectionMethod::getClosure() function with Zero arguments --
|
||||
|
||||
Warning: ReflectionMethod::getClosure() expects exactly 1 parameter, 0 given in %s on line %d
|
||||
|
||||
-- Testing ReflectionMethod::getClosure() function with Zero arguments --
|
||||
string(72) "Given object is not an instance of the class this method was declared in"
|
||||
===DONE===
|
25
ext/reflection/tests/closures_003_v1.phpt
Executable file
25
ext/reflection/tests/closures_003_v1.phpt
Executable file
|
@ -0,0 +1,25 @@
|
|||
--TEST--
|
||||
Reflection on closures: Segfaults with getParameters() and getDeclaringFunction()
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$closure = function($a, $b = 0) { };
|
||||
|
||||
$method = new ReflectionFunction ($closure);
|
||||
$params = $method->getParameters ();
|
||||
unset ($method);
|
||||
$method = $params[0]->getDeclaringFunction ();
|
||||
unset ($params);
|
||||
echo $method->getName ()."\n";
|
||||
|
||||
$parameter = new ReflectionParameter ($closure, 'b');
|
||||
$method = $parameter->getDeclaringFunction ();
|
||||
unset ($parameter);
|
||||
echo $method->getName ()."\n";
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECTF--
|
||||
{closure}
|
||||
{closure}
|
||||
===DONE===
|
43
ext/reflection/tests/closures_004.phpt
Normal file
43
ext/reflection/tests/closures_004.phpt
Normal file
|
@ -0,0 +1,43 @@
|
|||
--TEST--
|
||||
Reflection on closures: Segfault with getClosure() on closure itself
|
||||
--FILE--
|
||||
<?php
|
||||
$closure = function() { echo "Invoked!\n"; };
|
||||
|
||||
$method = new ReflectionFunction ($closure);
|
||||
|
||||
$closure2 = $method->getClosure ();
|
||||
|
||||
$closure2 ();
|
||||
$closure2->__invoke ();
|
||||
|
||||
unset ($closure);
|
||||
|
||||
$closure2 ();
|
||||
$closure2->__invoke ();
|
||||
|
||||
$closure = function() { echo "Invoked!\n"; };
|
||||
|
||||
$method = new ReflectionMethod ($closure, '__invoke');
|
||||
$closure2 = $method->getClosure ($closure);
|
||||
|
||||
$closure2 ();
|
||||
$closure2->__invoke ();
|
||||
|
||||
unset ($closure);
|
||||
|
||||
$closure2 ();
|
||||
$closure2->__invoke ();
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECTF--
|
||||
Invoked!
|
||||
Invoked!
|
||||
Invoked!
|
||||
Invoked!
|
||||
Invoked!
|
||||
Invoked!
|
||||
Invoked!
|
||||
Invoked!
|
||||
===DONE===
|
Loading…
Add table
Add a link
Reference in a new issue