Blue Static

Bending PHP to Your Will

Posted on August 6, 2011 at 23:33 UTC, filed under the category Uncategorized. Tags: PHP,

One of the best features of Objective-C is protocols. These are akin to PHP and Java interfaces, but have one huge advantage: you can mark certain methods as required or optional. This is useful in implementing the delegate pattern because if you do not care about a certain method, you can simply not implement that method. In PHP, this is not possible with interfaces: you must implement all the methods of an interface in order to implement the interface. While this is fine and makes sense in most cases, it’s not always convenient.

So I set off to find a way to create protocols in PHP. The language is fairly dynamic, so I was able to come up with a solution called WeakInterface. Before jumping into the implementation details, let’s see how it works in practice:

First you define your interface as you would normally:

interface AllOptionalInterface {
  public function DoSomething();
  public function DoSomething1(Closure $arg);
}

The class that implements this only cares about the DoSomething method, so it implements it:

class AllOptionalImpl {
  public function DoSomething() {
    echo 'I did something!';
  }
}

Note that this class isn’t said to implement this interface because it does not implement all the methods. Now in the code that is going to call this interface, you do the following:

$delegate = new WeakInterface('AllOptionalInterface');
$delegate->Bind(new AllOptionalImpl);
$delegate->DoSomething();
$delegate->DoSomething1(function() { echo 'Wow!'; });

This code will output “I did something!”. Let’s go line-by-line. First, $delegate is being created as an instance of WeakInterface, whose constructor takes the name of the interface to “implement.” Then the interface is bound to an instance of the implementation. And then finally two methods are called on the interface, and the one with an implementation is actually called. Calling DoSomething1() is a no-op.

I mentioned above that in Objective-C you can mark some methods as required, too. And that’s done with a docstring when using WeakInterfaces:

interface OneRequiredInterface {
  public function DoSomething();
  /** @required */
  public function DoAnything();
}

If you failed to implement DoAnything(), the call to WeakInterface::Bind() would throw an exception. But what if the number and type hints of the arguments of an implementation do not match that of the interface? WeakInterface has that base covered, too. Bind() will also check the parameters of all method implementations to make sure that the signatures match the interface’s.

That’s WeakInterface in a nutshell, but how does it actually work? It’s easiest to start with a callstack. So when $delegate->DoSomething() is called, this is how WeakInterface actually invokes the implementation:

#	Time	Memory	Function	Location
1	0.0033	975328	hoplite\base\WeakInterface->DoSomething( )	../example.php:14
2	0.0033	975856	hoplite\base\WeakInterface->__call( )	../weak_interface.php:0
3	0.0033	975856	hoplite\base\internal\MethodImp->Invoke( )	../weak_interface.php:84
4	0.0033	976568	ReflectionMethod->invokeArgs( )	../weak_interface.php:145
5	0.0033	976608	AllOptionalImpl->DoSomething( )	../example.php:0

WeakInterface implements the magic PHP method __call(), which is invoked whenever a method call is performed and no method is found by the runtime. It captures this and then forwards the invocation on to an implementation helper MethodImp, which then uses reflection to finally invoke the proper implementation.

Going deeper, when a WeakInterface is constructed, the interface passed to it is reflected and the method list is walked. For each method, a MethodImp is created. When the WeakInterface is bound, that set of MethodImps is looped over, checking that any required methods are implemented and that any implementations match the interface’s method signature. At call time, the method name is looked up in the MethodImp table and the arguments are forwarded to the actual implementation.

One note on performance: due to the number of intermediate method calls, using WeakInterface is roughly four times slower than a plain method call. For most applications, this should not be significant at all; but for performance-critical code, you should steer away from WeakInterface.

The code is available here along with a unit test.

Comments

Comment by Mathieu on 2011-08-07 06:49:13 +0000

Performance is once thing, this also messes up your type system: $delegate can no longer be considered an implementation of AllOptionalImpl. Both logically and in code: ($delegate instanceof AllOptionalImpl == FALSE). This will prevent you from coding against the interface (by using typehints).

Comment by Robert on 2011-08-07 16:00:10 +0000

Yes, it will affect your ability to use typehints. But I wrote this primarily for implementing the delegate pattern, so that’s not a large issue. Generally, though, if you are using RTTI (run-time type identification) as a core part of your logic, you have a class design issue. Also note that you can still get the object that WeakInterface is proxying for by calling WeakInterface::get(), which will return the implementing object.