Space Cat, Prince Among Thieves

All Glory to __invoke

Lost in the shiny new features (see: namespaces and closures) PHP 5.3 also added the __invoke method. While not plainly apparent, it is secretly an amazingly useful 'magic method' .

If you're not taking advantage of __invoke, you should be. Why? It provides a uniform execution points for objects that have a doPrimaryAction() style method.

What do I mean by this? Many simple, single responsibility objects have a usage that goes something like:

  1. Instantiate
  2. Set Options
  3. Execute
  4. [Optional] GOTO 2

They are essentially glorified functions with manageable defaults.

So instead of

<?php

public function add($left = 0, $right = 0) {
    return $left + $right;
}

echo add(1, 2); // 3
echo add(3, 2); // 5

We have something like

<?php

class Add {
    public $left;
    public $right;

    public function construct($left = 0, $right = 0) {
        $this->left  = $left;
        $this->right = $right;
    }

    public function doMath() {
        return $this->left + $this->right;
    }
}

$adder = new Add;

$adder->left  = 1;
$adder->right = 2;
echo $adder->doMath(); // 3

$adder->left  = 3;
echo $adder->doMath(); // 5

The example is farcical and stands to illustrate my point. Everything except the doMath() method is setup for the doMath method. You are setting up the environment for doMath to execute.

This pattern of object with one primary action has a downside over a function. You need to know the class name and the invoking method name, which varies from object to object. This dear reader is where __invoke comes in.

__invoke is a magic method allowing us to treat our object as a function or more correctly a closure. It will pass the Callable type hint when added, and makes the object directly invokable via call_user_function.

What this gives us most importantly though is a uniform way across differently intentioned objects to invoke its primary action.

If we update the previous example:

<?php

class Add {
    public $left;
    public $right;

    public function construct($left = 0, $right = 0) {
        $this->left  = $left;
        $this->right = $right;
    }

    public function __invoke() {
        return $this->left + $this->right;
    }
}

$adder = new Add;

$adder->left  = 1;
$adder->right = 2;
echo $adder(); // 3

$adder->left  = 3;
echo $adder(); // 5

You can see the difference in use is slight, but the benefit is the lack of ->doMath. We know this object does one thing, now we have a uniform way to make our objects do that one thing.

I haven't seen a lot of traction with this in the community yet but I remain quite hopeful for this to pick up. I do know however that Aura appears to be a fan of the pattern, with Aura.Dispatcher using it beautifully.

  • Aura.Dispatcher - Aura's Dispatcher has one primary purpose - to dispatch. As such, it is a perfect use case for being invokable.

Comment by: Evert on

Evert GravatarI've been looking for a use-case to use this, and found one with a class that had a very obvious 'primary action', basically a 'commit' after setting up the object for a transaction.

I ended up not using it, because I felt that it defeats the expectation on how it might work. An actual 'commit' is more descriptive, specifically if it's a method that was called on something called $operation (slightly changed the name for clarity).

Also, because the primary action sometimes happens in isolation of 'setting up' the object, it may be unclear to the user that the variable is actually an object.

So I think I will stick to $noun->verb() for now. I need better reasons than just syntactical sugar.

I can see myself use it in the future though (tentatively) in combination with yield.. e.g.: to loop through a Result object.

Comment by: Mike Riley on

Mike Riley GravatarNot to rain on your parade but this is actually just a confusing way to write PHP code, even though you understand it, it won't make sense to anyone else coming in to your project.

The purpose of __invoke is to allow a class to masquerade as a closure, you can read that right on its documentation or on the php dev mailing list... or by pretty much any of the sane implementations out there using this method.

Comment by: Theodore R. Smith on

Theodore R. Smith GravatarThe use case you provided would be best fulfilled by the __toString() magic method.

Comment by: Jesse G. Donat on

Jesse G. Donat GravatarOnly in my exact example of echo-ing, but even then it would be a problem as __toString must return a string - see: http://3v4l.org/gZT8v.

Using __toString for anything that wouldn't explicitly cast to a string would require you to do (string)$adder, which is far less understandable than $adder() and absolutely more error prone.

Comment by: Will on

Will GravatarIt might be nice to use with array_map, see: http://3v4l.org/Cqhho

Email address will never be publicly visible.