--TEST--
-Closure 036: Rebinding closures
+Closure 036: Rebinding closures, keep calling scope
--FILE--
<?php
$ca = $a->getIncrementor();
$cb = $ca->bindTo($b);
-$cb2 = Closure::bind($b, $ca);
+$cb2 = Closure::bind($ca, $b);
var_dump($ca());
var_dump($cb());
--- /dev/null
+--TEST--
+Closure 038: Rebinding closures, change scope, different runtime type
+--FILE--
+<?php
+
+class A {
+ private $x;
+
+ public function __construct($v) {
+ $this->x = $v;
+ }
+
+ public function getIncrementor() {
+ return function() { return ++$this->x; };
+ }
+}
+class B extends A {
+ private $x;
+ public function __construct($v) {
+ parent::__construct($v);
+ $this->x = $v*2;
+ }
+}
+
+$a = new A(0);
+$b = new B(10);
+
+$ca = $a->getIncrementor();
+var_dump($ca());
+
+echo "Testing with scope given as object", "\n";
+
+$cb = $ca->bindTo($b, $b);
+$cb2 = Closure::bind($ca, $b, $b);
+var_dump($cb());
+var_dump($cb2());
+
+echo "Testing with scope as string", "\n";
+
+$cb = $ca->bindTo($b, 'B');
+$cb2 = Closure::bind($ca, $b, 'B');
+var_dump($cb());
+var_dump($cb2());
+
+$cb = $ca->bindTo($b, NULL);
+var_dump($cb());
+
+?>
+--EXPECTF--
+int(1)
+Testing with scope given as object
+int(21)
+int(22)
+Testing with scope as string
+int(23)
+int(24)
+
+Fatal error: Cannot access private property B::$x in %s on line %d
--- /dev/null
+--TEST--
+Closure 039: Rebinding closures, change scope, same runtime type
+--FILE--
+<?php
+
+class A {
+ private $x;
+
+ public function __construct($v) {
+ $this->x = $v;
+ }
+
+ public function getIncrementor() {
+ return function() { return ++$this->x; };
+ }
+}
+class B extends A {
+ private $x;
+ public function __construct($v) {
+ parent::__construct($v);
+ $this->x = $v*2;
+ }
+}
+
+$a = new B(-5);
+$b = new B(10);
+
+$ca = $a->getIncrementor();
+var_dump($ca());
+
+echo "Testing with scope given as object", "\n";
+
+$cb = $ca->bindTo($b, $b);
+$cb2 = Closure::bind($ca, $b, $b);
+var_dump($cb());
+var_dump($cb2());
+
+echo "Testing with scope as string", "\n";
+
+$cb = $ca->bindTo($b, 'B');
+$cb2 = Closure::bind($ca, $b, 'B');
+var_dump($cb());
+var_dump($cb2());
+
+$cb = $ca->bindTo($b, NULL);
+var_dump($cb());
+
+?>
+--EXPECTF--
+int(-4)
+Testing with scope given as object
+int(21)
+int(22)
+Testing with scope as string
+int(23)
+int(24)
+
+Fatal error: Cannot access private property B::$x in %s on line %d
--- /dev/null
+--TEST--
+Closure 040: Rebinding closures, bad arguments
+--FILE--
+<?php
+
+class A {
+ private $x;
+ private static $xs = 10;
+
+ public function __construct($v) {
+ $this->x = $v;
+ }
+
+ public function getIncrementor() {
+ return function() { return ++$this->x; };
+ }
+ public function getStaticIncrementor() {
+ return static function() { return ++static::$xs; };
+ }
+}
+
+$a = new A(20);
+
+$ca = $a->getIncrementor();
+$cas = $a->getStaticIncrementor();
+
+$ca->bindTo($a, array());
+$ca->bindTo(array(), 'A');
+$ca->bindTo($a, array(), "");
+$ca->bindTo();
+$cas->bindTo($a, 'A');
+
+?>
+--EXPECTF--
+Notice: Array to string conversion in %s on line %d
+
+Warning: Class 'Array' not found in %s on line %d
+
+Warning: Closure::bindTo() expects parameter 1 to be object, array given in %s on line 25
+
+Warning: Closure::bindTo() expects at most 2 parameters, 3 given in %s on line %d
+
+Warning: Closure::bindTo() expects at least 1 parameter, 0 given in %s on line %d
+
+Warning: Cannot bind an instance to a static closure in %s on line %d
--- /dev/null
+--TEST--
+Closure 041: Rebinding: preservation of previous scope when not given as arg unless impossible
+--FILE--
+<?php
+
+/* It's impossible to preserve the previous scope when doing so would break
+ * the invariants that, for non-static closures, having a scope is equivalent
+ * to having a bound instance. */
+
+$staticUnscoped = static function () {
+ echo "scoped to A: "; var_dump(isset(A::$priv));
+ echo "bound: ", isset($this)?get_class($this):"no";
+};
+
+$nonstaticUnscoped = function () {
+ echo "scoped to A: "; var_dump(isset(A::$priv));
+ echo "bound: ", isset($this)?get_class($this):"no";
+};
+
+class A {
+ private static $priv = 7;
+ function getClosure() {
+ return function () {
+ echo "scoped to A: "; var_dump(isset(A::$priv));
+ echo "bound: ", isset($this)?get_class($this):"no";
+ };
+ }
+ function getStaticClosure() {
+ return static function () {
+ echo "scoped to A: "; var_dump(isset(A::$priv));
+ echo "bound: ", isset($this)?get_class($this):"no";
+ };
+ }
+}
+class B extends A {}
+
+$a = new A();
+$staticScoped = $a->getStaticClosure();
+$nonstaticScoped = $a->getClosure();
+
+echo "Before binding", "\n";
+$staticUnscoped(); echo "\n";
+$nonstaticUnscoped(); echo "\n";
+$staticScoped(); echo "\n";
+$nonstaticScoped(); echo "\n";
+
+echo "After binding, no instance", "\n";
+$d = $staticUnscoped->bindTo(null); $d(); echo "\n";
+$d = $nonstaticUnscoped->bindTo(null); $d(); echo "\n";
+$d = $staticScoped->bindTo(null); $d(); echo "\n";
+$d = $nonstaticScoped->bindTo(null); $d(); echo "\n";
+//$d should have been turned to static
+$d->bindTo($d);
+
+echo "After binding, with same-class instance for the bound ones", "\n";
+$d = $staticUnscoped->bindTo(new A); $d(); echo "\n";
+$d = $nonstaticUnscoped->bindTo(new A); $d(); echo " (should be scoped to dummy class)\n";
+$d = $staticScoped->bindTo(new A); $d(); echo "\n";
+$d = $nonstaticScoped->bindTo(new A); $d(); echo "\n";
+
+echo "After binding, with different instance for the bound ones", "\n";
+$d = $nonstaticUnscoped->bindTo(new B); $d(); echo " (should be scoped to dummy class)\n";
+$d = $nonstaticScoped->bindTo(new B); $d(); echo "\n";
+
+echo "Done.\n";
+
+--EXPECTF--
+Before binding
+scoped to A: bool(false)
+bound: no
+scoped to A: bool(false)
+bound: no
+scoped to A: bool(true)
+bound: no
+scoped to A: bool(true)
+bound: A
+After binding, no instance
+scoped to A: bool(false)
+bound: no
+scoped to A: bool(false)
+bound: no
+scoped to A: bool(true)
+bound: no
+scoped to A: bool(true)
+bound: no
+
+Warning: Cannot bind an instance to a static closure in %s on line %d
+After binding, with same-class instance for the bound ones
+
+Warning: Cannot bind an instance to a static closure in %s on line %d
+scoped to A: bool(false)
+bound: no
+scoped to A: bool(false)
+bound: A (should be scoped to dummy class)
+
+Warning: Cannot bind an instance to a static closure in %s on line %d
+scoped to A: bool(true)
+bound: no
+scoped to A: bool(true)
+bound: A
+After binding, with different instance for the bound ones
+scoped to A: bool(false)
+bound: B (should be scoped to dummy class)
+scoped to A: bool(true)
+bound: B
+Done.
\ No newline at end of file
--- /dev/null
+--TEST--
+Closure 042: Binding an instance to a non-scoped non-static closures gives it a dummy scope
+--SKIPIF--
+<?php if(!extension_loaded("reflection")) print "skip no reflection"; ?>
+--FILE--
+<?php
+
+$c = function() { var_dump($this); };
+$d = $c->bindTo(new stdClass);
+$d();
+$rm = new ReflectionFunction($d);
+var_dump($rm->getClosureScopeClass()->name); //dummy sope is Closure
+
+//should have the same effect
+$d = $c->bindTo(new stdClass, NULL);
+$d();
+$rm = new ReflectionFunction($d);
+var_dump($rm->getClosureScopeClass()->name); //dummy sope is Closure
+
+echo "Done.\n";
+
+--EXPECTF--
+object(stdClass)#%d (0) {
+}
+string(7) "Closure"
+object(stdClass)#%d (0) {
+}
+string(7) "Closure"
+Done.
--- /dev/null
+--TEST--
+Closure 043: Scope/bounding combination invariants; static closures
+--FILE--
+<?php
+/* Whether it's scoped or not, a static closure cannot have
+ * a bound instance. It should also not be automatically converted
+ * to a non-static instance when attempting to bind one */
+
+$staticUnscoped = static function () { var_dump(isset(A::$priv)); var_dump(isset($this)); };
+
+class A {
+ private static $priv = 7;
+ static function getStaticClosure() {
+ return static function() { var_dump(isset(A::$priv)); var_dump(isset($this)); };
+ }
+}
+
+$staticScoped = A::getStaticClosure();
+
+echo "Before binding", "\n";
+$staticUnscoped(); echo "\n";
+$staticScoped(); echo "\n";
+
+echo "After binding, null scope, no instance", "\n";
+$d = $staticUnscoped->bindTo(null, null); $d(); echo "\n";
+$d = $staticScoped->bindTo(null, null); $d(); echo "\n";
+
+echo "After binding, null scope, with instance", "\n";
+$d = $staticUnscoped->bindTo(new A, null); $d(); echo "\n";
+$d = $staticScoped->bindTo(new A, null); $d(); echo "\n";
+
+echo "After binding, with scope, no instance", "\n";
+$d = $staticUnscoped->bindTo(null, 'A'); $d(); echo "\n";
+$d = $staticScoped->bindTo(null, 'A'); $d(); echo "\n";
+
+echo "After binding, with scope, with instance", "\n";
+$d = $staticUnscoped->bindTo(new A, 'A'); $d(); echo "\n";
+$d = $staticScoped->bindTo(new A, 'A'); $d(); echo "\n";
+
+echo "Done.\n";
+
+--EXPECTF--
+Before binding
+bool(false)
+bool(false)
+
+bool(true)
+bool(false)
+
+After binding, null scope, no instance
+bool(false)
+bool(false)
+
+bool(false)
+bool(false)
+
+After binding, null scope, with instance
+
+Warning: Cannot bind an instance to a static closure in %s on line %d
+bool(false)
+bool(false)
+
+
+Warning: Cannot bind an instance to a static closure in %s on line %d
+bool(false)
+bool(false)
+
+After binding, with scope, no instance
+bool(true)
+bool(false)
+
+bool(true)
+bool(false)
+
+After binding, with scope, with instance
+
+Warning: Cannot bind an instance to a static closure in %s on line %d
+bool(true)
+bool(false)
+
+
+Warning: Cannot bind an instance to a static closure in %s on line %d
+bool(true)
+bool(false)
+
+Done.
--- /dev/null
+--TEST--
+Closure 044: Scope/bounding combination invariants; non static closures
+--FILE--
+<?php
+/* A non-static closure has a bound instance if it has a scope
+ * and does't have an instance if it has no scope */
+
+$nonstaticUnscoped = function () { var_dump(isset(A::$priv)); var_dump(isset($this)); };
+
+class A {
+ private static $priv = 7;
+ function getClosure() {
+ return function() { var_dump(isset(A::$priv)); var_dump(isset($this)); };
+ }
+}
+
+$a = new A();
+$nonstaticScoped = $a->getClosure();
+
+echo "Before binding", "\n";
+$nonstaticUnscoped(); echo "\n";
+$nonstaticScoped(); echo "\n";
+
+echo "After binding, null scope, no instance", "\n";
+$d = $nonstaticUnscoped->bindTo(null, null); $d(); echo "\n";
+$d = $nonstaticScoped->bindTo(null, null); $d(); echo "\n";
+
+echo "After binding, null scope, with instance", "\n";
+$d = $nonstaticUnscoped->bindTo(new A, null); $d(); echo "\n";
+$d = $nonstaticScoped->bindTo(new A, null); $d(); echo "\n";
+
+echo "After binding, with scope, no instance", "\n";
+$d = $nonstaticUnscoped->bindTo(null, 'A'); $d(); echo "\n";
+$d = $nonstaticScoped->bindTo(null, 'A'); $d(); echo "\n";
+
+echo "After binding, with scope, with instance", "\n";
+$d = $nonstaticUnscoped->bindTo(new A, 'A'); $d(); echo "\n";
+$d = $nonstaticScoped->bindTo(new A, 'A'); $d(); echo "\n";
+
+echo "Done.\n";
+
+--EXPECTF--
+Before binding
+bool(false)
+bool(false)
+
+bool(true)
+bool(true)
+
+After binding, null scope, no instance
+bool(false)
+bool(false)
+
+bool(false)
+bool(false)
+
+After binding, null scope, with instance
+bool(false)
+bool(true)
+
+bool(false)
+bool(true)
+
+After binding, with scope, no instance
+bool(true)
+bool(false)
+
+bool(true)
+bool(false)
+
+After binding, with scope, with instance
+bool(true)
+bool(true)
+
+bool(true)
+bool(true)
+
+Done.
--- /dev/null
+--TEST--
+Closure 045: Closures created in static methods are static, even without the keyword
+--FILE--
+<?php
+
+class A {
+static function foo() {
+ return function () {};
+}
+}
+
+$a = A::foo();
+$a->bindTo(new A);
+
+echo "Done.\n";
+
+--EXPECTF--
+Warning: Cannot bind an instance to a static closure in %s on line %d
+Done.
--- /dev/null
+--TEST--
+Closure 046: Rebinding: preservation of previous scope when "static" given as scope arg (same as closure #041)
+--FILE--
+<?php
+
+/* It's impossible to preserve the previous scope when doing so would break
+ * the invariants that, for non-static closures, having a scope is equivalent
+ * to having a bound instance. */
+
+$nonstaticUnscoped = function () { var_dump(isset(A::$priv)); var_dump(isset($this)); };
+
+class A {
+ private static $priv = 7;
+ function getClosure() {
+ return function() { var_dump(isset(A::$priv)); var_dump(isset($this)); };
+ }
+}
+class B extends A {}
+
+$a = new A();
+$nonstaticScoped = $a->getClosure();
+
+echo "Before binding", "\n";
+$nonstaticUnscoped(); echo "\n";
+$nonstaticScoped(); echo "\n";
+
+echo "After binding, no instance", "\n";
+$d = $nonstaticUnscoped->bindTo(null, "static"); $d(); echo "\n";
+$d = $nonstaticScoped->bindTo(null, "static"); $d(); echo "\n";
+//$d should have been turned to static
+$d->bindTo($d);
+
+echo "After binding, with same-class instance for the bound one", "\n";
+$d = $nonstaticUnscoped->bindTo(new A, "static"); $d(); echo "\n";
+$d = $nonstaticScoped->bindTo(new A, "static"); $d(); echo "\n";
+
+echo "After binding, with different instance for the bound one", "\n";
+$d = $nonstaticScoped->bindTo(new B, "static"); $d(); echo "\n";
+
+echo "Done.\n";
+
+--EXPECTF--
+Before binding
+bool(false)
+bool(false)
+
+bool(true)
+bool(true)
+
+After binding, no instance
+bool(false)
+bool(false)
+
+bool(true)
+bool(false)
+
+
+Warning: Cannot bind an instance to a static closure in %s on line %d
+After binding, with same-class instance for the bound one
+bool(false)
+bool(true)
+
+bool(true)
+bool(true)
+
+After binding, with different instance for the bound one
+bool(true)
+bool(true)
+
+Done.
}
/* }}} */
-/* {{{ proto Closure Closure::bindTo(object $to)
- Bind a closure to another object */
-ZEND_METHOD(Closure, bindTo) /* {{{ */
+/* {{{ proto Closure Closure::bind(Closure $old, object $to [, mixed $scope = "static" ] )
+ Create a closure from another one and bind to another object and scope */
+ZEND_METHOD(Closure, bind) /* {{{ */
{
- zval *newthis;
- zend_closure *closure = (zend_closure *)zend_object_store_get_object(getThis() TSRMLS_CC);
+ zval *newthis, *zclosure, *scope_arg = NULL;
+ zend_closure *closure;
+ zend_class_entry *ce, **ce_p;
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o!", &newthis) == FAILURE) {
+ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oo!|z", &zclosure, zend_ce_closure, &newthis, &scope_arg) == 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;
+ closure = (zend_closure *)zend_object_store_get_object(zclosure TSRMLS_CC);
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o!O", &newthis, &zclosure, zend_ce_closure) == FAILURE) {
- RETURN_NULL();
+ if ((newthis != NULL) && (closure->func.common.fn_flags & ZEND_ACC_STATIC)) {
+ zend_error(E_WARNING, "Cannot bind an instance to a static closure");
}
- closure = (zend_closure *)zend_object_store_get_object(zclosure TSRMLS_CC);
+ if (scope_arg != NULL) { /* scope argument was given */
+ if (IS_ZEND_STD_OBJECT(*scope_arg)) {
+ ce = Z_OBJCE_P(scope_arg);
+ } else if (Z_TYPE_P(scope_arg) == IS_NULL) {
+ ce = NULL;
+ } else {
+ char *class_name;
+ int class_name_len;
+ zval tmp_zval;
+ INIT_ZVAL(tmp_zval);
+
+ if (Z_TYPE_P(scope_arg) == IS_STRING) {
+ class_name = Z_STRVAL_P(scope_arg);
+ class_name_len = Z_STRLEN_P(scope_arg);
+ } else {
+ tmp_zval = *scope_arg;
+ zval_copy_ctor(&tmp_zval);
+ convert_to_string(&tmp_zval);
+ class_name = Z_STRVAL(tmp_zval);
+ class_name_len = Z_STRLEN(tmp_zval);
+ }
+
+ if ((class_name_len == sizeof("static") - 1) &&
+ (memcmp("static", class_name, sizeof("static") - 1) == 0)) {
+ ce = closure->func.common.scope;
+ }
+ else if (zend_lookup_class_ex(class_name, class_name_len, NULL, 1, &ce_p TSRMLS_CC) == FAILURE) {
+ zend_error(E_WARNING, "Class '%s' not found", class_name);
+ zval_dtor(&tmp_zval);
+ RETURN_NULL();
+ } else {
+ ce = *ce_p;
+ }
+ zval_dtor(&tmp_zval);
+ }
+ } else { /* scope argument not given; do not change the scope by default */
+ ce = closure->func.common.scope;
+ }
- zend_create_closure(return_value, &closure->func, newthis?Z_OBJCE_P(newthis):NULL, newthis TSRMLS_CC);
+ zend_create_closure(return_value, &closure->func, ce, 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");
}
/* }}} */
-ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_bindto, 0, 0, 0)
+ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_bindto, 0, 0, 1)
ZEND_ARG_INFO(0, newthis)
+ ZEND_ARG_INFO(0, newscope)
ZEND_END_ARG_INFO()
-ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_bind, 0, 0, 0)
- ZEND_ARG_INFO(0, newthis)
+ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_bind, 0, 0, 2)
ZEND_ARG_INFO(0, closure)
+ ZEND_ARG_INFO(0, newthis)
+ ZEND_ARG_INFO(0, newscope)
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)
+ ZEND_MALIAS(Closure, bindTo, bind, arginfo_closure_bindto, ZEND_ACC_PUBLIC)
{NULL, NULL, NULL}
};
closure->func = *func;
closure->func.common.prototype = NULL;
+ if ((scope == NULL) && (this_ptr != NULL)) {
+ /* use dummy scope if we're binding an object without specifying a scope */
+ /* maybe it would be better to create one for this purpose */
+ scope = zend_ce_closure;
+ }
+
if (closure->func.type == ZEND_USER_FUNCTION) {
if (closure->func.op_array.static_variables) {
HashTable *static_variables = closure->func.op_array.static_variables;
}
}
+ /* Invariants:
+ * If the closure is unscoped, it has no bound object.
+ * The the closure is scoped, it's either static or it's bound */
closure->func.common.scope = scope;
if (scope) {
closure->func.common.fn_flags |= ZEND_ACC_PUBLIC;
}
/* }}} */
+/* {{{ proto public ReflectionClass ReflectionFunction::getClosureScopeClass()
+ Returns the scope associated to the closure */
+ZEND_METHOD(reflection_function, getClosureScopeClass)
+{
+ reflection_object *intern;
+ zend_function *fptr;
+ const zend_function *closure_func;
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+ GET_REFLECTION_OBJECT_PTR(fptr);
+ if (intern->obj) {
+ closure_func = zend_get_closure_method_def(intern->obj TSRMLS_CC);
+ if (closure_func && closure_func->common.scope) {
+ zend_reflection_class_factory(closure_func->common.scope, return_value TSRMLS_CC);
+ }
+ }
+}
+/* }}} */
+
/* {{{ proto public mixed ReflectionFunction::getClosure()
Returns a dynamically created closure for the function */
ZEND_METHOD(reflection_function, getClosure)
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, getClosureScopeClass, 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)
--- /dev/null
+--TEST--
+Reflection::getClosureScopeClass()
+--SKIPIF--
+<?php
+if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50399) {
+ print 'skip';
+}
+?>
+--FILE--
+<?php
+$closure = function($param) { return "this is a closure"; };
+$rf = new ReflectionFunction($closure);
+var_dump($rf->getClosureScopeClass());
+
+Class A {
+ public static function getClosure() {
+ return function($param) { return "this is a closure"; };
+ }
+}
+
+$closure = A::getClosure();
+$rf = new ReflectionFunction($closure);
+var_dump($rf->getClosureScopeClass());
+echo "Done!\n";
+--EXPECTF--
+NULL
+object(ReflectionClass)#%d (1) {
+ ["name"]=>
+ string(1) "A"
+}
+Done!