19

I've got an Abstract PHP superclass, which contains code that needs to know which subclass its running under.

class Foo {
    static function _get_class_name() {
        return get_called_class();
        //works in PHP 5.3.*, but not in PHP 5.2.*
    }

    static function other_code() {
        //needs to know
        echo self::_get_class_name();
    }
}

class Bar extends Foo {
}

class FooBar extends Foo {
}

Bar::other_code(); // i need 'Bar'
FooBar::other_code(); // i need 'FooBar'

This would work if I called the function get_called_class() -- however, this code is going to be run in PHP version 5.2.*, so that function is not available.

There's some custom PHP implementations of get_called_class() out there, but they all rely on going thru the debug_backtrack(), parsing a file name & line number, and running a regex (as the coder is not aware that PHP 5.2 has reflection) to find the class name. This code needs to be able to be run with php, ie. not only from a .php file. (It needs to work from a php -a shell, or an eval() statement.)

Ideally, a solution would work without requiring any code to be added to the subclasses… The only potential solution I can see though is adding the following code to each subclass, which is obviously a disgusting hack:

class FooBar extends Foo {
    static function _get_class_name() {
        return 'FooBar';
    }
}

EDIT: Wait, this doesn't even seem to work. It would've been my last resort. Can anybody think of something similar to this solution that'd get me the required functionality. Ie., I'm willing to accept a solution that requires me to add one function or variable to each subclass telling it what its class name is. Unfortunately, it seems that calling self::_get_class_name() from the superclass calls the parent class' implementation, even if the subclass has overridden it.

hakre
  • 178,314
  • 47
  • 389
  • 754
Kenneth Ballenegger
  • 2,900
  • 2
  • 17
  • 25

9 Answers9

10

In reality it is often helpful to know the actual called (sub)class when executing a superclass method, and I disagree that there's anything wrong with wanting to solve this problem.

Example, my objects need to know the class name, but what they do with that information is always the same and could be extracted into a superclass method IF I was able to get the called class name. Even the PHP team thought this was useful enough to include in php 5.3.

The correct and un-preachy answer, as far as I can tell, is that prior to 5.3, you have to either do something heinous (e.g. backtrace,) or just include duplicate code in each of the subclasses.

rnr Tom
  • 116
  • 1
  • 3
6

Working solution:

function getCalledClass(){
    $arr = array(); 
    $arrTraces = debug_backtrace();
    foreach ($arrTraces as $arrTrace){
       if(!array_key_exists("class", $arrTrace)) continue;
       if(count($arr)==0) $arr[] = $arrTrace['class'];
       else if(get_parent_class($arrTrace['class'])==end($arr)) $arr[] = $arrTrace['class'];
    }
    return end($arr);
}
alda78
  • 61
  • 1
  • 2
  • 1
    while absolutely horrifying, +1 for the effort of providing a runtime solution. i guess if someone needs to desperate support php <= 5.2 .. eh – Garet Claborn Jun 10 '14 at 03:09
  • Unfortunately your implementation does not work as it should. Consider the following example: `class A { public static function foo() { return getCalledClass(); } } class B extends A {} echo B::foo();`. The expected result is 'B' (which is what you get, when you use `get_called_class()`), but here we get 'A' instead. – xemlock Dec 08 '14 at 11:03
3

This is not possible.

The concept of "called class" was introduced in PHP 5.3. This information was not tracked in previous versions.

As an ugly work-around, you could possibly use debug_backtrace to look into the call stack, but it's not equivalent. For instance, in PHP 5.3, using ClassName::method() doesn't forward the static call; you have no way to tell this with debug_backtrace. Also, debug_backtrace is relatively slow.

Artefacto
  • 90,634
  • 15
  • 187
  • 215
  • 2
    debug_backtrace, though, lists the class as the superclass instead of the subclass. It lists the file and line number where it's being called, so I could theoretically go in there and regex the line to find the called class, assuming it was called with a Bar:: format, but that's not really a solution. And it wouldn't work in the context of a shell where the file name is stupidly listed as 'php shell code' – Kenneth Ballenegger Aug 17 '10 at 01:34
  • @Ken Yes, `debug_backtrace` sucks. Unfortunately, your only remaining options are 1) upgrade, 2) replace the static method `other_code` (`_get_class_name` won't do, because calling it from a `other_code` implementation in the superclass would result in the superclass's version always being called) in every subclass. – Artefacto Aug 17 '10 at 03:30
  • This is possible. Try eval ;) – David Ericsson Jan 23 '14 at 18:15
