CakeFest 2024: The Official CakePHP Conference

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;

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

Смотрите также

add a note

User Contributed Notes 9 notes

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
41
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
19
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
4
Anonymous
5 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
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
2
luc at s dot illi dot be
7 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
1
malferov at gmail dot com
4 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:

The documentation is about the object of the Closure itself:

<?php

class A {}

$a = new A();

$closure = fn() => null;

$binded = $closure->bindTo($a, $closure,); // Warning: Cannot bind closure to scope of internal class Closure
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"
}
up
-32
anthony bishopric
12 years ago
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
To Top