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/in
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.
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.
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
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)
{//...
}
);
}
?>