PHP 5.3 魔术方法__invoke

2022-08-30 12:21:35

本主题扩展了 PHP 中何时/应该使用 __construct()、__get()、__set() 和 __call()?,其中讨论了 和 魔术方法。__construct__get__set

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

现在,在我对这种方法所做的研究中,人们将其比作Java方法 - 请参阅Interface Runnable.run()

经过长时间和艰苦的思考,我想不出任何理由为什么你会打电话而不是$obj();$obj->function();

即使您正在迭代对象数组,您仍然会知道要运行的 main 函数名称。

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


答案 1

当您需要一个必须维护某些内部状态的可调用时,使用__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]
];

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);
    }
}

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

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

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'));

虽然此示例需要与 lambdas |闭包|匿名函数更加紧密,但它更加简洁,因为它不会创建整个类结构,只是为了存储外部值。


答案 2

这个答案在2009年写得有点过时了。请用一粒盐服用。

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

该方法是 PHP 可以容纳伪一等函数的一种方式。__invoke

该方法可用于传递可用作闭包延续的类,或仅作为可以传递的函数的类。__invoke

许多函数式编程依赖于一等函数。即使是普通的命令式编程也可以从中受益。

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

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


推荐