Late Static Binding in PHP

I have just sent an e-mail to the internals list for PHP that implements late static binding. Many of you probably remember hearing about all of this a few months ago not too long after the Zend Framework webcast where they showed off an API that appeared to be somewhat impossible to create short of some poor hacks with terrible performance.

A good summary of the problem this patch fixes can be found on Joshua Eichorns blog: http://blog.joshuaeichorn.com/archives/2006/01/09/zactiverecord-cant-work/

So, for a more detailed summary :) of why this doesn’t work yet.

Take the following code:

<?php
class Foo {
    protected static $staticVar = 'Foo';
    public static function test() {
        echo self::$staticVar . "\n";
    }
}
Foo::test();

class Bar extends Foo {
    protected static $staticVar = 'Bar';
}
Bar::test();
?>

The above code seems fairly straight forward. What happens behind the scenes is that a Foo class is created and has the static function added to it’s function table (among other things.) At that time the class Foo is also added to the Foo::test function struct that is added to that table. So, the when Foo::test() is called, the engine looks for the Foo class, finds the test function in Foo’s function table and executes it.

So, when Bar is defined, it creates the Bar class. Then it notices that Bar extends foo and adds all of Foo’s functions, properties, and what not to the function table, property table, etc. Now, when this is done no additional changes are made to the Foo::test function struct to show that there is a new class (Bar) that can call it. So when you call Bar::test() it will find Bar, and find Foo::test but when the test function is ran no information is passed to the function to let it know that Bar was the class that called it. To put it more succinctly, Foo and Bar both know about ::test, but ::test has no idea who Bar is. The function will always execute in Foo’s class scope. So self:: always calls Foo::. Now, this isn’t always a problem. The only time it becomes an issue is if you have an inherited static function that wants to call another static function, static property, or constant that has been redefined in a child class.

The change I made basically allows the engine to store the class calling the static function. The caller can then be accessed using ‘static’ (as opposed to self.) This should open the door to many other framework implementation possibilities (First thing that comes to mind for me is a Rubyish Active Record object.)

So, if you take part in the PHP-DEV mailing list and you want late-static-binding let them know.

Share
This entry was posted in PHP. Bookmark the permalink.

8 Responses to Late Static Binding in PHP

  1. mike.lively says:

    A quick little update. My patch did its job in a not so optimal way in regards to the op_array structure. Long story short, I did it wrong once, did it a little bit better the second time, then dmitry rewrote the patch and did it in what I presume to be the right way.

    So, long story short…a patch for late static binding in PHP exists and is out there. I am actually in the process of putting together some use cases to support it’s inclusion. If you would like it to be included in the core of PHP I would highly recommend you make your opinions known.

  2. Jochem Maas says:

    I’d love to see late static binding (L.S.B.) in the engine. I ran into the limitation when building a generic ‘RecordSet’ type of class back in Nov’2003.

    At that time I didn’t know what the functionality I was looking was even called – to be totally honest I didn’t even write the class initially, a friend of mine did it – since then my coding skills have become much better and I have been able to extend, rewrite and bugfix the codebase in question (constantly hoping for L.S.B.).

    The friend in question wrote the last incarnation of the interbase/firebird extension – anyone who uses it can atest to it’s quality (the chap in question knows where his towel is, so to speak). My friend considered the lack of L.S.B an omission (at the least) – I still tend to agree!

    rgds,

  3. Well I’m glad that someone is working on this.. I’m exactly in the middle of implementing a php ActiveRecord and I just ran into this compile-time binding brick wall. Any word on this patch’s acceptance?

  4. mike.lively says:

    The patch’s current incarnation is at a bit of a stand still right now. It is not an incredibly high priority for most of the core developers (save one MAYBE two) and I haven’t had any time to work more on it. It definantly hasn’t been forgotten and when I have more time I will probably bring it up again.

    What is needed now is testing and use cases. So if you can contribute some real world use cases then feel free to post them here, or even email them to me directly: m () digitalsandwich ! com

  5. Alex Dean says:

    Is there a typo in your example code? Foo::test() echos Foo::$foo, but that doesn’t exist. Foo::$staticVar does, though.

  6. mike.lively says:

    As a matter of fact there was. It has been fixed. Thanks.

  7. Joseph North says:

    Any updates on this patch? I’ve looked around the dev lists, and it looks like it might not even make it into PHP6…

    As for a use case, I would like to extend a base database class and use an inherited loadById() method:

    class DataTable {
    protected $table;

    public function __construct() {
    $this->table = “default”;
    }

    public static function loadById($id) {
    $class = get_class();
    // return new $class($id) can’t work
    }
    }

    class BlogPost extends DataTable {
    protected $table = “blog”;
    }

    BlogPost::load();

  8. rvwoens says:

    To solve this problem when used in a normal method, i use this code:

    class baseclass() {
    function getInfo() {
    // oops PHP bug-> self is determined at compile time, not at runtime.
    // even for derived classes, self is always basevideodriver
    // return self::getDriverInfo();
    // also get_chass($this)::getDriverInfo() does not work.. 2nd PHP bug
    // so we need the help of evil eval()
    eval(‘$rv=’.get_class($this).’::getDriverInfo();’);
    return $rv;
    }
    }

    class .. extends baseclass {
    static function getDriverInfo() {
    this is the one that needs to be called…
    }

    PHP is such a quircky language. Maybe we should ignore the current louzy php dev team and start a fork of php called “real-php” or so.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>