PHP 5.3 Magic Method __invoke

27 php oop magic-methods

本主题扩展了什么时候/我应该在PHP中使用__construct(),__ get(),__ set()和__call()? 其中谈到了__construct,__get__set魔术方法.

从PHP 5.3开始,有一个新的魔术方法叫做__invoke.__invoke当脚本尝试将对象作为函数调用时,将调用该方法.

现在我已经为这种方法做过研究,人们把它比作Java方法.run()- 参见Interface Runnable.

经过长时间的努力思考,我无法想到为什么你会打电话$obj(); 而不是$obj->function();

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

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

Kek*_*koa 29

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

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

__invoke方法可用于传递可以充当闭包延续的类,或者仅仅作为可以传递的函数.

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

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

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

  • PHP 5.3中的第一类函数/闭包:函数multiplyBy($ x){return function($ y)use($ x){return $ x*$ y;};}.$ mulby3 = multiplyBy(3); $ twentyone = $ mulby3(7); - __invoke也是> 5.3,因此使用__invoke作为伪第一类的替代并不总是有意义的. (3认同)
  • 我不明白......如果不是$ a,第一类的功能究竟是什么?$ a = function($ x,$ y){return($ x <$ y?$ x:$ y;}; $ min = array_reduce(array(1,2,3),$ a); (2认同)
  • -1; `__invoke` 不是 PHP “容纳伪一流函数”的必要步骤;它是在与适当的 [匿名 (lambda) 函数](http://php.net/manual/en/functions.anonymous.php) 相同的版本中添加的。因此,您对更改背后意图的解释毫无意义。另请注意,Python——其中的函数非常出色,即使有限的 lambda 语法使许多函数式编程变得笨拙——支持类似于 PHP 的“__invoke”的“__call__”魔术方法。 (2认同)

小智 25

__invoke当你需要一个必须维持一些内部状态的可调用时,使用是有意义的.假设您要对以下数组进行排序:

$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]
];
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

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

正如您所看到的,该函数的逻辑与前一个逻辑相同,但由于需要使用不同的键进行排序,因此我们无法重用之前的函数.这个问题可以通过一个类来解决,该类在__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);
    }
}
Run Code Online (Sandbox Code Playgroud)

实现__invoke它的Class对象是"可调用的",它可以在函数可以使用的任何上下文中使用,所以现在我们可以简单地实例化Comparator对象并将它们传递给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'
Run Code Online (Sandbox Code Playgroud)

以下段落反映了我的主观意见,所以如果你想要你现在可以停止阅读答案;):虽然前面的例子显示了一个非常有趣的用法__invoke,但这种情况很少见,我会避免使用它,因为它可以真的完成令人困惑的方式,通常有更简单的实施方案.在同一排序问题中的替代方案的示例是使用返回比较函数的函数:

function getComparisonByKeyFn($key) {
    return function($a, $b) use ($key) {
            return $a[$key] < $b[$key] ? -1 : ($a[$key] > $b[$key] ? 1 : 0);
    };
}
usort($arr, getComparisonByKeyFn('weight'));
usort($arr, getComparisonByKeyFn('key'));
usort($arr, getComparisonByKeyFn('value'));
Run Code Online (Sandbox Code Playgroud)

虽然这个例子需要与lambdas有一点亲密关系 闭合| 匿名函数它更简洁,因为它不会创建一个完整的类结构只是为了存储外部值.

  • `__invoke` 对 `Closure` 的优势在于,你可以获得 `class` 提供的所有结构,包括强制接口、继承和应用特征。我可以看到接口是这里最方便的东西。 (3认同)
  • 不能要求更好的解释。谢谢。 (2认同)

小智 14

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


san*_*mai 5

实际上$obj();$obj->function();如果您知道自己正在处理某种类型的对象,则不应该调用。也就是说,除非你想让你的同事挠头。

__invoke方法在不同情况下生效。特别是当您希望提供一个通用的可调用对象作为参数时。

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

$obj->setProcessor(function ($arg) {
    // do something costly with the arguments
});
Run Code Online (Sandbox Code Playgroud)

现在假设您想要缓存和重用冗长操作的结果,或者访问该函数以前使用的参数。有可能是厚实的定期关闭。

// 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;
});
Run Code Online (Sandbox Code Playgroud)

看,如果您碰巧需要$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;
    }
}
Run Code Online (Sandbox Code Playgroud)

通过上面的类,处理缓存数据变得轻而易举。

$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);
Run Code Online (Sandbox Code Playgroud)

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