user83632
user83632

Reputation:

PHP 5.3 Magic Method __invoke

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?

Upvotes: 40

Views: 33471

Answers (7)

DevWL
DevWL

Reputation: 18870

Concluding (based on all of the above)

Generally I see __invoke(){...} magic method as a great opportunity for abstracting use of class object main functionality or for intuitive object setup (preparing object before using it's methods).

Case 1 - For example lets say that I use some third party object that implements __invoke magic method providing this way easy access to main functionality of an object instance. To use it main feature I only need to know what parameters __invoke method expects and what would be the end result of this function (closure). This way I am able to use the class object main functionality with only little effort to investigate the object capabilities (note that in this example we don't need to know or use any method name).

Abstracting from the real code...

instead of

$obj->someFunctionNameInitTheMainFunctionality($arg1, $arg2);

we now use:

$obj($arg1, $arg2);

We can now also pass an object to other functions that expects its parameters to be callable just like in a regular function:

instead of

someFunctionThatExpectOneCallableArgument($someData, [get_class($obj), 'someFunctionNameInitTheMainFunctionality']);

we now use:

someFunctionThatExpectOneCallableArgument($someData, $obj);

__invoke also provides a nice usage shortcut so why not to use it ?

Upvotes: 2

Ali Motameni
Ali Motameni

Reputation: 2787

The __invoke() method is called when a script tries to call an object as a function.

Note: This feature is available since PHP 5.3.0.

<?php
    class CallableClass
    {
        public function __invoke($x)
        {
            var_dump($x);
        }
    }
    $obj = new CallableClass;
    $obj(5);
    var_dump(is_callable($obj));
?>

The above example will output:

int(5)

bool(true)

Click to see the reference.

Upvotes: 1

Kekoa
Kekoa

Reputation: 28250

This answer is slightly outdated for being written in 2009. Please take it with a grain of salt.

PHP does not allow the passing of function pointers like other languages. Functions are not first class in PHP. Functions being first class mainly means that you can save a function to a variable, and pass it around and execute it at any time.

The __invoke method is a way that PHP can accommodate pseudo-first-class functions.

The __invoke method can be used to pass a class that can act as a closure or a continuation, or simply as a function that you can pass around.

A lot of functional programming relies on first class functions. Even normal imperative programming can benefit from this.

Say you had a sort routine, but wanted to support different compare functions. Well, you can have different compare classes that implement the __invoke function and pass in instances to the class to your sort function, and it doesn't even have to know the name of the function.

Really, you could always have done something like passing a class and have a function call a method, but now you can almost talk about passing a "function" instead of passing a class, although it's not as clean as in other languages.

Upvotes: 34

sanmai
sanmai

Reputation: 30951

Really you shouldn't call $obj(); as opposed to $obj->function(); if you know you're dealing with a certain type of object. That said, unless you want your fellow colleagues scratch their heads.

The __invoke method comes to life in different situations. Especially when you're expected to provide a generic callable as an argument.

Imagine you have a method in a class (which you have to use and can't change) that takes only a callable as an argument.

$obj->setProcessor(function ($arg) {
    // do something costly with the arguments
});

Now imagine you want to cache and reuse the result of a lengthy operation, or access previously used arguments to that function. With regular closures that can be chunky.

// say what? what is it for?
$argList = [];

$obj->setProcessor(function ($arg) use (&$argList) {
    static $cache;
    // check if there is a cached result...
    // do something costly with the arguments
    // remember used arguments
    $argList[] = $arg;
    // save result to a cache
    return $cache[$arg] = $result;
});

See, if you happen to need to access the $argList from somewhere else, or simply clean the cache of stalled entries, you're in trouble!

Here comes __invoke to the rescue:

class CachableSpecificWorker
{
    private $cache = [];

    private $argList = [];

    public function __invoke($arg)
    {
        // check if there is a cached result...

        // remember used arguments
        $this->argList[] = $arg;

        // do something costly with the arguments

        // save result to a cache
        return $this->cache[$arg] = $result;
    }

    public function removeFromCache($arg)
    {
        // purge an outdated result from the cache
        unset($this->cache[$arg]);
    }

    public function countArgs()
    {
        // do the counting
        return $resultOfCounting;
    }
}

With the class above working with the cached data becomes a breeze.

$worker = new CachableSpecificWorker();
// from the POV of $obj our $worker looks like a regular closure
$obj->setProcessor($worker);
// hey ho! we have a new data for this argument
$worker->removeFromCache($argWithNewData);
// pass it on somewhere else for future use
$logger->gatherStatsLater($worker);

This is just a simple example to illustrate the concept. One can go even further and create a generic wrapper and caching class. And much more.

Upvotes: 6

BrunoRB
BrunoRB

Reputation: 909

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.

Upvotes: 53

Rob Kennedy
Rob Kennedy

Reputation: 163357

It's a combination of two things. You've correctly identified one of them already. This is indeed just like Java's IRunnable interface, where every "runnable" object implements the same method. In Java, the method is named run; in PHP, the method is named __invoke, and you don't need to explicitly implement any particular interface type beforehand.

The second aspect is the syntactic sugar, so instead of calling $obj->__invoke(), you can skip the method name, so it appears as though you're calling the object directly: $obj().

The key part for PHP to have closures is the first one. The language needs some established method to call on a closure object to make it do its thing. The syntactic sugar is just a way to make it look less ugly, as is the case with all the "special" functions with double-underscore prefixes.

Upvotes: 4

jaz303
jaz303

Reputation: 1136

I believe this functionality exists mainly to support 5.3's new closure functionality. Closures are exposed as instances of the Closure class, and are directly invokable e.g. $foo = $someClosure();. A practical benefit of __invoke() is that it becomes possible to create a standard callback type, rather than using strange combinations of strings, objects and arrays depending on whether you're referencing a function, instance method or static method.

Upvotes: 15

Related Questions