PHP 5.3 Magic Method __invoke

时间:2009-05-20 13:56:46

标签: php oop magic-methods

本主题扩展了When do/should I use __construct(), __get(), __set(), and __call() in PHP?,其中讨论了__construct__get__set魔术方法。

从PHP 5.3开始,有一种名为__invoke的新方法。当脚本尝试将对象作为函数调用时,将调用__invoke方法。

我正在研究这个方法,人们将它比作Java方法.run() - 请参阅Interface Runnable

经过长时间的努力思考,我想不出有什么理由可以拨打$obj();而不是$obj->function();

即使您在一组对象上进行迭代,您仍然可以知道要运行的主函数名称。

__invoke魔法方法也是另一个例子'因为你可以,并不意味着你应该'在PHP中使用快捷方式,或者是否存在实际上这是正确的事情?

7 个答案:

答案 0 :(得分:27)

PHP不允许像其他语言一样传递函数指针。 PHP中的函数不是first class。作为第一类的函数主要意味着您可以将函数保存到变量中,并将其传递并随时执行。

__invoke方法是PHP可以容纳伪第一类函数的方法。

__invoke方法可用于传递可以充当closurecontinuation的类,或者只是作为可以传递的函数传递。

许多函数式编程都依赖于第一类函数。即使是正常的命令式编程也可以从中受益。

假设您有一个排序例程,但希望支持不同的比较功能。好吧,你可以使用不同的比较类来实现__invoke函数,并将实例传递给类到sort函数,它甚至不需要知道函数的名称。

真的,你总是可以做一些事情,比如传递一个类并让函数调用一个方法,但是现在你几乎可以谈论传递一个“函数”而不是传递一个类,尽管它不像其他语言那样干净

答案 1 :(得分:18)

当您需要__invoke来维持某种内部状态时,使用callable是有意义的。假设您要对以下数组进行排序:

$arr = [
    ['key' => 3, 'value' => 10, 'weight' => 100], 
    ['key' => 5, 'value' => 10, 'weight' => 50], 
    ['key' => 2, 'value' => 3, 'weight' => 0], 
    ['key' => 4, 'value' => 2, 'weight' => 400], 
    ['key' => 1, 'value' => 9, 'weight' => 150]
];

usort函数允许您使用某个函数对数组进行排序,非常简单。但是在这种情况下,我们希望使用其内部数组'value'键对数组进行排序,可以这样做:

$comparisonFn = function($a, $b) {
    return $a['value'] < $b['value'] ? -1 : ($a['value'] > $b['value'] ? 1 : 0);
};
usort($arr, $comparisonFn);

// ['key' => 'w', 'value' => 2] will be the first element, 
// ['key' => 'w', 'value' => 3] will be the second, etc

现在也许您需要再次对数组进行排序,但这次使用'key'作为目标键,有必要重写该函数:

usort($arr, function($a, $b) {
    return $a['key'] < $b['key'] ? -1 : ($a['key'] > $b['key'] ? 1 : 0);
});

正如您所看到的,该函数的逻辑与前一个逻辑相同,但由于需要使用不同的键进行排序,因此我们无法重复使用前一个函数。这个问题可以通过一个类来解决,该类在__invoke方法中封装了比较逻辑,并定义了在其构造函数中使用的键:

class Comparator {
    protected $key;

    public function __construct($key) {
            $this->key = $key;
    }

    public function __invoke($a, $b) {
            return $a[$this->key] < $b[$this->key] ? 
               -1 : ($a[$this->key] > $b[$this->key] ? 1 : 0);
    }
}

实现__invoke它是“可调用”的Class对象,它可以在函数可以使用的任何上下文中使用,所以现在我们可以简单地实例化Comparator个对象并将它们传递给{ {1}}功能:

usort

以下段落反映了我的主观意见,所以如果你想要,你现在可以停止阅读答案;):虽然前面的例子显示了usort($arr, new Comparator('key')); // sort by 'key' usort($arr, new Comparator('value')); // sort by 'value' usort($arr, new Comparator('weight')); // sort by 'weight' 的一个非常有趣的用法,但是这种情况是罕见的,我会避免使用它,因为它可以以非常混乱的方式完成,通常有更简单的实现替代方案。在同一排序问题中的替代方案的示例是使用返回比较函数的函数:

__invoke

虽然这个例子需要与lambdas | closures | anonymous functions更加亲密,但它更简洁,因为它不会创建一个完整的类结构来存储外部值。

答案 2 :(得分:14)

