将闭包定义为类中的方法

Posted

技术标签:

【中文标题】将闭包定义为类中的方法【英文标题】:define a closure as method from class 【发布时间】:2011-02-22 07:18:14 【问题描述】:

我正在尝试使用 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.

首先这个例子不打印字符串而是返回函数,但这不是我的问题。

其次,我无法访问 private 或 protected,因为回调函数是一个全局函数,而不是在 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.";

来自 Dog 而不是全局的。

【问题讨论】:

不,我不明白你想要什么。如果 greet 是一种方法,则可见性默认为 public,因此您的最后两个代码块是等效的。 在 PHP 5.3 发布之前,在 clojure 中注入 $this 的魔法已被删除。实际上,它被推迟到即将发布的 PHP 版本。会有一些类似Closure::bindTo 的东西可以用来将对象作为上下文绑定到 lambda。 【参考方案1】:

嗯,你不能使用 $this 的全部原因是因为闭包是后台的一个对象(闭包类)。

有两种方法可以解决这个问题。首先,添加 __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);
    ;

另一种选择是提供一个通用的 getter:

class Dog 

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


欲了解更多信息,请参阅:http://www.php.net/manual/en/language.oop5.magic.php

【讨论】:

我想补充一下,您可以通过反射使该字段可访问。 你可以在 PHP 5.4 的闭包中使用$this 这是我知道的旧答案。但值得注意的是 Closure::bind() (php.net/manual/en/closure.bind.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 显式传递给您的函数,它被视为普通对象。 您应该创建 getter 以访问这些值,即:

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().";
         ;
    

对于encapsulation,无论如何你都应该创建getter(和setter)。


另一个注意事项:您链接到的文章是在 PHP 5.3 的最终版本发布之前发布的。也许这个隐式对象传递被删除了。

【讨论】:

是的,我知道它不像当前版本。我想要的是创建一个 Dog 类的新函数,然后在该函数中我可以访问 $this 而无需传递 $self。【参考方案4】:

我在工作中使用这个 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)
                                   //...
                                   
                                  );
         
?>

【讨论】:

以上是关于将闭包定义为类中的方法的主要内容,如果未能解决你的问题,请参考以下文章

Groovy闭包 Closure ( 闭包调用 与 call 方法关联 | 接口中定义 call() 方法 | 类中定义 call() 方法 | 代码示例 )

Groovy闭包 Closure ( 闭包调用 与 call 方法关联 | 接口中定义 call() 方法 | 类中定义 call() 方法 | 代码示例 )

javascript基础 方法 函数 闭包 集合

使用闭包进行 PHP 单元测试

Groovy闭包 Closure ( 闭包的 delegate 代理策略 | OWNER_FIRST | DELEGATE_FIRST | OWNER_ONLY | DELEGATE_ONLY )

Kotlin函数 ⑨ ( Kotlin 语言中的闭包概念 | Java 语言中函数作为参数的替代方案 )