Closure::bindTo

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

Closure::bindTo β€” Π”ΡƒΠ±Π»ΠΈΡ€ΡƒΠ΅Ρ‚ Π·Π°ΠΌΡ‹ΠΊΠ°Π½ΠΈΠ΅ ΠΈ привязываСт копию замыкания ΠΊ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Ρƒ ΠΈ области видимости класса

ОписаниС

public function 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

Π‘ΠΌΠΎΡ‚Ρ€ΠΈΡ‚Π΅ Ρ‚Π°ΠΊΠΆΠ΅

  • АнонимныС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ
  • Closure::bind() - Π”ΡƒΠ±Π»ΠΈΡ€ΡƒΠ΅Ρ‚ Π·Π°ΠΌΡ‹ΠΊΠ°Π½ΠΈΠ΅ ΠΈ привязываСт копию замыкания ΠΊ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Ρƒ ΠΈ области видимости класса статичСски
οΌ‹Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ

ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΡ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ 8 notes

up
45
Nezar Fadle ΒΆ
11 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
37
tatarynowicz at gmail dot com ΒΆ
13 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 ΒΆ
14 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 ΒΆ
8 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 ΒΆ
10 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 ΒΆ
14 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
1
malferov at gmail dot com ΒΆ
2 years 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 ΒΆ
4 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"
}