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.