lmbActiveRecord’s late static binding emulation

I was quite inspired on having heard that the late static binding(LSB) patch will make it into PHP-5.3. In short, it should allow to know the name of a class which invoked the parent’s static class method.

If you might think debug_backtrace can help you out try executing the following code:

<?php
class Foo
{
  static function me()
  {
    $trace = debug_backtrace();
    $item = $trace[0];
    $class = $item['class'];
    $method = $item['function'];
    echo "{$class}::{$method}";
  }
}
class Bar extends Foo{}
Bar :: me();

If you are expecting to see “Bar::me” you will be wrong, it will be “Foo::me” :( There is currently no clean way(without dirty hacks) to fetch the name of the concrete child class in such a situation.

This “misfeature” of PHP prevented Limb3 lmbActiveRecord from having nice static finders. For example, instead of using User :: find() directly, one had to write lmbActiveRecord :: find(”User”) which, obviously, was a bit ugly.

I had a dirty-hack-patch which allowed to have shorter syntax for static finders but I was hesitating if it made sense to commit it since it was reaaaally ugly. However, once I heard somewhere on the mailing list the late static binding will be in PHP-5.3 I committed this patch in order to make lmbActiveRecord sort of “forward compatible” with PHP-5.3.

From now on you can use shorter syntax(btw, the old way of calling finders is valid too, of course):

<?php
$books = Book :: find();
$authors = Author :: find(1);
$cats = Category :: find("name='test'");
//...and so on

You might be interested to know how it actually works. Ok, here is the internals of the black voodoo lmbActiveRecord :: _getCallingClass() which, in fact, emulates the new get_calling_class(it may have a bit different name) function in the upcoming PHP-5.3.

<?php
...
class lmbActiveRecord
{
...
  protected static function _getCallingClass()
  {
    //once PHP-5.3 LSB patch is available we'll use get_called_class
    if(function_exists('get_called_class'))
      return get_called_class();

    $trace = debug_backtrace();
    $back = $trace[1];
    $method = $back['function'];
    $fp = fopen($back['file'], 'r');

    for($i=0; $i<$back['line']-1; $i++)
      fgets($fp);

    $line = fgets($fp);
    fclose($fp);

    if(!preg_match('~(w+)s*::s*' . $method . 's*(~', $line, $m))
      throw new lmbARException("Static calling class not found!(using multiline static method call?)");
    if($m[1] == 'lmbActiveRecord')
      throw new lmbARException("Found static class can't be lmbActiveRecord!");
    return $m[1];
  }
...
}

Here’s a short explanation of the lmbActiveRecord :: _getCallingClass() emulation:

  1. Backtrace is retrieved, while the name of the class is of no use(as it was shown in the first example) the name of the file, line and method can be used to fetch the concrete name of the calling class
  2. The file is opened and rewound forward until the needed line.
  3. The line is matched with regular expression for the concrete class name.
  4. If not found one of the exceptions is raised.

This is not an ideal solution, because:

  1. It’s slower than lmbActiveRecord :: find(..): it takes about 0.69 sec. to run 1000 iterations of lmbActiveRecord :: find(’Foo’) and 0.85 sec. -
    of Foo :: find() on my dev. box.

  2. It can be confused if multiple calls to different static finders are used on the same line, e.g:
    <?php
    $authors = Author :: find(); $books = Book :: find();
    
  3. It doesn’t work with static finders spanned over several lines, e.g:
    <?php
    $books = Book :: find('name="' .
                                        $name . '"');
    

    The problem here is the fact debug_backtrace returns the line where the ending “;” symbol is located, not the line with Book :: find.

Well, if you can live with this limitations go use new lmbActiveRecord’s finders ;) (available in repository only). At any rate, all of them will be magically resolved with PHP-5.3, still you can taste the benefits of the emulated LSB now.

2 Responses to “lmbActiveRecord’s late static binding emulation”

  1. lifeforms Says:

    I have been struggling with this same problem, but this patch makes me feel so dirty just by looking at it :’)

  2. pachanga Says:

    I feel your pain ;) But there’s nothing else we can do before PHP-5.3. Well, at least this patch gives some sort of “forward compatibility” with upcoming LSB feature.

Leave a Reply