PHP Conference Fukuoka 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
10 years ago
We can use the concept of bindTo to write a very small Template Engine:#############index.php############<?phpclass 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
12 years ago
You can do pretty Javascript-like things with objects using closure binding:<?phptrait 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
13 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 objectecho $fn1(); // 2echo $fn2(); // 3
up
5
Anonymous
7 years ago
If you want to unbind completely the closure and the scope you need to set both to null:<?phpclass 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
9 years ago
Access private members of parent classes; playing with the scopes:<?PHPclass 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:<?PHPuse 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
13 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
3 years ago
Get all object vars without using Reflection:<?phpdeclare(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