Static Late Binding in PHP
PHP is flawed. Deeply, immensely so. At first, I made the naive assumption that PHP 5.1 had fixed a lot of the language level failings of its predecessor. But I was wrong. Because the language itself lacks a specification or clear documentation of the internal semantics, issues like the static late binding semantics are generally only existent inside the heads of the core dev team. Developers are usually left to their own devices to discover why something doesn’t work the way they expect.
Understanding the quagmire of PHP and why it is the way it is, is better left to another day. These notes simply focus on one particularly broken aspect of the language and the questions around whether or not it has been fixed.
Let’s start with a simple example and imagine a hypothetical collections or active record API that wraps a persistent
data source. The exact details are not important except for the fact that our select method
expects the name of the table to query against (or at least, a string that can be inflected into a compatible table name). What we’re interested in is the high level model - we want an API that wraps each collection in the data store with a static finder that can be used to return results directly:
abstract class StaticFinder {
public static function findByKey($key, $value) {
$store = Registry::getStorageAdaptor();
$store->selectByKey(get_class(), array($key=>$value);
return $store->getResults();
}
}
It would be intuitive to assume, based on the language conventions of PHP, that we could do the following:
class Entries extends StaticFinder { }
$entries = Entries::findByKey('author', 'maetl');
But no, this won’t work at all. Running this code would produce results approximating the following erroneous SQL:
SELECT FROM static_finder WHERE author='maetl'
It turns out that when get_class
is called from a static method it will resolve with the name of the class where it is defined, not the child class that is actually inheriting the method at runtime. Working with regular class instances is fine - we can get the name of the child class simply by passing the self reference: get_class($this)
. But static classes seemingly do not have an inheritance chain
that we can access at runtime. get_class(self)
doesn’t work; neither does __CLASS__
. Numerous developers have assumed, attempted, and failed. The problem is well documented. Even Zend themselves ran headlong into this issue when designing the DB package for the Zend Framework. An early prototype of their code actually worked around it using debug_backtrace
to retrieve the name of the
static class executing the inherited method. Needless to say, that had to be scrapped because the performance was atrocious, to say nothing of the disgusting aesthetics of such a hack.
This concept is known as “late binding” because of the way the dynamic language runtime works. Essentially,
when a PHP class is first defined, the engine creates an identifier table linking up the static methods, but when a child class is defined inheriting these static methods, the engine neglects to annotate the original table with the updated binding information. So no matter where a static method is called, it assumes its runtime scope from the class where it was first defined. Since no PHP language specification exists, core developers can claim that this behaviour is absolutely fine. “Don’t use inherited static methods” is their usual response to requests for this feature. That’s fine from an object oriented viewpoint, but PHP is a multi-paradigm language. From any reasonable perspective, the symmetry of static inheritance in PHP is broken, regardless of whether you believe the use of static methods to be a good design choice or not.
A glimmer of hope is in sight with support for static late binding currently checked in the HEAD branch of the engine, and it’s slated to become part of the 5.3 distribution. The problem being that this current implementation is still infested with quirks and confusions. I’m not feeling particularly confident that this makes the PHP language any more self consistent or clean, although it will solve the problem for those who just want static finder methods in their active record style APIs.