PHP 5.3 Magic Method __invoke
The use of __invoke
makes sense when you need a callable that has to to maintain some internal state. Lets say you want to sort the following array:
$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 usort function 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:
$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:
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 __invoke
method and that define the key to be used in its constructor:
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 __invoke
it's a "callable", it can be used in any context that a function could be, so now we can simply instantiate Comparator
objects and pass them to the usort
function:
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:
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 functions it's much more concise since it doesn't create a whole class structure just to store an outer value.
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.
The __invoke
method comes to life in different situations. Especially when you're expected to provide a generic callable as an argument.
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 $argList
from somewhere else, or simply clean the cache of stalled entries, you're in trouble!
Here comes __invoke
to the rescue:
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.
I believe this functionality exists mainly to support 5.3's new closure functionality. Closures are exposed as instances of the Closure
class, 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.
This answer is slightly outdated for being written in 2009. Please take it with a grain of salt.
PHP does not allow the passing of function pointers like other languages. Functions are not first class in 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.
The __invoke
method is a way that PHP can accommodate pseudo-first-class functions.
The __invoke
method can be used to pass a class that can act as a closure or a continuation, or simply as a function that you can pass around.
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.
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.