All Glory to __invoke
- Comments:
- 5
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:
- Instantiate
- Set Options
- Execute
- [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
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
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
Comment by: Jesse G. Donat on
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.