views:

47

answers:

3

I was working on an abstract class to save on some code for a couple of classes. These classes are all factories that instantiate themselves through different static calls. I could save some code by putting all those methods in an abstract class.

However, I ran into a late static binding problem... since our web host isn't using 5.3 or later, I don't have access to get_called_class. If I have

$class = __CLASS__;
return new $class();

in the abstract class, __CLASS__ is the name of the abstract class, when I actually want it to use the called class.

I've seen examples of abstract factories on the web where the child classes have their own instantiation methods, and don't rely on the abstract parent for it. However, in this situation, the only reason for the abstract class is to save code, so if I can't do it there, the value of it diminishes greatly.

Is there a workaround in php < 5.3? debug_backtrace()?


Edit:

I did a test and it seems debug_backtrace() will not work! I guess this is why we need late static binding.

<?

abstract class abstractFactory {
    public function create() {
            print_r(debug_backtrace());
            $class = __CLASS__;
            return new $class();
    }
}

class concreteFactory extends abstractFactory {}

$chimborazo = concreteFactory::create();

and the result:

$ php test.php
Array
(
    [0] => Array
        (
            [file] => /var/www/test.php
            [line] => 13
            [function] => create
            [class] => abstractFactory
            [type] => ::
            [args] => Array
                (
                )

        )

)

Fatal error: Cannot instantiate abstract class abstractFactory in /var/www/test.php on line 7
+1  A: 

The only workaround I've seen for this involves calling debug_backtrace to determine the class name of the caller(s). This is of course a giant hack. I've seen some code that combines a backtrace with actually opening up the calling file and parsing it to figure things out. Bizarre, horrible stuff.

The lack of LSB is going to come back and bite you later. Upgrade now, even if it means switching hosts. In fact, especially if it means switching hosts. 5.3 has been out for a year now.

Charles
I did a test, and calling `debug_backtrace()` within the abstract `create()` property didn't have an element that referenced the originating class! I would think it would be there, but I guess this is why we need late static binding :P
Check out m1tk4's code then, it might be smarter...
Charles
+1  A: 

Here's what I've been using until moving to 5.3:

if (!function_exists('get_called_class')) {

   /**
    * Implementation of get_called_class() for pre-5.3 PHP
    *
    * @return string
    */
   function get_called_class()
   {
      $bt = debug_backtrace();
      $lines = file($bt[1]['file']);
      preg_match('/([a-zA-Z0-9\_]+)::'.$bt[1]['function'].'/',
               $lines[$bt[1]['line']-1],
               $matches);
      return $matches[1];
   }
}

This lets you determine in a static function what class name the function has been invoked with. It's a workaround that has some performance issues but it's the only one I've found. If there are others I'd be interested to know.

m1tk4
A: 

Once way to do it is to override the various instantiation methods, and pass the name of the class directly:

<?

abstract class abstractFactory {

    public function create($class) {
        return new $class();
    }

    public function instantiate($class) {
        return new $class();
    }

}

class concreteFactory extends abstractFactory {

    public function create() {
        parent::create(__CLASS__);
    }

    public function instantiate() {
        parent::instantiate(__CLASS__);
    }
}


$chimborazo = concreteFactory::create();
$chimborazo = concreteFactory::instantiate();
He's not using 5.3, so he doesn't have the `__CLASS__` constant to begin with...
Charles
I'm the OP ;) `__CLASS__` has been available since 4.3: http://php.net/manual/en/language.constants.predefined.php . I'm using 5.2.6 and I use it all the time.
I need to learn to read, heh.
Charles
I do the same thing all the time :)