40

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?

Community
  • 1
  • 1

7 Answers7

50

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.

BrunoRB
  • 889
  • 8
  • 14
  • 2
    couldn't have asked for a better explanation. Thanks. – mansoor.khan Nov 27 '16 at 05:12
  • 3
    The advantage `__invoke` has on `Closure`s, is you get all the structure a `class` provides, including enforcing interfaces, inheritance, and applying traits. I could see interfaces being the most handy thing here. – Josh from Qaribou Jul 31 '17 at 20:02
  • 1
    Good you showed a function based alternative to your example. `__invoke` seems to be an aberration to keep using OO paradigm where it isn't needed. – jpenna Dec 04 '19 at 18:56
  • or you use array syntax for objects : usort($arr, [new Comparator(),'sortByKey']); // sort by 'key' usort($arr, [new Comparator(),'sortByValue']); // sort by 'value' usort($arr, [new Comparator(),'sortByWeight']); // sort by 'weight' – Arthur Kielbasa Jan 18 '22 at 11:45
35

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.

sanmai
  • 29,083
  • 12
  • 64
  • 76
Kekoa
  • 27,892
  • 14
  • 72
  • 91
  • 2
    i don't get it ... what exactly first class functions if not $a? $a = function ($x,$y) {return ($x<$y ? $x : $y;}; $min = array_reduce(array(1,2,3), $a); – stefs May 20 '09 at 14:24
  • oooooh! okay, sorry for mixing up anonymous/first class/lambda-style functions all the time. – stefs May 20 '09 at 14:27
  • 3
    first class functions / closures in PHP 5.3: function multiplyBy($x) { return function ($y) use ($x) { return $x*$y;};}. $mulby3 = multiplyBy(3); $twentyone = $mulby3(7); - __invoke is >5.3 too, so using __invoke as a replacement for pseudo-first-class makes not always sense. – stefs May 20 '09 at 15:07
  • Many thanks for your answer, thats cleared a few things up and provided me with a lot of reading on the subject. –  May 20 '09 at 20:59
  • 1
    Your first link would tell you PHP does indeed have first-class functions. http://en.wikipedia.org/wiki/First-class_function#Language_support – Steve Clay Jul 06 '12 at 04:22
  • 1
    @mrclay Things are apparently better in PHP than when I wrote the post. Fortunately, I have not been involved in PHP for years. – Kekoa Jul 06 '12 at 05:11
  • 3
    -1; `__invoke` was not a necessary step for PHP to "accommodate pseudo-first-class functions"; it was added in the same version as proper [anonymous (lambda) functions](http://php.net/manual/en/functions.anonymous.php). Your explanation of the intent behind the change thus makes little sense. Note also that Python - in which functions are very much first-class, even if the limited lambda syntax makes a lot of functional programming clunky - supports a `__call__` magic method that is analogous to PHP's `__invoke`. – Mark Amery Oct 05 '14 at 19:46
  • 1
    This answer is not "slightly" outdated, it is **totally outdated**. PHP supports first-class functions for a very long time now and the **answer is based** on the fact first-class functions *are not supported*. – jpenna Dec 04 '19 at 18:50
15

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.

jaz303
  • 1,136
  • 9
  • 11
6

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.

sanmai
  • 29,083
  • 12
  • 64
  • 76
  • or you use static const than you can share the cache even between objects or you just reuse the service | class. which yes would have the bad design choice of having a state – Arthur Kielbasa Jan 18 '22 at 11:40
4

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.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
2

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 ?

Lucas Bustamante
  • 15,821
  • 7
  • 92
  • 86
DevWL
  • 17,345
  • 6
  • 90
  • 86
1

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.

Community
  • 1
  • 1
Ali Motameni
  • 2,567
  • 3
  • 24
  • 34