What is the purpose of implementing a magic __invoke method in a class?

15

I know what the __invoke method is for. It is for a class to perform an action, if called as a function.

In addition, it is present in the special class of php called Closure , which is instantiated when we call the anonymous function in php.

What is the purpose of having a class that can be called as a function, using the magic method __invoke ?

  class Invokable {

         public function __construct($name)
         {
             $this->name = $name;
         }

         public function __invoke()
         {
              return $this->name;
         }
 }


 $user = new Invokable('Wallace');

 $user(); // "Wallace";

What is the advantage of using this implementation in a user-created class? That is, I'm disregarding the already existing implementation of Closure .

    
asked by anonymous 02.02.2016 / 17:59

1 answer

9

__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.

    
08.02.2016 / 19:00