Home > Uncategorized > PHP 5.2, late static binding, get_called_class() and $self = new self()

PHP 5.2, late static binding, get_called_class() and $self = new self()

Early on in Septuro’s development, well prior to the name being adopted, I had a tough challenge dealing with the dataTemplates. Chris Webb, one of the Septuro founders, was helping me with this particular issue. In the abstract dataTemplate, we wanted to set up a factory method make() that just pumped out an instance of the class that was called. The issue that we ran into involved late static binding.

class abstract_dataTemplate {
    static function make() {
        return new self();
    }
}
class stringTemplate extends abstract_dataTemplate {

}
stringTemplate::make();

In this example, we called stringTemplate::make(), but since make() is defined within abstract_dataTemplate, the function will always return an instance of abstract_dataTemplate. We found out that in PHP 5.3, late static binding will be better supported and come with a function get_called_class() that will return the name of the called class, rather than the current class. Unfortunately, we run PHP 5.2 on our servers and are unable to use this function. I considered using debug_backtrace(), file() and preg_match() to mimic the functionality, but this won’t work if there is more than 1 instance per line of a late static bind call.

I looked at php.net to see if anyone else found a good solution, but everyone seemed to be stopped at the same point I was. Then I realized I could use static class properties to keep a count of how many times a match has already been found on the current line. This would let me support multiple calls per line without issue – with each call returning the exact class that called it. So I took a function listed in the php.net comments and modified it up to make use of this static class.

if(!function_exists('get_called_class')) {
    class class_tools {
        static $i = 0;
        static $fl = null;

        static function get_called_class() {
            $bt = debug_backtrace();

            if(self::$fl == $bt[2]['file'].$bt[2]['line']) {
                self::$i++;
            } else {
                self::$i = 0;
                self::$fl = $bt[2]['file'].$bt[2]['line'];
            }

            $lines = file($bt[2]['file']);

            preg_match_all('
                /([a-zA-Z0-9\_]+)::'.$bt[2]['function'].'/',
                $lines[$bt[2]['line']-1],
                $matches
            );

            return $matches[1][self::$i];
        }
    }

    function get_called_class() {
        return class_tools::get_called_class();
    }
}

We now have perfect support of get_called_class() in PHP 5.2, and we can use the factory method make() inside the abstract dataTemplate, rather than in all of the derived templates.

Categories: Uncategorized Tags:
  1. October 8th, 2009 at 23:05 | #1

    Awesome! Great help, thanx!

  2. November 24th, 2009 at 23:48 | #2

    You can add the following code after line 23 to determine the called class if the function was called in call_user_func or call_user_func_array:

    if (array_key_exists(3, $bt)
    && array_key_exists(’function’, $bt[3])
    && in_array($bt[3]['function'],
    array(’call_user_func’, ‘call_user_func_array’))
    ) {
    if (is_array($bt[3]['args'][0])) {
    $toret = $bt[3]['args'][0][0];
    }
    }

  3. December 1st, 2009 at 07:19 | #3

    Thank you very much. I was trying to figure out how to do this and then i find you. I make my own improved version of this function based on your post, comments on php.net get_called_class function and Jrgns comments. Awesome. Thx again.

    My function works with instance calls, self calls, call_user_func(_array)? calls (thx Jrgns) and return false when called outside a class

  4. February 19th, 2010 at 02:23 | #4

    Made my day! Thank you!

  5. February 22nd, 2010 at 18:05 | #5

    There is a bit of a problem with this code that makes it not exactly equal to PHP 5.3’s get_called_class(). The get_called_class() works when calling the static function with the prefix classname, but it doesn’t work when called from within another static method of that class with self:: For example…

    class Account extends Generic {

    }

    class Generic {
    public static function getDBName_static() {
    return strtolower(get_called_class());
    }

    public static function this_fails() {
    return self::getDBName_static();
    }
    }

    When I call Account::getDBName_static(); it properly returns “account”. But, when I call self:: this_fails(); it returns “self”, when it should be returning “account”. This is a odd use-case, but since I’m extending my base class with other classes I cannot define the class name as in my first example, I must use self:: in my static methods so they extend properly. I don’t have time to fix it right now, but I will have to fix it eventually, just wanted to see if anyone has noticed this, and/or anyone has fixed this problem already.

    Cheers!

  6. February 22nd, 2010 at 18:26 | #6

    Also found that calling this function twice fails. You need to reset the values of $i and $fl between each call. Sounds like a fundamental design problem, I haven’t the time to dive in right now, but I’m not sure why $i and $fl need to be static as you’re not calling this function recursively. So why aren’t they just defined and unset at the start and end of this function? :\ I did that quickly and it seemed to work ok. I am very happy someone took the time to do this though, thanks a LOT, just found some problems with it. :)

  7. visual77
    February 22nd, 2010 at 20:48 | #7

    @Andrew Farley

    It has been awhile since I wrote this function, but I believe the purpose of the static variables was in a case where get_called_class() was called in such a way that it backtraces to the same line. Without the static variables, it would return the first class both times, but with the static variables, it would recognize that this is the second scan of the same line.

    Line 13 / 14 should reset the variables if it is not the same as last time, but it sounds like it is not working properly.

  8. visual77
    February 22nd, 2010 at 20:53 | #8

    Andrew Farley :

    There is a bit of a problem with this code that makes it not exactly equal to PHP 5.3’s get_called_class(). The get_called_class() works when calling the static function with the prefix classname, but it doesn’t work when called from within another static method of that class with self:: For example…

    class Account extends Generic {

    }

    class Generic {
    public static function getDBName_static() {
    return strtolower(get_called_class());
    }

    public static function this_fails() {
    return self::getDBName_static();
    }
    }

    When I call Account::getDBName_static(); it properly returns “account”. But, when I call self:: this_fails(); it returns “self”, when it should be returning “account”. This is a odd use-case, but since I’m extending my base class with other classes I cannot define the class name as in my first example, I must use self:: in my static methods so they extend properly. I don’t have time to fix it right now, but I will have to fix it eventually, just wanted to see if anyone has noticed this, and/or anyone has fixed this problem already.

    Cheers!

    This could be fixed with a simple while loop after the backtrace, where it continues to seek back while the class name is self.