Friday, November 13, 2015

Wielding PHP magic with the Callable Object Pattern

The PHP magic method __invoke provides a powerful way to encapsulate functionality while separating state from results and errors.
I'm no warlock. I eschew the magic methods PHP offers in favor of explicit method signatures.  I feel this approach has served we well over the years. But I've taken a liking to __invoke recently.

If you don't know __invoke, it allows you to call your object as if it were a function. Wikipedia calls these "function objects" and says:
A function object is a computer programming construct allowing an object to be invoked or called as if it were an ordinary function, usually with the same syntax (a function parameter that can also be a function).
Here's an example, just to get the concept down:

<?php
class Yell {
    public function __invoke($message) {
        echo $message;
    }
}
$yell = new Yell();
$yell('Hello');

Notice that $yell is an object, but is called as if it were a function.

Why use function objects?


Decorating

Since you're dealing with an object, you have the opportunity to configure how the object will behave before you invoke the function:

<?php
class Yell {
    public function setEnclosingTags($before, $after) {
        $this->before = $before;
        $this->after = $after;
    }
    public function __invoke($message) {
        echo $this->before . $message . $this->after;
    }
    private $before = '';
    private $after = '';
}
$yell = new Yell();
$yell->setEnclosingTags('', '');
$yell('hello');  // outputs hello

Were you to write a function for this behavior, the signature would look like this:

<?php
yell(string $message, string $begin = null, string $end = null): void

For such a simple function, this signature would suffice. But this is only a simple example. Imagine your state setup was more complicated. You might end up with a method having 6 or more arguments. That's just unmaintainable. Object methods make for much cleaner code.

Separating errors from results

Let's take this a little further. Suppose we want to know the total number of characters yelled and whether there was an error yelling them:

<?php
class Yell {
    public function setOutputStream($stream) {
        $this->stream;
    }
    public function __invoke($message) {
        $bytes = fprintf($this->stream, $message);
        $this->error = (strlen($message) !== $bytes);
        return $bytes;
    }
    public function writeError() {
        return $this->error;
    }
    private $stream = STDOUT;
    private $error = false;
}
$yell = new Yell();
$bytes = $yell('hello');
if ($yell->writeError()) {
    die('Whoops, only managed to yell this many bytes: ' . $bytes);
}

Clean. The function result comes from the invocation. The error state comes from the object. Perhaps this is the best of both worlds.

Why use magic when an explicit method will do?

I was of this school for a long time. My past self would instead have defined an explicit method:

<?php
class Yell {
    public function output($message) {
        echo $message;
    }
}

I've come to appreciate syntactic sugar that clarifies my code. To me, the value of PHP function objects lay in being able to clearly pass the object to any method taking a callable.

<?php
$yell = new Yell();
$yell->setEnclosingTags('[', ']');
$words = [ 'goodbye', 'cruel', 'world' ];
array_walk($words, $yell);
// vs the awkward: array_walk($words, [ $yell, 'output' ]);

The Callable Object Pattern

Every function could be replaced by a function object. That kind of astronaut architecture is dangerous, and I plan to stick with explicit methods in the main. Yet, I feel there are two use cases for the Callable Object Pattern that avoid becoming anti-pattern-like.

Decorators

Two properties of decorators suggest to me they'd be good for following the Callable Object Pattern:
  1. They usually have a lot of setup. For this reason, decorator classes either develop long-winded function signatures or require dedicated configuration objects. Instead, the callable object could have all the setup methods on it.
  2. They may be run multiple times, with slight variations on setup. Callable objects have a relatively large setup (the new call, then any setup methods) for the relatively small pay-off of function invocation. However, if the invocation needs to be made several times, with little change in the setup, the initial setup pays off.

Higher-order functions

This use case started my deep consideration of __invoke. In the code I'm currently writing, which is fixing a legacy system from terrible, procedural PHP 5.3-ish code to PHP 5.6 code, I needed to do several things in several different places:
  • Copy files from network locations, using different protocols, but retrying if the copy fails
  • Capture any PHP emitted errors and report them
These aren't related pieces of code. Both have several different unrelated use cases, but do intersect in a few places. So, I want the capturing to work on any arbitrary block of code. I want the copy-with-retry to work on any arbitrary copy command. I need to use them in many different, separate, uncoupled classes as well as together.

I settled on a higher-order function implementation that goes like this:

<?php
use Haldayne\Fox\Retry, Haldayne\Fox\CapturingErrors;

$retry = new Retry(
    $capture = new CapturingErrors(
        function ($src, $dst) {
            return copy($src, $dst);
        }
    )
);

$capture->setCapturedErrorTypes(E_ALL|~E_STRICT);
$retry->setAttempts(5);

$result = $retry('ssh.sftp2://user@host/file', 'file');
if (false === $result) {
    die($capture->getCapturedErrors()->implode(PHP_EOL);
}
Taking those inside-out. CapturingErrors runs the function inside of it, but wraps the call in an error handler that spools any triggered errors (except for E_STRICT). Retry runs the function up to 5 times, exponentially backing off in case of failure. When the whole pipeline resolves, I check the return code and can display the captured errors.

Each individual class using the Callable Object Pattern only knows it receives a function. It doesn't care what that function is. In this use case, that function happens to be another class implementing the Callable Object Pattern.  This leads to greater composability, because __invoke is treated as an implicit interface.

What do you think?

I've made a case for using the Callable Object Pattern in a couple of specific cases. Do you agree? Do you use __invoke in this way? Or other ways?

0 comments:

Post a Comment

Share your thoughts!