Closure::bindTo

(PHP 5 >= 5.4.0, PHP 7, PHP 8)

Closure::bindTo Duplicates the closure with a new bound object and class scope

Description

public Closure::bindTo(?object $newThis, object|string|null $newScope = "static"): ?Closure

Create and return a new anonymous function with the same body and bound variables as this one, but possibly with a different bound object and a new class scope.

The “bound object” determines the value $this will have in the function body and the “class scope” represents a class which determines which private and protected members the anonymous function will be able to access. Namely, the members that will be visible are the same as if the anonymous function were a method of the class given as value of the newScope parameter.

Static closures cannot have any bound object (the value of the parameter newThis should be null), but this method can nevertheless be used to change their class scope.

This method will ensure that for a non-static closure, having a bound instance will imply being scoped and vice-versa. To this end, non-static closures that are given a scope but a null instance are made static and non-static non-scoped closures that are given a non-null instance are scoped to an unspecified class.

Note:

If you only want to duplicate the anonymous functions, you can use cloning instead.

Parameters

newThis

The object to which the given anonymous function should be bound, or null for the closure to be unbound.

newScope

The class scope to which the closure is to be associated, or 'static' to keep the current one. If an object is given, the type of the object will be used instead. This determines the visibility of protected and private methods of the bound object. It is not allowed to pass (an object of) an internal class as this parameter.

Return Values

Returns the newly created Closure object or null on failure.

Examples

Example #1 Closure::bindTo() example

<?php

class A
{
private
$val;

public function
__construct($val)
{
$this->val = $val;
}

public function
getClosure()
{
// Returns closure bound to this object and scope
return function() {
return
$this->val;
};
}
}

$ob1 = new A(1);
$ob2 = new A(2);

$cl = $ob1->getClosure();
echo
$cl(), "\n";

$cl = $cl->bindTo($ob2);
echo
$cl(), "\n";

?>

The above example will output something similar to:

1
2

See Also

add a note

User Contributed Notes 8 notes

up
43
Nezar Fadle
9 years ago
We can use the concept of bindTo to write a very small Template Engine:

#############
index.php
############

<?php

class Article{
private
$title = "This is an article";
}

class
Post{
private
$title = "This is a post";
}

class
Template{

function
render($context, $tpl){

$closure = function($tpl){
ob_start();
include
$tpl;
return
ob_end_flush();
};

$closure = $closure->bindTo($context, $context);
$closure($tpl);

}

}

$art = new Article();
$post = new Post();
$template = new Template();

$template->render($art, 'tpl.php');
$template->render($post, 'tpl.php');
?>

#############
tpl.php
############
<h1><?php echo $this->title;?></h1>
up
36
tatarynowicz at gmail dot com
11 years ago
You can do pretty Javascript-like things with objects using closure binding:

<?php
trait DynamicDefinition {

public function
__call($name, $args) {
if (
is_callable($this->$name)) {
return
call_user_func($this->$name, $args);
}
else {
throw new
\RuntimeException("Method {$name} does not exist");
}
}

public function
__set($name, $value) {
$this->$name = is_callable($value)?
$value->bindTo($this, $this):
$value;
}
}

class
Foo {
use
DynamicDefinition;
private
$privateValue = 'I am private';
}

$foo = new Foo;
$foo->bar = function() {
return
$this->privateValue;
};

// prints 'I am private'
print $foo->bar();

?>
up
20
safakozpinar at gmail dot com
12 years ago
Private/protected members are accessible if you set the "newscope" argument (as the manual says).

<?php
$fn
= function(){
return ++
$this->foo; // increase the value
};

class
Bar{
private
$foo = 1; // initial value
}

$bar = new Bar();

$fn1 = $fn->bindTo($bar, 'Bar'); // specify class name
$fn2 = $fn->bindTo($bar, $bar); // or object

echo $fn1(); // 2
echo $fn2(); // 3
up
5
Anonymous
6 years ago
If you want to unbind completely the closure and the scope you need to set both to null:

<?php
class MyClass
{
public
$foo = 'a';
protected
$bar = 'b';
private
$baz = 'c';

/**
* @return array
*/
public function toArray()
{
// Only public variables
return (function ($obj) {
return
get_object_vars($obj);
})->
bindTo(null, null)($this);
}
}
?>

In this example, only the public variables of the class are exported (foo).

If you use the default scope (->bindTo(null)) also protected and private variables are exported (foo, bar and baz).

