PHP Conference Nagoya 2025

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', чтобы замыкание сохранило текущую область видимости. При передаче объекта как аргумента: роль названия класса будет играть тип объекта. Параметр определяет видимость защищённых и закрытых методов связанного объекта. Нельзя передавать в параметр название или экземпляр объекта внутреннего PHP-класса.

Возвращаемые значения

Метод возвращает новый объект замыкания 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 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
1 year 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