/usr/portage

The reincarnation of static classes 0

The coming version 5.3 of PHP could be summarized under reincarnation of static classes. Two of the biggest problems with static classes, late static binding and missing method-interceptor for static methods, are fixed now (ok, interceptors for static values are missing, what about __setStatic() and __getStatic()?) but nevertheless it is more than a first step. Also the nice to have feature, to call static class from variables containing a the name as a string is implemented now.
The long overdue feature of namespaces has also been included in the current tarballs of PHP 5.3.

Namespaces

Namespace support in PHP 5.3 is pretty unobtrusive. You declare one namespace per file and you can import namespaces. The declaration command is – who would have think about that – “namespace”. So something like this declares class Foo in the namespace “Bar”:

<?php
namespace Bar;
class Foo
{
	public function fooMethod()
	{}
}

In another file you could do now “import Bar::Foo;” and use class Foo from
that point on as like it would be in the global scope.

Namespaces and autoloading

If you have defined an autoload-method, either via spl_register_autoload() or by just defining the __autoload()-method, the complete string of your class including namespace is supplied as the first argument. So if your structure your code the PEAR way (Zend framework does it that way), you just replace the str_replace('_', DIRECTORY_SEPARATOR, $class_name)-snippet with a str_replace('::', DIRECTORY_SEPARATOR, $class_name). That’s pretty easy. There are some edge-case which I would consider a bug: if you have a constant – say DIRECTORY_SEPARATOR – declared in your namespace “Foo” and then import “Foo” in your global scope, the constant Foo::DIRECTORY_SEPARATOR is silently ignored. I would expect, that an fatal error is thrown because an attempt to overwrite constants has been found. Same for functions. Only in case of classes the expected fatal error is thrown, stating, that a conflict was found.

Another, deeper problem: the names “Interface” or “Static” are not allowed as a name for classes, methods or functions. This is pretty annoying. I was used to work with names like Builder_Strategy_Interface, but I cannot use that any longer, because I cannot declare Builder::Strategy::Interface. This should definitly be fixed, but I guess I have the correct feeling, that fixing this requires changing basic parts of PHP.

As a sidenote: having a constant NAMESPACE_SEPARATOR would be pretty helpful (#43046).

Static interceptor

A simple example:

<?php
class MyStatic
{
	public static function __callStatic($method, $arguments)
	{
		echo $method . "\n";
	}
}
MyStatic::myMethod(); // String "mymethod" is echoed

Static interceptors work pretty well. The only problem I found is, that method names are converted to lower case (#43047), which is different to the normal __call() (indeed, I would expect, that method names are not converted at all, just leave them).

Late static binding

Late static binding means, that a set of static methods is mapped to their descendants, during the execution. To make it simpler: from now on, inheriting from static classes makes sense the first time. You can extend from a class which implements a singleton and the singleton “just works”. The PHP developers introduced a new function name get_called_class() which returns the name of the current call scope (different to the magic constant __CLASS__, which returns the location of the definition). Take a look at the example from lsb_005.phpt (from the late static binding test suite):

<?php
class TestClass {
	public static function getClassName() { 
		return get_called_class();
	}
}
class ChildClass extends TestClass {}
echo TestClass::getClassName() . "\n";
echo ChildClass::getClassName() . "\n";

The first call returns TestClass, the second ChildClass. Just for the sake of completeness, here is an example of an inherited singleton:

<?php
class Mother
{
	protected static $_instances = array();
	protected function __construct()
	{}
	protected function __clone()
	{}
	public static function getInstance()
	{
		$class = get_called_class();
		if (!isset(static::$_instances[$class])) {
			static::$_instances[$class] = new static;
		}
		return static::$_instances[$class];
	}
}
class Child extends Mother
{}

Mother::getInstance() would return one single instance of “Mother”, Child::getInstance() would return one single instance of “Child”. HINT: Just assume class Mother and Child would be namespaced, the get_called_class() would return ::Mother.

Call static classes by variables

Think on a delegation based system, where your configuration returns a string of the class to manage users (because you have users stored in a MySQL database and in a LDAP-server). So you do something like that:

<?php
$config  = MyRegistry::get('config');
$manager = $config->manager->user;
$user    = call_user_func_array(array($manager, 'createUser'), array());

From now on, the call_user_func_array()-trick is not needed anymore. You can call the manager class more straighforward:

<?php
$config  = MyRegistry::get('config');
$manager = $config->manager->user;
$user    = $manager::createUser();

This is a massive improvement, as call_user_func_array() is known to be really slow. I’m currently working on a component, which encapsulates business logic and data storage once and for all. The features coming with 5.3 will make things much easier.

The features of PHP 5.3 are pretty well defined now. I would propose doing a few extras. Patching the autoload system, so that the single parts of the namespace is passed in a list as the second argument. The other thing would be to better check scope violation, as described above.

For Gentoo users: I throwed together a pretty hacky ebuild for a current nightly build of PHP 5.3. Just take, do not look at it and try PHP 5.3 out. It is really worth doing it.

Filed under , , & no comments & no trackbacks

Trackbacks

Trackback specific URI for this entry

No Trackbacks

Comments

No comments

Add a Comment & let me know what you think