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

Asked

Viewed 1,667 times

17

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

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

What is the purpose of owning a class that can be called as a function, through 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/utility of using this implementation in a user-created class? That is, I am disregarding the already existing implementation of Closure.

  • I think someone could give an "increment" in the answers, adding details such as: The instance of the class that implements invoke can be evaluated as true when we use callable or is_callable.

  • Hm, but this is already implicit in the excerpt of my reply that says that an object that implements __invoke is a callable.

1 answer

11


__invoke makes sense when you need one callable that can maintain an internal state. 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 function usort 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

Now you may need to reorder the array, this time based on the key 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 function logic is identical, however we could not reuse the previous one due to the need to sort based on a different key. This problem can be solved with a class that encapsulates comparison logic in the method __invoke and that defines the key to be used in the constructor, ex:

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 objects Comparator 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'

Stretch the front reflects my opinion, and as such, subjective, you can stop reading here if you want ;): Although this is an extremely interesting example of the use of __invoke, such cases are rare and I would particularly stick with the understanding of the functioning for case cross with some code so, 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 to implementation (although not always so comprehensive). An example in the same previous 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 little familiarity with Standard | closures | Anonymous functions it is much more concise since it does not create the whole structure of a class just to store a simple external variable.

  • 1

    But I particurlamente liked the callable example

  • 2

    On the opinion, I agree. It is good to avoid obscuring the codes. The simpler and readable, the better.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.