PHP 8.4.1 Released!

Closure::bindTo

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

Closure::bindTo Duplicar el cierre con un objeto vinculado y ámbito de clase nuevos

Descripción

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

Crea y devuelve una nueva función anónima con el cuerpo y variables vinculadas como ésta, pero posiblemente con un objeto vinculado diferente y un nuevo ámbito de clase.

El "objeto vinculado" determina el valor que $this tendrá en el cuerpo de la función, y el "ámbito de clase" representa una clase que determina los miembros privados y protegidos a los que será capaz de acceder la función anónima. Concretamente, los miembros que serán visibles son los mismos que si la función anónima fuese un método de la clase dada como valor del parámetro newScope.

Los cierres estáticos no pueden tener ningún objeto vinculado (el valor del parámetro newThis debería ser null), pero esta función puede, no obstante, usarse para cambiar su ámbito de clase.

Esta función se asegurará de que a un cierre no estático que tenga una instancia vinculada se le aplique un ámbito y viceversa. En este punto, los cierres no estáticos que le son dados un ámbito, excepto una instancia null, son hechos estáticos, y los cierres no estáticos y sin ámbito que le son dados una instancia no nula se les aplica un ámbito de clase no especificado.

Nota:

Si solamente se quieren duplicar las funciones anónimas, se puede usar cloning en su lugar.

Parámetros

newThis

El objeto al que la función anónima dada debería ser vinculado, o null para que el cierre sea desvinculado.

newScope

El ámbito de la clase a la que se va a asociar el cierre, o 'static' para mantener el actual. Si se proporciona un objeto, el tipo del mismo se usará en su lugar. Esto determina la visibilidad de métodos protegidos y privados del objeto vinculado. No se permite pasar (un objeto de) una clase interna a este parémtro.

Valores devueltos

Devuelve el objeto Closure recién creado, o null en caso de error.

Ejemplos

Ejemplo #1 Ejemplo de Closure::bindTo()

<?php

class A {
private
$val;
function
__construct($val) {
$this->val = $val;
}
function
getClosure() {
//devuelve el cierre vinculado a este objeto y el ámbito
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";
?>

El resultado del ejemplo sería algo similar a:

1
2

Ver también

add a note

User Contributed Notes 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