我相信这个功能主要是为了支持5.3的新闭包功能。闭包作为Closure类的实例公开,并且可以直接调用,例如$foo = $someClosure();__invoke()的一个实际好处是可以创建标准回调类型,而不是使用字符串,对象和数组的奇怪组合,具体取决于您是引用函数,实例方法还是静态方法。

答案 3 :(得分:3)

这是两件事的组合。你已经正确地确定了其中一个。这确实就像Java的IRunnable接口,其中每个“runnable”对象实现相同的方法。在Java中,该方法名为run;在PHP中,该方法名为__invoke,您无需事先显式实现任何特定的接口类型。

第二个方面是语法糖,因此您可以跳过方法名称,而不是调用$obj->__invoke(),因此看起来好像是直接调用对象:{{ 1}}。

PHP拥有闭包的关键部分是第一个。该语言需要一些已建立的方法来调用闭包对象以使其完成其任务。语法糖只是让它看起来不那么难看的一种方式,就像所有带有双下划线前缀的“特殊”函数一样。

答案 4 :(得分:3)

如果你知道自己正在处理某种类型的对象,那么你真的不应该调用$obj();而不是$obj->function();。也就是说,除非你想让你的同事们低头。

__invoke方法在不同情况下变为现实。特别是当您希望提供通用可调用作为参数时。

想象一下,你在一个类中有一个方法(你必须使用它并且不能改变),它只接受一个可调用的参数。

$obj->setProcessor(function ($arg) {
    // do something costly with the arguments
});

现在假设您想要缓存并重用冗长操作的结果,或者访问以前使用过的参数到该函数。定期关闭可能会很粗糙。

// say what? what is it for?
$argList = [];

$obj->setProcessor(function ($arg) use (&$argList) {
    static $cache;
    // check if there is a cached result...
    // do something costly with the arguments
    // remember used arguments
    $argList[] = $arg;
    // save result to a cache
    return $cache[$arg] = $result;
});

请参阅,如果您碰巧需要从其他地方访问$argList,或者只是清理停滞的条目缓存,那么您就麻烦了!

__invoke来救援:

class CachableSpecificWorker
{
    private $cache = [];

    private $argList = [];

    public function __invoke($arg)
    {
        // check if there is a cached result...

        // remember used arguments
        $this->argList[] = $arg;

        // do something costly with the arguments

        // save result to a cache
        return $this->cache[$arg] = $result;
    }

    public function removeFromCache($arg)
    {
        // purge an outdated result from the cache
        unset($this->cache[$arg]);
    }

    public function countArgs()
    {
        // do the counting
        return $resultOfCounting;
    }
}

使用上面的类处理缓存数据变得轻而易举。

$worker = new CachableSpecificWorker();
// from the POV of $obj our $worker looks like a regular closure
$obj->setProcessor($worker);
// hey ho! we have a new data for this argument
$worker->removeFromCache($argWithNewData);
// pass it on somewhere else for future use
$logger->gatherStatsLater($worker);

这只是一个简单的例子来说明这个概念。人们可以更进一步,创建一个通用的包装器和缓存类。还有更多。

答案 5 :(得分:2)

当脚本尝试将对象作为函数调用时,会调用 __ invoke()方法。

注意:此功能自PHP 5.3.0起可用。

<?php
    class CallableClass
    {
        public function __invoke($x)
        {
            var_dump($x);
        }
    }
    $obj = new CallableClass;
    $obj(5);
    var_dump(is_callable($obj));
?>

上面的示例将输出:

int(5)

bool(true)

单击以查看the reference

答案 6 :(得分:1)

结论(基于以上所有)

通常我认为__invoke(){...}魔术方法是抽象使用类对象主要功能或直观对象设置(在使用它的方法之前准备对象)的绝佳机会。

案例1 - 例如,假设我使用了一些实现__invoke魔术方法的第三方对象,这样就可以轻松访问对象实例的主要功能。要使用它的主要功能,我只需要知道__invoke方法所期望的参数以及该函数(闭包)的最终结果。通过这种方式,我可以使用类对象主要功能,只需很少的努力就可以对对象功能进行投资(请注意,在此示例中,我们不需要知道或使用任何方法名称)。

从真实代码中摘要......

而不是

$obj->someFunctionNameInitTheMainFunctionality($arg1, $arg2);

我们现在使用:

$obj($arg1, $arg2);

我们现在还可以将一个对象传递给其他函数,这些函数希望它的参数可以像常规函数一样调用:

而不是

someFunctionThatExpectOneCallableArgument($someData, [get_class($obj), 'someFunctionNameInitTheMainFunctionality']);

我们现在使用:

someFunctionThatExpectOneCallableArgument($someData, $obj);

__ invoke还提供了一个很好的使用快捷方式,为什么不使用呢?