Closure::bindTo
(PHP 5 >= 5.4.0, PHP 7, PHP 8)
Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域。
说明
创建并返回一个 匿名函数, 它与当前对象的函数体相同、绑定了同样变量,但可以绑定不同的对象,也可以绑定新的类作用域。
“绑定的对象”决定了函数体中的 $this
的取值,“类作用域”代表一个类型、决定在这个匿名函数中能够调用哪些 private 和 protected 的方法。
也就是说,此时 $this 可以调用的方法,与 newScope
类的成员函数是相同的。
静态闭包不能有绑定的对象(
newThis
参数的值应该设为
null
)不过仍然可以用 bindTo 方法来改变它们的类作用域。
此函数确保对于非静态闭包,拥有绑定实例也意味着被限定作用域,反之亦然。
为此,非静态闭包给定一个 null
实例的作用域可以使其变为静态,
非静态无作用域的闭包给定一个非 null 的实例作用在一个非指定类。
注意:
如果你只是想要复制一个匿名函数,可以用 cloning 代替。
参数
-
newThis
-
绑定给匿名函数的一个对象,或者
null
来取消绑定。 -
newScope
-
关联到匿名函数的类作用域,或者 'static' 保持当前状态。如果是一个对象,则使用这个对象的类型为新的类作用域。 这会决定绑定的对象的 保护、私有成员 方法的可见性。 不允许内部类(的对象)作为参数传递。
返回值
返回新创建的 Closure 对象,
或者失败时返回 null
。
范例
示例 #1 Closure::bindTo() 实例
<?php
class A {
function __construct($val) {
$this->val = $val;
}
function getClosure() {
// 返回绑定到此对象和作用域的闭包
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";
?>
以上例程的输出类似于:
1 2
参见
- 匿名函数
- Closure::bind() - 复制一个闭包,绑定指定的$this对象和类作用域。

User Contributed Notes 8 notes
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();
?>
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>
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
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.
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
?>
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) {
}
*/
?>
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"
}
Closures can rebind their $this variable, but private/protected methods and functions of $this are not accessible to the closures.
<?php
$fn = function(){
return $this->foo;
};
class Bar{
private $foo = 3;
}
$bar = new Bar();
$fn = $fn->bindTo($bar);
echo $fn(); // Fatal error: Cannot access private property Bar::$foo