2

The PHP/5.2 alternative to late static binding that keeps duplicate code to the minimum while avoiding weird hacks would be to create one-liners on child classes that pass the class name as argument:

abstract class Transaction{
    public $id;

    public function __construct($id){
        $this->id = $id;
    }

    protected static function getInstanceHelper($class_name, $id){
        return new $class_name($id);
    }
}

class Payment extends Transaction{
    public static function getInstance($id){
        return parent::getInstanceHelper(__CLASS__, $id);
    }
}

class Refund extends Transaction{
    public static function getInstance($id){
        return parent::getInstanceHelper(__CLASS__, $id);
    }
}

var_dump( Payment::getInstance(1), Refund::getInstance(2) );
object(Payment)#1 (1) {
  ["id"]=>
  int(1)
}
object(Refund)#2 (1) {
  ["id"]=>
  int(2)
}
Álvaro González
  • 128,942
  • 37
  • 233
  • 325
  • Not exactly what I had in mind, but this could work. After all, having to write `__CLASS__` for every class is not such a big job. Then, other logic can be built around that. – XedinUnknown Oct 14 '14 at 13:56
1

This hack includes the heinous use of debug_backtrace... not pretty, but it does the job:

<?php 
function callerName($functionName=null)
{
    $btArray = debug_backtrace();
    $btIndex = count($btArray) - 1;
    while($btIndex > -1)
    {
        if(!isset($btArray[$btIndex]['file']))
        {
            $btIndex--;
            if(isset($matches[1]))
            {
                if(class_exists($matches[1]))
                {
                    return $matches[1];
                }
                else
                {
                    continue;
                }
            }
            else
            {
                continue;
            }
        }
        else
        {
            $lines = file($btArray[$btIndex]['file']);
            $callerLine = $lines[$btArray[$btIndex]['line']-1];
            if(!isset($functionName))
            {
                preg_match('/([a-zA-Z\_]+)::/',
                $callerLine,
                $matches);
            }
            else
            {
                preg_match('/([a-zA-Z\_]+)::'.$functionName.'/',
                    $callerLine,
                    $matches);
            }
            $btIndex--;
            if(isset($matches[1]))
            {
                if(class_exists($matches[1]))
                {
                    return $matches[1];
                }
                else
                {
                    continue;
                }
            }
            else
            {
                continue;
            }
        }
    }
    return $matches[1];
}
Diego Saa
  • 1,254
  • 1
  • 12
  • 23
1

The solution is:

get_class($this);

However, I don't know if this sentence works in static functions. Give it a try and tell me your feedback.

robregonm
  • 597
  • 3
  • 12
0

This function does the same job but works with instances too:

if (!function_exists('get_called_class')) {

    function get_called_class() {

        $bt = debug_backtrace();

        /*
            echo '<br><br>';
            echo '<pre>';
            print_r($bt);
            echo '</pre>';
        */

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

        if ($bt[1]['type'] == '::') {

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

        } else if ($bt[1]['type'] == '->') {

            $result = get_class($bt[1]['object']);
        }

        return $result;
    }
}
Tolga Arican
  • 491
  • 3
  • 14
0
<?php

class Foo {

    private static $instance;

    static function _get_class_name() {
        return self::myNameIs();
    }

    static function other_code() {
        //needs to know
        echo self::_get_class_name();
    }

}

class Bar extends Foo {

    public static function myNameIs() {
        self::$instance = new Bar();
        return get_class(self::$instance);
    }

}

class FooBar extends Foo {

    public static function myNameIs() {
        self::$instance = new FooBar();
        return get_class(self::$instance);
    }

}

Bar::other_code(); // i need 'Bar'
FooBar::other_code(); // i need 'FooBar'
0

I have asked a question like this before, because I wanted a parent to have a factory method that was something like this

public static function factory() {
    return new __CLASS__;
}

But it always returned the parent class, not the inherited one.

I was told that it is not possible without late static binding. It was introduced in PHP 5.3. You can read the documentation.

alex
  • 438,662
  • 188
  • 837
  • 957