PHP 5.3 魔术方法 __invoke
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/888064/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
PHP 5.3 Magic Method __invoke
提问by Kekoa
This topic expands on When do/should I use __construct(), __get(), __set(), and __call() in PHP?which talks about the __construct, __getand __setmagic methods.
本主题扩展了何时/应该在 PHP 中使用 __construct()、__get()、__set() 和 __call()?其中讨论了__construct,__get和__set魔术方法。
As of PHP 5.3 there is a new Magic Method called __invoke. The __invokemethod is called when a script tries to call an object as a function.
从 PHP 5.3 开始,有一个名为__invoke. __invoke当脚本尝试将对象作为函数调用时会调用该方法。
Now on research I have done for this method, people liken it to the Java method .run()- see Interface Runnable.
现在,在我为这种方法所做的研究中,人们将它比作 Java 方法.run()- 请参阅Interface Runnable。
Having thought long and hard about this I can't think of any reason why you would call $obj();as opposed to $obj->function();
考虑了很久,我想不出有什么理由你会打电话$obj();而不是$obj->function();
Even if you were iterating over an array of objects, you would still know the main function name that you would want to run.
即使您正在迭代对象数组,您仍然会知道要运行的主函数名称。
So is the __invokemagic method another example of 'just because you can, doesn't mean you should' shortcut in PHP, or are there cases where this would actually be the right thing to do?
那么,__invoke魔术方法是 PHP 中“仅仅因为你可以,并不意味着你应该”的另一个例子,或者在某些情况下这实际上是正确的做法吗?
回答by Kekoa
This answer is slightly outdated for being written in 2009. Please take it with a grain of salt.
这个答案写于2009年略有过时,请持保留态度。
PHP does not allow the passing of function pointers like other languages. Functions are not first classin PHP. Functions being first class mainly means that you can save a function to a variable, and pass it around and execute it at any time.
PHP 不允许像其他语言一样传递函数指针。函数在 PHP中不是一流的。函数是一等的主要意味着你可以将一个函数保存到一个变量中,并在任何时候传递它并执行它。
The __invokemethod is a way that PHP can accommodate pseudo-first-class functions.
该__invoke方法是 PHP 可以容纳伪一流函数的一种方式。
The __invokemethod can be used to pass a class that can act as a closureor a continuation, or simply as a function that you can pass around.
该__invoke方法可用于传递一个类,该类可以充当闭包或延续,或者只是作为可以传递的函数。
A lot of functional programming relies on first class functions. Even normal imperative programming can benefit from this.
许多函数式编程依赖于一等函数。即使是普通的命令式编程也能从中受益。
Say you had a sort routine, but wanted to support different compare functions. Well, you can have different compare classes that implement the __invoke function and pass in instances to the class to your sort function, and it doesn't even have to know the name of the function.
假设您有一个排序例程,但想要支持不同的比较功能。好吧,您可以使用不同的比较类来实现 __invoke 函数并将实例传递给排序函数的类,并且它甚至不必知道函数的名称。
Really, you could always have done something like passing a class and have a function call a method, but now you can almost talk about passing a "function" instead of passing a class, although it's not as clean as in other languages.
真的,你总是可以做一些事情,比如传递一个类并让一个函数调用一个方法,但是现在你几乎可以谈论传递一个“函数”而不是传递一个类,尽管它不像其他语言那样干净。
回答by BrunoRB
The use of __invokemakes sense when you need a callablethat has to to maintain some internal state. Lets say you want to sort the following array:
__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]
];
The usortfunction allows you to sort an array using some function, very simple. However in this case we want to sort the array using its inner arrays 'value'key, what could be done this way:
该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
Now maybe you need to sort the array again, but this time using 'key'as the target key, it would be necessary to rewrite the function:
现在也许您需要再次对数组进行排序,但是这次使用'key'作为目标键,则需要重写该函数:
usort($arr, function($a, $b) {
return $a['key'] < $b['key'] ? -1 : ($a['key'] > $b['key'] ? 1 : 0);
});
As you can see the logic of the function is identical to the previous one, however we can't reuse the previous due to the necessity of sorting with a different key. This problem can be addressed with a class that encapsulates the logic of comparison in the __invokemethod and that define the key to be used in its constructor:
如您所见,该函数的逻辑与前一个相同,但是由于需要使用不同的键进行排序,我们不能重用前一个。这个问题可以通过一个类来解决,该类将比较逻辑封装在__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);
}
}
A Class object that implements __invokeit's a "callable", it can be used in any context that a function could be, so now we can simply instantiate Comparatorobjects and pass them to the usortfunction:
一个实现__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'
The following paragraphs reflect my subjective opinion, so if you want you can stop reading the answer now ;): Although the previous example showed a very interesting use of __invoke, such cases are rare and I would avoid its use since it can be done in really confusing ways and generally there are simpler implementation alternatives. An example of an alternative in the same sorting problem would be the use of a function that returns a comparison function:
以下段落反映了我的主观意见,因此,如果您愿意,现在可以停止阅读答案;):虽然前面的示例显示了非常有趣的 使用__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'));
Although this example requires a little more intimacy with lambdas | closures | anonymous functionsit's much more concise since it doesn't create a whole class structure just to store an outer value.
回答by jaz303
I believe this functionality exists mainly to support 5.3's new closure functionality. Closures are exposed as instances of the Closureclass, and are directly invokable e.g. $foo = $someClosure();. A practical benefit of __invoke()is that it becomes possible to create a standard callback type, rather than using strange combinations of strings, objects and arrays depending on whether you're referencing a function, instance method or static method.
我相信这个功能的存在主要是为了支持 5.3 的新关闭功能。闭包作为Closure类的实例公开,并且可以直接调用,例如$foo = $someClosure();. 一个实际的好处__invoke()是可以创建标准的回调类型,而不是使用字符串、对象和数组的奇怪组合,具体取决于您是引用函数、实例方法还是静态方法。
回答by sanmai
Really you shouldn't call $obj();as opposed to $obj->function();if you know you're dealing with a certain type of object. That said, unless you want your fellow colleagues scratch their heads.
实际上$obj();,$obj->function();如果您知道自己正在处理某种类型的对象,则不应该调用。也就是说,除非你想让你的同事挠头。
The __invokemethod comes to life in different situations. Especially when you're expected to provide a generic callable as an argument.
该__invoke方法在不同情况下生效。特别是当您希望提供一个通用的可调用对象作为参数时。
Imagine you have a method in a class (which you have to use and can't change) that takes only a callable as an argument.
想象一下,你在一个类中有一个方法(你必须使用它并且不能改变),它只接受一个可调用的作为参数。
$obj->setProcessor(function ($arg) {
// do something costly with the arguments
});
Now imagine you want to cache and reuse the result of a lengthy operation, or access previously used arguments to that function. With regular closures that can be chunky.
现在假设您想要缓存和重用冗长操作的结果,或者访问该函数以前使用的参数。有可能是厚实的定期关闭。
// 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;
});
See, if you happen to need to access the $argListfrom somewhere else, or simply clean the cache of stalled entries, you're in trouble!
看,如果您碰巧需要$argList从其他地方访问,或者只是清理停滞条目的缓存,那么您就有麻烦了!
Here comes __invoketo the rescue:
这里来__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;
}
}
With the class above working with the cached data becomes a breeze.
通过上面的类,处理缓存数据变得轻而易举。
$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);
This is just a simple example to illustrate the concept. One can go even further and create a generic wrapper and caching class. And much more.
这只是一个简单的例子来说明这个概念。可以更进一步,创建一个通用的包装器和缓存类。以及更多。
回答by Rob Kennedy
It's a combination of two things. You've correctly identified one of them already. This is indeed just like Java's IRunnableinterface, where every "runnable" object implements the same method. In Java, the method is named run; in PHP, the method is named __invoke, and you don't need to explicitly implement any particular interface type beforehand.
这是两件事的结合。您已经正确识别了其中之一。这确实就像 Java 的IRunnable接口一样,其中每个“可运行”对象都实现相同的方法。在 Java 中,该方法名为run; 在 PHP 中,该方法名为__invoke,您不需要事先显式实现任何特定的接口类型。
The second aspect is the syntactic sugar, so instead of calling $obj->__invoke(), you can skip the method name, so it appears as though you're calling the object directly: $obj().
第二个方面是语法糖,因此$obj->__invoke()您可以跳过方法名称而不是调用,因此看起来好像您正在直接调用对象:$obj()。
The key part for PHP to have closures is the first one. The language needs some established method to call on a closure object to make it do its thing. The syntactic sugar is just a way to make it look less ugly, as is the case with all the "special" functions with double-underscore prefixes.
PHP 拥有闭包的关键部分是第一个。该语言需要一些既定的方法来调用闭包对象以使其完成它的工作。语法糖只是一种让它看起来不那么难看的方法,就像所有带有双下划线前缀的“特殊”函数一样。
回答by DevWL
Concluding (based on all of the above)
结论(基于以上所有)
Generally I see __invoke(){...} magic method as a great opportunity for abstracting use of class object main functionality or for intuitive object setup (preparing object before using it's methods).
通常,我将 __invoke(){...} 魔术方法视为抽象类对象主要功能的使用或直观的对象设置(在使用对象的方法之前准备对象)的绝佳机会。
Case 1 - For example lets say that I use some third party object that implements __invoke magic method providing this way easy access to main functionality of an object instance. To use it main feature I only need to know what parameters __invoke method expects and what would be the end result of this function (closure). This way I am able to use the class object main functionality with only little effort to investygate the object capabilities (note that in this example we don't need to know or use any method name).
案例 1 - 例如,假设我使用了一些实现 __invoke 魔术方法的第三方对象,通过这种方式可以轻松访问对象实例的主要功能。要使用它的主要功能,我只需要知道 __invoke 方法需要什么参数以及这个函数的最终结果(闭包)。这样我就可以使用类对象的主要功能,只需很少的努力来调查对象的功能(注意,在这个例子中,我们不需要知道或使用任何方法名称)。
Abstracting from the real code...
从真正的代码中抽象出来...
instead of
代替
$obj->someFunctionNameInitTheMainFunctionality($arg1, $arg2);
we now use:
我们现在使用:
$obj($arg1, $arg2);
We can now also pass an object to other functions that expects its parameters to be callable just like in a regular function:
我们现在还可以将一个对象传递给其他函数,这些函数期望其参数可以像在常规函数中一样调用:
instead of
代替
someFunctionThatExpectOneCallableArgument($someData, [get_class($obj), 'someFunctionNameInitTheMainFunctionality']);
we now use:
我们现在使用:
someFunctionThatExpectOneCallableArgument($someData, $obj);
__invoke also provides a nice usage shortcut so why not to use it ?
__invoke 还提供了一个很好的使用快捷方式,为什么不使用它呢?
回答by Ali Motameni
The __invoke()method is called when a script tries to call an object as a function.
所述__invoke()方法被调用时,脚本试图调用一个对象作为功能。
Note: This feature is available since PHP 5.3.0.
注意:此功能自PHP 5.3.0.
<?php
class CallableClass
{
public function __invoke($x)
{
var_dump($x);
}
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>
The above example will output:
上面的例子将输出:
int(5)
bool(true)
整数(5)
布尔(真)
Click to see the reference.
单击以查看参考。

