This topic expands on When do/should I use __construct(), __get(), __set(), and __call() in PHP? which talks about the __construct
, __get
and __set
magic methods.
As of PHP 5.3 there is a new Magic Method called __invoke
. The __invoke
method is called when a script tries to call an object as a function.
Now on research I have done for this method, people liken it to the Java method .run()
- see 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();
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 __invoke
magic 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?
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.