PHP Conference Japan 2024

Closure::bindTo

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

Closure::bindTo 使用新的绑定对象和类作用域复制闭包

描述

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

创建并返回一个新的具有与当前闭包相同主体和绑定变量的匿名函数,但可能具有不同的绑定对象和新的类作用域。

“绑定对象”决定函数体中$this的值,而“类作用域”表示一个类,该类决定匿名函数可以访问哪些私有和受保护的成员。具体来说,可见的成员与匿名函数作为给定为newScope参数值的类的成员时的成员相同。

静态闭包不能有任何绑定对象(newThis参数的值应为null),但是此方法仍然可以用来更改其类作用域。

此方法将确保对于非静态闭包,具有绑定实例意味着具有作用域,反之亦然。为此,给定作用域但实例为null的非静态闭包将变为静态,而给定非空实例的非静态非作用域闭包将被作用域到一个未指定的类。

注意:

如果只想复制匿名函数,则可以使用克隆

参数

newThis

要将给定匿名函数绑定到的对象,或用于使闭包解除绑定的null

newScope

要将闭包关联到的类作用域,或“static”以保留当前作用域。如果给定一个对象,则将使用该对象的类型。这决定了绑定对象的受保护和私有方法的可见性。不允许将(内部类的)对象作为此参数传递。

返回值

返回新创建的Closure对象,或在失败时返回null

示例

示例 #1 Closure::bindTo() 示例

<?php

class A
{
private
$val;

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

public 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

参见

添加注释

用户贡献的注释 8 条注释

43
Nezar Fadle
9 年前
我们可以使用 bindTo 的概念编写一个非常小的模板引擎

#############
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>
36
tatarynowicz at gmail dot com
11 年前
您可以使用闭包绑定对对象执行类似 Javascript 的操作

<?php
trait DynamicDefinition {

public function
__call($name, $args) {
if (
is_callable($this->$name)) {
return
call_user_func($this->$name, $args);
}
else {
throw new
\RuntimeException("方法 {$name} 不存在");
}
}

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

class
Foo {
use
DynamicDefinition;
private
$privateValue = '我是私有的';
}

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

// 输出 '我是私有的'
print $foo->bar();

?>
20
safakozpinar at gmail dot com
12 年前
如果您设置“newscope”参数(如手册中所述),则可以访问私有/受保护的成员。

<?php
$fn
= function(){
return ++
$this->foo; // 增加值
};

class
Bar{
private
$foo = 1; // 初始值
}

$bar = new Bar();

$fn1 = $fn->bindTo($bar, 'Bar'); // 指定类名
$fn2 = $fn->bindTo($bar, $bar); // 或对象

echo $fn1(); // 2
echo $fn2(); // 3
5
匿名用户
6 年前
如果您想完全解除闭包和作用域的绑定,则需要将两者都设置为 null

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

/**
* @return array
*/
public function toArray()
{
// 仅公共变量
return (function ($obj) {
return
get_object_vars($obj);
})->
bindTo(null, null)($this);
}
}
?>

在此示例中,仅导出类的公共变量 (foo)。

如果您使用默认作用域 (->bindTo(null)),则还会导出受保护和私有变量 (foo、bar 和 baz)。

很难弄清楚这一点,因为文档中没有提到可以使用 null 作为作用域。
3
luc at s dot illi dot be
8 年前
访问父类的私有成员;使用作用域。
<?PHP
class Grandparents{ private $__status1 = '已婚'; }
class
Parents extends Grandparents{ private $__status2 = '离婚'; }
class
Me extends Parents{ private $__status3 = '单身'; }

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

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

// 测试 1:
$c = $status1_3->bindTo($R = new Me, Parents::class);
#$c(); // 致命错误:无法访问私有属性 Me::$__status3

// 测试 2:
$d = $status1_2->bindTo($R = new Me, Parents::class);
$d();
var_dump($R);
/*
object(Me)#5 (4) {
["__status3":"Me":private]=>
string(6) "单身"
["__status2":"Parents":private]=>
string(5) "幸福"
["__status1":"Grandparents":private]=>
string(7) "已婚"
["__status1"]=>
string(5) "幸福"
}
*/

// 测试 3:
$e = $status1_3->bindTo($R = new Me, Grandparents::class);
#$e(); // 致命错误:无法访问私有属性 Me::$__status3

// 测试 4:
$f = $status1_2->bindTo($R = new Me, Grandparents::class);
$f();
var_dump($R);
/*
object(Me)#9 (4) {
["__status3":"Me":private]=>
string(6) "单身"
["__status2":"Parents":private]=>
string(8) "离婚"
["__status1":"Grandparents":private]=>
string(5) "幸福"
["__status2"]=>
string(5) "幸福"
}
*/
?>

清除堆栈跟踪
<?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) {
}
*/
?>
8
amica at php-resource dot de
12 年前
有了可重新绑定的 $this,就可以做一些坏事

<?php
A {
private
$a = 12;
private function
getA () {
return
$this->a;
}
}
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
?>
0
malferov at gmail dot com
1 年前
如果您,像我一样,没有立即理解文档中关于 'newScope' 参数的“(一个)内部类的对象”到底指的是什么

文档中所说的内部类,指的是任何 PHP 内部类,例如 'stdClass'、'Closure'、'WeakMap' 等

<?php

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

$binded = $closure->bindTo($a, 'stdClass',); // 无法将闭包绑定到内部类 stdClass 的作用域
$binded = $closure->bindTo($a, $closure,); // 警告:无法将闭包绑定到内部类 Closure 的作用域等。
0
Olexandr Kalaidzhy
2 年前
不使用 Reflection 获取所有对象变量

<?php

declare(strict_types=1);

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('参数应为对象,给定的是 "%s"。', 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));

?>

输出结果

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