Daily Archive for February 6th, 2009

PHP Dynamic Method Definitions

I recently stumbled across a quite old post about using mixins in PHP. It observes that calling a member function within another member function of another object is possible, and runs that function with the $this variable of the other. Like so:

class Alpha {
  function called() { echo $this -> _beta; }
}

class Beta {
  var $_beta = "Hello!";
  function caller() { Alpha::called(); }
}

$beta = new Beta();
$beta -> caller();

This example displays “Hello!” by using the $beta object as the $this variable in Alpha::called. The author of the post then wants to achieve something similar to this:

class Beta {
  var $_beta;
}

$beta = new Beta();
mixin($beta, Alpha);
$beta -> called();

The mixin call would insert all functions from class Alpha into class Beta, thereby allowing a direct call to called despite it never being defined in Beta. If you will, the mixin behaves like importing all the functions from a class into another. And unlike inheritance, it works dynamically.

Except that there’s no way in PHP (as of 5.2, at least) to add a function to an object. The details of why it’s impossible are complex (and can be summarized as “whatever your approach, there will be a problem that cannot be worked around”). Therefore, adding functions to objects is mostly a matter of choosing the approach that has the most acceptable problem.

My choice is to have classes extend a certain base class before they can receive functions dynamically. This class looks mostly like this:

class Dynamic
{
  function __call($func, $args)
  {
    $func = strtolower($func);
    $assoc = $this -> funcs[$func];
    if (is_object($assoc))
      return call_user_func_array(array($assoc,$func), $args);
    if (!isset($assoc)) $assoc = get_class($this);
    $argarr = array();
    $keys = array_keys($args);
    foreach ($keys as $id => $key)
      $argarr[] = '$args[$keys['.$id.']]';
    $argstr = implode($argarr, ",");
    return eval("return $assoc::$func($argstr);");
  }

  function import($arg1, $arg2=null)
  {
    assert (is_object($arg1) || class_exists($arg1));
    if (isset($arg2))
      $this -> funcs[strtolower($arg2)] = $arg1;
    else
      foreach (get_class_methods($arg1) as $method)
        $this -> funcs[strtolower($method)] = $arg1;
  }
}

Any errors are left to the reader as an exercise ;) This code allows you to add functions from other objects, and functions from other classes, to your class:

class Alpha
{
  var $_value = "Alpha";
  function show() { echo $this -> _value; }
}

class Beta extends Dynamic
{
  var $_value = "Beta";
}

$alpha = new Alpha();
$beta = new Beta();

$beta -> import(Alpha); // or (Alpha, show)
$beta -> show();        // "Beta"

$beta -> import($alpha); // or ($alpha, show)
$beta -> show();         // "Alpha"

If you import an object, then all its functions are installed into the dynamic object, and calling these functions actually calls the functions on the imported object. Similarly, if you import a class, then all its functions are installed into the dynamic object, but calling them will run them on the dynamic object. And you may add a second argument to only load a single function from the object or class, instead of loading them all.

Two important things to notice here:

  • You can’t override __call in classes that extend Dynamic. Unless, of course, you forward the call to the parent class if you cannot handle it.
  • Functions defined directly in the class, as well as those handled by its __call function (if any) are never overridden by importing. You can only override functions that don’t exist (or were imported from somewhere else).

Enjoy! I hereby place all this code in the public domain.



1170 feed subscribers
(readers who polled a feed this week)