14

i'm trying to play with php5.3 and closure.

I see here (Listing 7. Closure inside an object : http://www.ibm.com/developerworks/opensource/library/os-php-5.3new2/index.html) that it's possible to use $this in the callback function, but it's not. So I try to give $this as use variable :

$self = $this;
$foo = function() use($self) { //do something with $self }

So to use the same example :

class Dog
{
private $_name;
protected $_color;

public function __construct($name, $color)
{
     $this->_name = $name;
     $this->_color = $color;
}
public function greet($greeting)
{
     $self = $this;
     return function() use ($greeting, $self) {
         echo "$greeting, I am a {$self->_color} dog named {$self->_name}.";
     };
}
}

$dog = new Dog("Rover","red");
$dog->greet("Hello");

Output:
Hello, I am a red dog named Rover.

First of all this example does not print the string but return the function, but that's not my problem.

Secondly I can't access to private or protected, because the callback function is a global function and not in the context from the Dog object. Tha't my problem. It's the same as :

function greet($greeting, $object) {
    echo "$greeting, I am a {$self->_color} dog named {$self->_name}.";
}

And I want :

public function greet($greeting) {
    echo "$greeting, I am a {$self->_color} dog named {$self->_name}.";
}

Which is from Dog and not global.

hakre
  • 193,403
  • 52
  • 435
  • 836
Charles
  • 11,367
  • 10
  • 77
  • 114
  • No, I didn't understand what you want. If greet is a method, the visibility defauls to public, so your two last code blocks are equivalent. – Artefacto May 21 '10 at 10:58
  • That magic injection of `$this` in a clojure has been dropped before PHP 5.3 was released. Actually, it was postponed for an upcoming version of PHP. There will some something like `Closure::bindTo` that one can use to bind objects as contexts to lambdas. – Ionuț G. Stan May 21 '10 at 13:01

4 Answers4

9

Well, the whole reason that you can't use $this, is because the closure is an object in the background (the Closure class).

There are two ways around this. First, is add the __invoke method (What's called if you call $obj())..

class Dog {

    public function __invoke($method) {
        $args = func_get_args();
        array_shift($args); //get rid of the method name
        if (is_callable(array($this, $method))) {
            return call_user_func_array(array($this, $method), $args);
        } else {
            throw new BadMethodCallException('Unknown method: '.$method);
        }
    }

    public function greet($greeting) {
        $self = $this;
        return function() use ($greeting, $self) {
            $self('do_greet', $greeting);
        };
    }

    protected function do_greet($greeting) {
        echo "$greeting, I am a {$this->_color} dog named {$this->_name}.";
    }
}

If you want the closure to not change if you modify the host object, you can just change the return function to something like:

public function greet($greeting) {
    $self = (clone) $this;
    return function() use ($greeting, $self) {
        $self('do_greet', $greeting);
    };
}

The other option, is to provide a generic getter:

class Dog {

    public function __get($name) {
        return isset($this->$name) ? $this->$name : null;
    }

}

For more information, see: http://www.php.net/manual/en/language.oop5.magic.php

ircmaxell
  • 163,128
  • 34
  • 264
  • 314
4

As of PHP 5.4.0 Alpha1, you can access $this from within the context of an object instance:

<?php
class Dog
{
  private $_name;
  protected $_color;

  public function __construct($name, $color)
  {
    $this->_name = $name;
    $this->_color = $color;
  }

  public function greet($greeting)
  {
    $func = function() use ($greeting) {
      echo "$greeting, I am a {$this->_color} dog named {$this->_name}.";
    };

    $func();
  }
}

$dog = new Dog("Rover","red");
$dog->greet("Hello");

You can also do this:

$dog = new Dog("Rover", "red");
$getname = Closure::bind($dog, function() { return $this->_name; });
echo $getname(); // Rover

As you can see, it's possible to easily mess with private data... so be careful.

Matthew
  • 47,584
  • 11
  • 86
  • 98
3

Well it makes sense that you cannot access private and protected fields of an object. And by explicitly passing $self to your function, it is treated just as a normal object.
You should create getters in order to access these values , i.e. :

class Dog
{
    private $_name;
    protected $_color;

    public function __construct($name, $color)
    {
         $this->_name = $name;
        $this->_color = $color;
    }
    public function getName() {
        return $this->_name;
    }
    public function getColor() {
        return $this->_color;
    }
    public function greet($greeting)
    {
         $self = $this;
         return function() use ($greeting, $self) {
             echo "$greeting, I am a {$self->getColor()} dog named {$self->getName()}.";
         };
    }
}

You should create getter (and setters) anyway, for matter of encapsulation.


Another note: The article you link to was published before the final version of PHP 5.3 was released. Maybe this implicit object passing was removed.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Yes I know it's not like the current version. That I want is to create a new function of the class Dog and then in the function I can access to $this without to pass $self. – Charles May 21 '10 at 12:40
0

I use this create_closure() in my work to seperate callbacks into Classes:

<?php
function create_closure($fun, $args, $uses)
         {$params=explode(',', trim($args.','.$uses, ','));
          $str_params='';
          foreach ($params as $v)
                  {$v=trim($v, ' &$');
                   $str_params.='\''.$v.'\'=>&$'.$v.', ';
                  }
          return "return function({$args}) use ({$uses}) {{$fun}(array({$str_params}));};";
         }
?>

example:

<?php
$loop->addPeriodicTimer(1, eval(create_closure('pop_message', '$timer', '$cache_key, $n, &$response, &$redis_client')));

function pop_message($params)
         {extract($params, EXTR_REFS);
          $redis_client->ZRANGE($cache_key, 0, $n)
                            ->then(//normal
                                   function($data) use ($cache_key, $n, &$timer, &$response, &$redis_client)
                                   {//...
                                   },
                                   //exception
                                   function ($e) use (&$timer, &$response, &$redis_client)
                                   {//...
                                   }
                                  );
         }
?>
diyism
  • 12,477
  • 5
  • 46
  • 46