如何调用作为类变量的闭包


How to call a closure that is a class variable?

class MyClass {
  var $lambda;
  function __construct() {
    $this->lambda = function() {echo 'hello world';};
    // no errors here, so I assume that this is legal
  }
}
$myInstance = new MyClass();
$myInstance->lambda();
//Fatal error: Call to undefined method MyClass::lambda()
那么获取类变量的正确语法是什么呢?

在PHP中,方法和属性在一个单独的命名空间中(你可以有一个同名的方法和一个属性),你是访问一个属性还是一个方法取决于你使用的语法。

$expr->something()是一个方法调用,因此PHP将在类的方法列表中搜索something

$expr->something是一个属性获取,所以PHP将在类的属性列表中搜索something

$myInstance->lambda();被解析为方法调用,因此PHP在类中搜索名为lambda的方法,但没有这样的方法(因此出现 call to undefined method错误)。

所以你必须使用fetch属性语法来获取lambda,然后调用它。

  • 从PHP 7.0开始,您可以使用($obj->lambda)():

    ($obj->lambda)();
    

    括号确保PHP将($obj->lambda)解析为获取名为lambda的属性。然后,()调用获取属性的结果

  • 或者你可以使用->lambda->__invoke():

    $myInstance = new MyClass();
    $myInstance->lambda->__invoke();
    

    __invoke是PHP的一个魔法方法。当对象实现此方法时,它变得可调用:可以使用$var()语法调用它。匿名函数是Closure的实例,它实现了__invoke

  • 或者赋值给一个局部变量:

    $lambda = $myInstance->lambda;
    $lambda();
    
  • 或者使用call_user_func: 调用
    call_user_func($myInstance->lambda);
    

    call_user_func可以调用任意callable,包括匿名函数

  • 或者,如果这是代码中的常见模式,您可以设置一个__call方法来转发对lambda的调用:

    class MyClass
    {
        private $lambda;
        public function __construct()
        {
            $this->lambda = function() {
                echo "Hello world!'n";
            };
        }
        public function __call($name, $args)
        {
            return call_user_func_array($this->$name, $args);
        }
    }
    

    $myInstance = new MyClass();
    $myInstance->lambda();
    

    从PHP 5.4开始,你甚至可以在trait中这样做:

    trait LambdasAsMethods
    {
        public function __call($name, $args)
        {
            return call_user_func_array($this->$name, $args);
        }
    }
    class MyClass
    {
        use LambdasAsMethods;
        private $lambda;
        public function __construct()
        {
            $this->lambda = function() {
                echo "Hello World!'n";
            };
        }
    }
    $myInstance = new MyClass();
    $myInstance->lambda();
    

你也可以调用你的lambda函数而不改变你的类,使用ReflectionFunction

$myInstance = new MyClass();
$lambda = new ReflectionFunction($myInstance->lambda);
$lambda->invoke();

或者如果你必须传递参数,那么

$args = array('arg'=>'value');
$lambda->invokeArgs($args);