How to bind $this to a closure that is passed as a method parameter in PHP 5.4?
Asked Answered
O

4

12

Is there any way to bind $this to a closure that is passed as a parameter? I read and reread everything I could find in manual or over the internet, but no one mentions this behaviour, except this blog post: http://www.christophh.net/2011/10/26/closure-object-binding-in-php-54/ which mentions it but doesn't show how to do it.

So here's an example. When calling the get(function() {}) method I want the callback function that is passed to it was bound to the object i.e. bound to $this, but unfortunately it doesn't work. Is there any way I can do it?

class APP
{
    public $var = 25;

    public function __construct() {

    }
    public function get($callback) {
        if (!is_callable($callback)) {
            throw new InvalidArgumentException('Paran must be callable.');
        }
        // $callback->bindTo($this);
        $callback->bindTo($this, $this);
        $callback();
    }
}

$app = new APP();
$app->get(function() use ($app) {
    echo '<pre>';
    var_dump($app);
    echo '<br />';
    var_dump($this);
});

$app works. $this is NULL.

Odontalgia answered 9/12, 2013 at 10:39 Comment(0)
S
9

I actually didn't understand why using the bindTo method didn't work in this case, but I could get it to work using Closure::bind

public function get($callback) {
    if (!is_callable($callback)) {
        throw new InvalidArgumentException('Param must be callable.');
    }

    $bound = Closure::bind($callback, $this);
    $bound();
}

Edit

Aparently the bindTo method has the same behavior, so you should reassign its return value to $callback. For example:

public function get($callback) {
    if (!is_callable($callback)) {
        throw new InvalidArgumentException('Param must be callable.');
    }

    $callback = $callback->bindTo($this);
    $callback();
}
Stationmaster answered 9/12, 2013 at 10:54 Comment(4)
Damn! Thanks a lot Guilherme! I tried Closure::bind, but I did'n know it was the returning value I should call after that.Odontalgia
And in one line $callback->bindTo($this)->__invoke(); :)Odontalgia
Are you sure that is_callable check is enough ? If i pass [$someObject, 'existedMethod'] to your function it will pass callable check (because it`s valid callable variable) and fail at binding line.Protestant
@Protestant That's a long time I answered it. I really didn't think about this because the person who asked intended to use an anonymous function as callback. Feel free to edit it and add more details if you solve it for this use case. :)Stationmaster
K
3

Do it like this:

class APP
{
  public $var = 25;

  public function __construct() {}

  public function get($callback) {
    if (!is_callable($callback)) {
      throw new InvalidArgumentException('Param must be callable.');
    }
    // $callback->bindTo($this);
    // you must save result in another var and call it
    $callback1 = $callback->bindTo($this, $this);
    $callback1();
  }
}

$app = new APP();
$app->get(function() use ($app) {
  echo '<pre>';
  var_dump($app);
  echo '<br />';
  var_dump($this);
});
Kannada answered 9/12, 2013 at 10:57 Comment(1)
This also works! So it's the returning closure that is bound and not the original one.Odontalgia
F
1

Just pass it as an argument:

    public function get($callback) {
        if (!is_callable($callback)) {
            throw new InvalidArgumentException('Paran must be callable.');
        }
        // $callback->bindTo($this);
        return $callback($this);
    }

...

$app = new APP();
$app->get(function($that) use ($app) {
    echo '<pre>';
    var_dump($app);
    echo '<br />';
    var_dump($that);
});

Alternatively, if you really did need to bind it, you would have to use a function that returned a function, like this:

    public function getCallback($callback) {
        return function($app){
            return $callback($this, $app);
        }
    }

...

$app = new APP();
$f = $app->get(function($that, $app) {
    echo '<pre>';
    var_dump($app);
    echo '<br />';
    var_dump($that);
});
$f($app);
Fournier answered 9/12, 2013 at 10:51 Comment(1)
Thanks Benubird (Like your name :)), I know that workarounds already (As I said, I almost read everything I could find about this subject), but It looks really ugly to me, I though If a language implements closures this should be achievable more direct way.Odontalgia
L
1

A small correction: don't use is_callable to check if Closure is passed by parameter.

Because is_callable too accept String with name of function.

public function get(\Closure $callback) {

    $bound = \Closure::bind($callback, $this);
    $bound();
}

With is_callable we have this possibility:

$app = new App;


$app->get('my_function');

If function exists, this error is thrown:

Closure::bind() expects parameter 1 to be Closure, string given

Because "My_function" is passed in test of is_callable, but Closure::bind expects instance of Closure.

Litman answered 13/7, 2015 at 12:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.