Late Static Binding (LSB) forward_static_call()

I finally freed up some time to finish some quick tests for some of the late static binding patches I made and one of them finally made it into head.

The original post I had bringing up this issue was lovingly title Late Static Binding…Sorta. Basically the original patch alone did not provide a means to override a method, forward execution to the parent method and still preserve the ability for static:: to be anything meaningful. It would be turned into the syntactic equivelant of self::. I came up with a few patches to address this. After several rounds of back and forth about the patches the conversation died out with no decision. I finally resurrected the topic and was able to find concensus for the third patch (forward_static_call()).

This weekend I wrapped up a few small tests and sent the patch in and it was subsequently pushed to php 5.3 and php 6.0. Now, this is not at all the way I wanted things to work, in all honesty I think the patch is pretty hokey but unfortunately nobody really spoke up in support of the changes I wanted to make to parent:: in regards to LSB. So I thought it far more important to make sure there was a way to make sure static methods could be overridden while ensuring that access to parent methods would be unabated.

So now, if you want to override a static method and forward execution to the parent class, the safe way (in regards to static inheritance) is shown in Table2 while the (unfortunately) not so safe way is shown in Table1:

<?php

class ActiveRecord
{
    public static funtion loadById($id, PDO $db)
    {
        $table = get_called_class();
        $statement = $db->prepare("
            SELECT * FROM {$table}
            WHERE {$table}_id = ?
        ");
       
        $statement->execute(array($id));
        $column_values = $$statement->fetch(PDO::FETCH_ASSOC);

        if ($column_values)
        {
            $ar = new static($db);
            $ar->column_values = $statement->fetch(PDO::FETCH_ASSOC);
            return $ar;
        }
        else
        {
            return FALSE;
        }
    }
}

class Table1 extends ActiveRecord
{
    public static function loadById($id, PDO $db)
    {
        /**
         * DANGER! the table name will resolve to ActiveRecord
         */
        $ar = parent::loadById($id, $db);

        if ($ar === FALSE)
        {
            return new static($db);
        }
    }
}

class Table2 extends ActiveRecord
{
    public static function loadById($id, PDO $db)
    {
        /**
         * SAFE WAY! the table name will correctly resolve to Table2
         */
        $ar = forward_static_call(array('parent', 'loadById'), $id, $db);

        if ($ar === FALSE)
        {
            return new static($db);
        }
    }
}

?>

This shows an example of the differences between using parent:: and forward_static_call. I really do wish that the behavior of parent:: would just be modified to work like forward_static_call does. It would be alot less awkward and imo closer to what the average oo programmer would expect. I suppose the issue is up for debate if anyone feels like bringing it up on internals, we aren’t stuck with it until php 5.3 rolls :). The patch is even available it just needs some more vocal supporters.

In either case at least there is a way around it now…

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

9 Responses to Late Static Binding (LSB) forward_static_call()

  1. OnyxRaven says:

    Is there a forward() or forward_method_call()… etc to be paired with the forward_static_call()? I havent followed internals to see, and it would be a fair sight better than call_user_func_array(array($class, __FUNCTION__), func_get_args())

  2. mike lively says:

    No there is just forward_static_call() and forward_static_call_array(). Though it would be interesting to have forward_static_call with no paramters just pass through to the parent version with the same parameters,

  3. OnyxRaven says:

    I just had a thought that it would be very useful to have a generic forward_call($callback[, $parameters]) where callback is extended to respect at least the ‘parent’ keyword as above, but could also call other classes/instances just like callbacks do elsewhere, and it would by default grab the current parameters, but could be provided parameters instead.

    the above could be done with call_user_func() if ‘parent’ were a special keyword to it?

    I also had the thought that forward could perceive its own function name, and take the first parameter as an object instance only, so forwarding or delegating a call would more like message forwarding in obj-c or smalltalk … forward_call($object);

    Off topic though, I guess. LSB is important enough by itself :-)

  4. One way to resolve this is define loadById as final and in Table1 class define a method with another name, inside this another method, call self::loadById, well, table name will be resolved correctly

  5. Shouldn’t Table1 and Table2 extend ActiveRecord.

  6. Mike Lively says:

    Yes it should…I have a terrible habit when it comes to late nights, quick blog postings….and extending :P. Thanks for pointing it out.

  7. Nate says:

    Hi Mike, thanks so much for all your efforts, it’s really great to see (non-crippled ;-)) late-static-binding support in PHP.

    I had a question about the forward_static_call functionality being folded into the parent keyword in this context. The internals list makes it *sound like* this has already been added, but I tried it in a nightly snapshot from a few nights ago and it doesn’t seem to work.

    Do you know what the status is on that? Thanks.

  8. Mike Lively says:

    To the best of my knowledge the parent:: keyword has not been changed to work as forward_static_call. In my original email this was my preferred patch and it in all honesty is STILL my preffered patch. However either not enough people have expressed their support for this change and / or most of the core developers do not seem to have an opinion on the matter (or at least haven’t made it known).

    If you would like to see the parent:: keyword changed PLEASE make this known to PHP-DEV.

  9. Mathieu says:

    A few months later, but it seems that parent:: does forwarding by itself now:

    class ActiveRecord
    {
        public static function findByPk($id)
        {
            $class = get_called_class();
            echo "ID: $id\n";
            echo "CLASS: $class\n";
        }
    }

    class Blog extends ActiveRecord
    {
        public static function findByPk($id)
        {
            echo "Hello, world! I am BLOG!\n";
            parent::findByPk($id);
        }
    }

    Blog::findByPk(15);

    with a recent PHP 5.3 checkout results in:

    Hello, world! I am BLOG!
    ID: 15
    CLASS: Blog

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>