It was hard to figure it out because there is nowhere mentioned in the documentation that you can use null as a scope.
up
3
luc at s dot illi dot be
8 years ago
Access private members of parent classes; playing with the scopes:
<?PHP
class Grandparents{ private $__status1 = 'married'; }
class
Parents extends Grandparents{ private $__status2 = 'divorced'; }
class
Me extends Parents{ private $__status3 = 'single'; }

$status1_3 = function()
{
$this->__status1 = 'happy';
$this->__status2 = 'happy';
$this->__status3 = 'happy';
};

$status1_2 = function()
{
$this->__status1 = 'happy';
$this->__status2 = 'happy';
};

// test 1:
$c = $status1_3->bindTo($R = new Me, Parents::class);
#$c(); // Fatal: Cannot access private property Me::$__status3

// test 2:
$d = $status1_2->bindTo($R = new Me, Parents::class);
$d();
var_dump($R);
/*
object(Me)#5 (4) {
["__status3":"Me":private]=>
string(6) "single"
["__status2":"Parents":private]=>
string(5) "happy"
["__status1":"Grandparents":private]=>
string(7) "married"
["__status1"]=>
string(5) "happy"
}
*/

// test 3:
$e = $status1_3->bindTo($R = new Me, Grandparents::class);
#$e(); // Fatal: Cannot access private property Me::$__status3

// test 4:
$f = $status1_2->bindTo($R = new Me, Grandparents::class);
$f();
var_dump($R);
/*
object(Me)#9 (4) {
["__status3":"Me":private]=>
string(6) "single"
["__status2":"Parents":private]=>
string(8) "divorced"
["__status1":"Grandparents":private]=>
string(5) "happy"
["__status2"]=>
string(5) "happy"
}
*/
?>

Clear the stack trace:
<?PHP
use Exception;
use
ReflectionException;

$c = function()
{
$this->trace = [];
};

$c = $c->bindTo($R = new ReflectionException, Exception::class);
$c();

try
{
throw
$R;
}
catch(
ReflectionException $R)
{
var_dump($R->getTrace());
}
/*
array(0) {
}
*/
?>
up
8
amica at php-resource dot de
12 years ago
With rebindable $this at hand it's possible to do evil stuff:

<?php
class A {
private
$a = 12;
private function
getA () {
return
$this->a;
}
}
class
B {
private
$b = 34;
private function
getB () {
return
$this->b;
}
}
$a = new A();
$b = new B();
$c = function () {
if (
property_exists($this, "a") && method_exists($this, "getA")) {
$this->a++;
return
$this->getA();
}
if (
property_exists($this, "b") && method_exists($this, "getB")) {
$this->b++;
return
$this->getB();
}
};
$ca = $c->bindTo($a, $a);
$cb = $c->bindTo($b, $b);
echo
$ca(), "\n"; // => 13
echo $cb(), "\n"; // => 35
?>
up
0
malferov at gmail dot com
11 months ago
If you, like me, did not immediately understand what exactly "(an object of) an internal class" in the documentation about the 'newScope' parameter:

By an internal class, the documentation means any internal PHP class such as 'stdClass', 'Closure', 'WeakMap', and etc:

<?php

class A {}
$a = new A();
$closure = fn() => null;

$binded = $closure->bindTo($a, 'stdClass',); // Cannot bind closure to scope of internal class stdClass
$binded = $closure->bindTo($a, $closure,); // Warning: Cannot bind closure to scope of internal class Closure etc.
up
0
Olexandr Kalaidzhy
2 years ago
Get all object vars without using Reflection:

<?php

declare(strict_types=1);

class
A
{
private
$foo = 'foo';
protected
$bar = 'bar';
public
$buz = 'buz';
}

function
get_object_vars_all($object): array
{
if (!
\is_object($object)) {
throw new
\InvalidArgumentException(sprintf('The argument should be an object, "%s" given.', get_debug_type($object)));
}

$closure = function () {
return
get_object_vars($this);
};

return
$closure->bindTo($object, $object)();
}

$a = new A();

var_dump(get_object_vars($a));
var_dump(get_object_vars_all($a));

?>

The output:

array(1) {
["buz"]=>
string(3) "buz"
}
array(3) {
["foo"]=>
string(3) "foo"
["bar"]=>
string(3) "bar"
["buz"]=>
string(3) "buz"
}
To Top