__invoke
makes sense when you need to a callable that can maintain an internal state. Let's say you want to sort an 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 a simple function. However, in this case we want to sort the array based on the internal key 'value'
, which can be done as follows:
$comparisonFn = function($a, $b) {
return $a['value'] < $b['value'] ? -1 : ($a['value'] > $b['value'] ? 1 : 0);
};
usort($arr, $comparisonFn);
// o resultado vai ser um array onde
// ['key' => 'w', 'value' => 2] é o primeiro elemento,
// ['key' => 'w', 'value' => 3] é o segundo, etc
You may now need to reorder the array, this time based on the key
key, you would need 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, however we could not reuse the previous one due to the need to order based on a different key. This problem can be solved with a class that encapsulates the comparison logic in the __invoke
method and sets the key to be used in the constructor, eg
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);
}
}
An object of a class that implements __invoke
is a callable, it can be used in any context that a function could, so now we can simply instantiate Comparator
objects and pass them as the comparison function to usort
:
usort($arr, new Comparator('key')); // ordena por 'key'
usort($arr, new Comparator('value')); // ordena por 'value'
usort($arr, new Comparator('weight')); // ordena por 'weight'
Excerpt reflects my opinion, and as such, subjective, you can stop reading here if you want;) : Although this is an extremely interesting example of using __invoke
, such cases are rare, and I would particularly be left with the understanding of the operation in case it crosses with some code like that, but would avoid its use since, although the example shown is simple, it can be done in very confusing ways and there are usually clearer alternatives of implementation (though not always so comprehensive). An example in the same comparison problem would be the use of a function that returns the comparator 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 bit of familiarity with lambdas | closures | anonymous functions it is much more concise since it does not create the entire structure of a class just to store a simple external variable.