本主题扩展了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中使用快捷方式,或者是否存在实际上这是正确的事情?
答案 0 :(得分:27)
PHP不允许像其他语言一样传递函数指针。 PHP中的函数不是first class。作为第一类的函数主要意味着您可以将函数保存到变量中,并将其传递并随时执行。
__invoke
方法是PHP可以容纳伪第一类函数的方法。
__invoke
方法可用于传递可以充当closure或continuation的类,或者只是作为可以传递的函数传递。
许多函数式编程都依赖于第一类函数。即使是正常的命令式编程也可以从中受益。
假设您有一个排序例程,但希望支持不同的比较功能。好吧,你可以使用不同的比较类来实现__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还提供了一个很好的使用快捷方式,为什么不使用呢?