Определить замыкание как метод из класса
Я пытаюсь играть с php5.3 и закрытием.
Здесь я вижу (листинг 7. Закрытие внутри объекта: http://www.ibm.com/developerworks/opensource/library/os-php-5.3new2/index.html), что можно использовать $this в функции обратного вызова, но это не так. Поэтому я пытаюсь дать $this как переменную использования:
$self = $this;
$foo = function() use($self) { //do something with $self }
Итак, чтобы использовать тот же пример:
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.
Прежде всего, этот пример не печатает строку, а возвращает функцию, но это не моя проблема.
Во-вторых, я не могу получить доступ к частным или защищенным, потому что функция обратного вызова является глобальной функцией, а не контекстом от объекта Dog. Это моя проблема. Это то же самое, что:
function greet($greeting, $object) {
echo "$greeting, I am a {$self->_color} dog named {$self->_name}.";
}
И я хочу:
public function greet($greeting) {
echo "$greeting, I am a {$self->_color} dog named {$self->_name}.";
}
Что из Собаки, а не глобально.
Ответы
Ответ 1
Ну, вся причина, по которой вы не можете использовать $this, заключается в том, что закрытие является объектом в фоновом режиме (класс Closure).
Есть два способа обойти это. Во-первых, добавляет метод __invoke (что называется, если вы вызываете $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}.";
}
}
Если вы хотите, чтобы закрытие не менялось, если вы изменяете объект-хост, вы можете просто изменить функцию возврата на что-то вроде:
public function greet($greeting) {
$self = (clone) $this;
return function() use ($greeting, $self) {
$self('do_greet', $greeting);
};
}
Другой вариант - предоставить общий приемник:
class Dog {
public function __get($name) {
return isset($this->$name) ? $this->$name : null;
}
}
Для получения дополнительной информации см. http://www.php.net/manual/en/language.oop5.magic.php
Ответ 2
Как и PHP 5.4.0 Alpha1, вы можете получить доступ к $this
из контекста экземпляра объекта:
<?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");
Вы также можете сделать это:
$dog = new Dog("Rover", "red");
$getname = Closure::bind($dog, function() { return $this->_name; });
echo $getname(); // Rover
Как вы можете видеть, возможно легко соединить личные данные... так что будьте осторожны.
Ответ 3
Хорошо, имеет смысл, что вы не можете получить доступ к закрытым и защищенным полям объекта. И, явно передавая $self
вашей функции, он рассматривается как обычный объект.
Вы должны создать геттеры, чтобы получить доступ к этим значениям, то есть:
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()}.";
};
}
}
В любом случае вы должны создать getter (и сеттеры), для вопроса encapsulation.
Другое примечание. Статья, на которую вы ссылаетесь, была опубликована до выхода финальной версии PHP 5.3. Возможно, эта неявная передача объекта была удалена.
Ответ 4
Я использую this create_closure() в своей работе, чтобы разделить обратные вызовы на классы:
<?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}));};";
}
?>
Пример:
<?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)
{//...
}
);
}
?>