Monday, December 20, 2010

Life without static in PHP

The problem with static members in PHP is the poor initialisation capabilities. The initial value of a static property can only be a literal or a named constant:
class MyClass {

   static $prop1 = 'hello'; //correct

   static $prop2 = PHP_VERSION; //correct

   static $prop3 = 1 + 2; //incorrect

   static $prop4 = my_func(); //incorrect

   static $prop5 = new AnotherClass(); //incorrect

}



The same problem exists for non-static properties too, but the constructor is a dedicated place to initialize non-static properties. But since we don't have Java-like static constructors in PHP there is no place to do static property initialization. In a lot of cases people do it by putting the assignment statements after the body of the class, but this method is very ugly: it requires you to set the visibility of your static members to public, and breaks OO style.

An other option is using singletons. It's surely a better way to use singletons in cases when you want to write a class with only static members. Otherwise a class with only static members is also an implementation of the singleton pattern, but with the latter method you loose the flexibility of the property initialization. So let's look at the classical singleton implementation in php (just for a quick reminder):
class MyClass {

    private static $_instance;

    /**
     * @return MyClass
     */
    public static function inst() {
        if (NULL === self::$_instance) {
            self::$_instance = new MyClass;
        }
        return self::$_instance;
    }

    private function __construct() {
        // empty private constructor
    }
}

It's noteworthy that when you write a singleton class instead of using the static keyword all the time, then the method calls on your singleton instance are not static calls, therefore you can simulate late static binding without php5.3.

An other benefit of singletons is that you can easily refactor them to be object pools. Object pools are great if you want to control the number of instances of a class, or if you want to implement an enum-like type. Let's see an example:
class MyClass {

    private static $_instances = array();

    public static function inst($name = 'default') {
        if ( ! array_key_exists($name, self::$_instances)) {
            // initializing self::$_instances[$name]
        }
        return self::$_instances[$name];
    }

    private function __construct() {
        // empty private constructor
    }

}

In this example the instance to be returned is defined by the client via the $name parameter. Note that it has got a default value therefore you can still use MyClass::inst() as you used it previously. This way with a bit of luck you can turn your singleton into an object pool without breaking even a single line of the existing code!

Things get complicated when you have both static and non-static members in the same class. First of all in such a case please review your design and make sure this is what you want. Having both static and non-static members in the same class is a not-so-pure OO thing, and in some languages (eg. Scala) it's explicitly forbidden.

But let's see what happens in this case: you have static members and non-static members, but you don't want to use the static keyword so you create a singleton instance and simulate static calls using you singleton. So some of your members can only be used via the singleton instance, and the others can be used via the other instances. An example:
class MyRecord {

    private static $_instance;

    public static function inst() {
        if (NULL === self::$_instance) {
            self::$_instance = new MyRecord;
        }
        return self::$_instance;
    }

    /**
     * static method. It should only be called using MyRecord::inst()
     *
     * @param int $pk
     * @return MyRecord
     */
    public function get_by_pk($pk) {
        // retrieving the appropriate database row, creating and returning 
        // a MyRecord instance for it
    }

    public function delete() {
        // deleting your active record instance
    }

    public function save() {
        // saving your active record instance
    }

}

As you can see, this skeleton active record implementation shows the problem: the get_by_pk method is theroretically static, but because of our singleton implementation the static keyword is not used, it's not separated from non-static methods by any keywords and nothing guarantees that it will be used properly. It can be marked in doc comments but it's still not good, your code is still messy.

So this code has significant disadvantages, but I think you can live with it in some situations. About one year ago I created a lightweight active record implementation like this built on the kohana database module, and it did it's job in some applications. The solution was acceptable for my actual needs, but it was not good - this is why I didn't share it :)

That's all for now, comments are welcome.

2 comments:

  1. Hi.

    I also hate that you cannot assign non literal values in your class definition, and I can't why shouldn't we have some lazy run-time initialization for the variables, just like it works now for the constants:

    <?php

    class Foo{
    const bar = BAZ;
    }

    define('BAZ', 'Foo');
    echo Foo::bar;

    this will output Foo, because the BAZ constant will be only resolved(that will be after we defined it in the runtime) when the bar constant is referenced.

    But I don't like your idea also, for the following reasons:
    - I don't like static/singletons in general, they are bad for testing, they can be useful, but I think that they are greatly overused in PHP, mostly for performance reasons, which I think isn't worth it.
    - you didn't thought about inheritance: without late static binding, you cannot reference the current object from a static function. So for example if you try to extend the MyRecord with a CustomRecord, you have to manually override the inst() method, to use "new CustomRecord" else your inst will instantiate the MyRecord, not the CustomRecord, but of course with the LSB you can fix that.

    For your builder class, you should be aware, that there are ways(__clone, unserialize/__wakeup) to instantiate a class with private constructor, so that only saves you from the accidents.

    I don't like how the Scala guys solved the static member stuff, see
    http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-3
    but I don't thing that overusing the singletons is more OOPish, than allowing the mixed usage of static and non-static members in a class.

    I liked fabpots slides on the new possibilities with php5.3, you should read it if you didn't do it yet:
    http://www.slideshare.net/fabpot/design-patternrevisitedphp53

    Tyrael

    ReplyDelete
  2. What about object-oriented programming instead of sticking to static and singletons?

    ReplyDelete