__call()

The call() magic function is to class functions what __get() is to class variables – if you call meow() on an object of class dog, PHP will fail to find the function and check whether you have defined a __call() function. If so, your __call() is used, with the name of the function you tried to call and the parameters you passed being passed in as parameters one and two respectively.

Here’s an example of __call() in action:

<?php
    
class dog {
        public
$Name;

        public function bark() {
            print
"Woof!n";
        }

        // public function meow() {
            // print "Dogs don't meow!n";
        // }

        public function __call($function, $args) {
            
$args = implode(', ', $args);
            print
"Call to $function() with args '$args' failed!n";
        }
    }

    $poppy = new dog;
    
$poppy->meow("foo", "bar", "baz");
?>

Again, note that the meow() function is commented out – if you want to be sure that __call() is not used if the function already exists, remove the comments from meow().

__call()

The call() magic function is to class functions what __get() is to class variables – if you call meow() on an object of class dog, PHP will fail to find the function and check whether you have defined a __call() function. If so, your __call() is used, with the name of the function you tried to call and the parameters you passed being passed in as parameters one and two respectively.

Here’s an example of __call() in action:

<?php
    
class dog {
        public
$Name;

        public function bark() {
            print
"Woof!n";
        }

        // public function meow() {
            // print "Dogs don't meow!n";
        // }

        public function __call($function, $args) {
            
$args = implode(', ', $args);
            print
"Call to $function() with args '$args' failed!n";
        }
    }

    $poppy = new dog;
    
$poppy->meow("foo", "bar", "baz");
?>

Again, note that the meow() function is commented out – if you want to be sure that __call() is not used if the function already exists, remove the comments from meow().

 

http://www.garfieldtech.com/blog/magical-php-call 에서 참조

21 August 2007 – 11:35pm — Larry

PHP is, conceptually, a very traditional language. By that I mean that it is steeped in the C/C++/Java mindset of how a program is put together, rather than the LISP/Javascript/Ruby/Python mindset. That’s not a bad thing, necessarily, but it does mean that a lot of the cool dynamic language capabilities in those languages isn’t really available to PHP developers. I freely admit to being jealous of functions as first-class objects, for instance.

PHP 5, however does include lots of “magic” capabilities, some in the object model directly and some via SPL Interfaces, that, if used properly, can make up for a lot of that lack of dynamic capaibility. A favorite of mine is the ability to add methods to a class at runtime.

What? You didn’t know PHP can do that? Well, that’s because it can’t. However, we can simulate it pretty closely if we’re careful. Let’s see how.

Calling magic

PHP 5 includes a number of magic methods that a class can implement. Each magic method begins with __, and gets called automatically by PHP under certain conditions. Technically some are called overloading methods rather than magic methods, but they all effectively work by the same magic blue smoke.

Today we’re just going to look at one of them: __call(). If a class implements __call(), then if an object of that class is called with a method that doesn’t exist __call() is called instead. To wit:

<?php
class Caller {
  private $x = array('a', 'b', 'c');

  public function __call($method, $args) {
    print "Method $method called:n";
    var_dump($args);
    return $this->x;
  }
}

$foo = new Caller();
$a = $foo->test(1, 2, 3);
var_dump($a);
?>

Other PHP components like SOAP use __call() to support dynamic methods based on a remote object. How does that help us add methods to a class at runtime, though?

Registering magic

We could define all of the methods within the class and then have the __call() method route to them, but then we have to define the methods within the class, which defeats the purpose. But if we define them outside of the class, how do we treat them like a method?

First, we have to register the pseudo-method with the class:

<?php
class Dynamic {
  protected $methods = array();

  public static function registerMethod($method) {
    self::$methods[] = $method;
  }
}

function test() {
  print "Hello World" . PHP_EOL;
}

Dynamic::registerMethod('test');
?>

And then it becomes obvious how to call it.

<?php
class Dynamic {
  static protected $methods = array();

  public static function registerMethod($method) {
    self::$methods[] = $method;
  }

  private function __call($method, $args) {
    if (in_array($method, self::$methods)) {
      return call_user_func_array($method, $args);
    }
  }
}

function test() {
  print "Hello World" . PHP_EOL;
}

Dynamic::registerMethod('test');
$d = new Dynamic();
$d->test();
?>

Neat. But really, all we’ve done is create a level of indirection to a function. It’s not a method. test() doesn’t even know what its object is. That’s trivial to fix, though:

<?php
class Dynamic {
  //...
  public $message = 'Hello World';

  private function __call($method, $args) {
    if (in_array($method, self::$methods)) {
      array_unshift($args, $this);
      return call_user_func_array($method, $args);
    }
  }
  //...
}

function test($obj) {
  print $obj->message . PHP_EOL;
}

Dynamic::registerMethod('test');
$d = new Dynamic();
$d->test();
?>

OK, so now we use $obj instead of $this in the pseudo-method, but otherwise we’re golden. We can take any function that takes an object as its first parameter and assign it to a class, and poof, we now have a new method!

Or do we?

Private magic

We’re still missing one key property of methods. A method of an object has access to that object’s private and protected properties. In our case, the psuedo-method is still treating the object as a foreign object (which it is), and so doesn’t have access to private methods or properties. What’s more, if there was a way for our function to get at private properties of $obj, it would be a bug in the PHP engine. It’s not supposed to have access to those properties. That’s why they’re private!

Although at this point it seems hopeless, we’re still not done. A function outside of an object cannot access private properties of that object, but an object can pass any property it wants to another method or function. Passing the entire object’s internal properties as parameters to the psueudo-method would be very sloppy and very brittle, however. Instead, let’s use a facade object. That facade object will hold no data, but just references to data. References in memory ignore public/private status, because the variable (reference) is protected, not the data itself. We let the object being referenced set everything up, because it has access to its own properties. To wit:

<?php
class Dynamic {
  static protected $methods = array();
  private $message = 'Hello World';

  public static function registerMethod($method) {
    self::$methods[] = $method;
  }

  private function __call($method, $args) {
    if (in_array($method, self::$methods)) {
      $obj = new DynamicFacade($this);
      $fields = array();
      foreach (array_keys(get_class_vars(__CLASS__)) as $field) {
        if ($field != 'methods') {
          $fields[$field] = &$this->$field;
        }
      }
      $obj->registerFields($fields);
      array_unshift($args, $obj);
      return call_user_func_array($method, $args);
    }
  }
}

class DynamicFacade {

  private $object = NULL;
  private $fields = array();
  private $arrays = array();

  function __construct($obj) {
    $this->object = $obj;
  }

  private function __get($var) {
    if (in_array($var, array_keys($this->fields))) {
      return $this->fields[$var];
    }
    else {
      return $this->object->$var;
    }
  }

  private function __set($var, $val) {
    if (in_array($var, array_keys($this->fields))) {
      $this->fields[$var] = $val;
    }
    else {
      $this->$object->$var = $val;
    }
  }

  private function __isset($var) {
    if (in_array($var, array_keys($this->fields))) {
      return TRUE;
    }
    return isset($this->object->$var);
  }

  private function __unset($var) {
    unset($this->object->$var);
    unset($this->fields[$var]);
  }

  public function registerFields(&$fields) {
    $this->fields = $fields;
  }

  function __call($method, $args) {
    return call_user_func_array(array($this->object, $method), $args);
  }
}

function test($obj) {
  print $obj->message . PHP_EOL;
}

Dynamic::registerMethod('test');
$d = new Dynamic();
$d->test();
?>

Here, we now build a facade object within the __call() method. That call object has a reference to our dynamic class object. Then we create an array that contains references to our dynamic object’s properties. Those references ignore public/private concerns. Then we pass that array, by reference, to the facade object. The facade object then has an array whose values reference directly to the properties of the dynamic object. We then use two other overloading methods, __get() and __set(), to route requests against the facade object back to the original object’s properties. Try it!

A more complex implementation could even make pseudo-methods conditional, say, on the type of an object. It’s not multiple inheritance… but it tastes almost like it.

Limitations

There’s still one problem with this mechanism. Because of the way arrays work, we can’t modify private arrays. They don’t resolve properly. I still haven’t found a solution to that issue, sadly. If anyone else has a solution, I’d love to hear it.

There’s also the performance question. Any level of indirection adds CPU cycles, sometimes a lot. How does creating a facade object for each pseudo-method request stack up? It turns out that creating the facade object has only a negligible impact on performance. PHP 5’s engine is really quite good at creating objects. Unfortunately, it’s not so good at calling dynamic methods. Just triggering __call() itself is, in fact, quite slow. In my benchmarks, it came out as 12 times slower than a normal method call. To be fair, we’re talking about 12 times a fraction of a millisecond, and I had to crank the number of iterations up to a million in order to test it, but it’s still disappointing.

Update: Looks like it’s not __call() that’s killing performance. It’s call_user_func_array(). Yay for benchmarks. 🙂

Suggestions for improvement are always welcome. In the mean time, let me know if you were able to leverage this capability in a production system! I always like knowing my tutorials prove useful somewhere.

Happy Coding!

 

 

 

PHP is, conceptually, a very traditional language. By that I mean that it is steeped in the C/C++/Java mindset of how a program is put together, rather than the LISP/Javascript/Ruby/Python mindset. That’s not a bad thing, necessarily, but it does mean that a lot of the cool dynamic language capabilities in those languages isn’t really available to PHP developers. I freely admit to being jealous of functions as first-class objects, for instance.

PHP 5, however does include lots of “magic” capabilities, some in the object model directly and some via SPL Interfaces, that, if used properly, can make up for a lot of that lack of dynamic capaibility. A favorite of mine is the ability to add methods to a class at runtime.

What? You didn’t know PHP can do that? Well, that’s because it can’t. However, we can simulate it pretty closely if we’re careful. Let’s see how.

Calling magic

PHP 5 includes a number of magic methods that a class can implement. Each magic method begins with __, and gets called automatically by PHP under certain conditions. Technically some are called overloading methods rather than magic methods, but they all effectively work by the same magic blue smoke.

Today we’re just going to look at one of them: __call(). If a class implements __call(), then if an object of that class is called with a method that doesn’t exist __call() is called instead. To wit:

<?php
class Caller {
  private
$x = array('a', 'b', 'c');

  public function __call($method, $args) {
    print
"Method $method called:n";
   
var_dump($args);
    return
$this->x;
  }
}

$foo = new Caller();
$a = $foo->test(1, 2, 3);
var_dump($a);
?>

Other PHP components like SOAP use __call() to support dynamic methods based on a remote object. How does that help us add methods to a class at runtime, though?

Registering magic

We could define all of the methods within the class and then have the __call() method route to them, but then we have to define the methods within the class, which defeats the purpose. But if we define them outside of the class, how do we treat them like a method?

First, we have to register the pseudo-method with the class:

<?php
class Dynamic {
  protected
$methods = array();

  public static function registerMethod($method) {
   
self::$methods[] = $method;
  }
}

function test() {
  print
"Hello World" . PHP_EOL;
}

Dynamic::registerMethod('test');
?>

And then it becomes obvious how to call it.

<?php
class Dynamic {
  static protected
$methods = array();

  public static function registerMethod($method) {
   
self::$methods[] = $method;
  }

  private function __call($method, $args) {
    if (
in_array($method, self::$methods)) {
      return
call_user_func_array($method, $args);
    }
  }
}

function test() {
  print
"Hello World" . PHP_EOL;
}

Dynamic::registerMethod('test');
$d = new Dynamic();
$d->test();
?>

Neat. But really, all we’ve done is create a level of indirection to a function. It’s not a method. test() doesn’t even know what its object is. That’s trivial to fix, though:

<?php
class Dynamic {
 
//...
 
public $message = 'Hello World';

  private function __call($method, $args) {
    if (
in_array($method, self::$methods)) {
     
array_unshift($args, $this);
      return
call_user_func_array($method, $args);
    }
  }
 
//...
}

function test($obj) {
  print
$obj->message . PHP_EOL;
}

Dynamic::registerMethod('test');
$d = new Dynamic();
$d->test();
?>

OK, so now we use $obj instead of $this in the pseudo-method, but otherwise we’re golden. We can take any function that takes an object as its first parameter and assign it to a class, and poof, we now have a new method!

Or do we?

Private magic

We’re still missing one key property of methods. A method of an object has access to that object’s private and protected properties. In our case, the psuedo-method is still treating the object as a foreign object (which it is), and so doesn’t have access to private methods or properties. What’s more, if there was a way for our function to get at private properties of $obj, it would be a bug in the PHP engine. It’s not supposed to have access to those properties. That’s why they’re private!

Although at this point it seems hopeless, we’re still not done. A function outside of an object cannot access private properties of that object, but an object can pass any property it wants to another method or function. Passing the entire object’s internal properties as parameters to the psueudo-method would be very sloppy and very brittle, however. Instead, let’s use a facade object. That facade object will hold no data, but just references to data. References in memory ignore public/private status, because the variable (reference) is protected, not the data itself. We let the object being referenced set everything up, because it has access to its own properties. To wit:

<?php
class Dynamic {
  static protected
$methods = array();
  private
$message = 'Hello World';

  public static function registerMethod($method) {
   
self::$methods[] = $method;
  }

  private function __call($method, $args) {
    if (
in_array($method, self::$methods)) {
     
$obj = new DynamicFacade($this);
     
$fields = array();
      foreach (
array_keys(get_class_vars(__CLASS__)) as $field) {
        if (
$field != 'methods') {
         
$fields[$field] = &$this->$field;
        }
      }
     
$obj->registerFields($fields);
     
array_unshift($args, $obj);
      return
call_user_func_array($method, $args);
    }
  }
}

class DynamicFacade {

  private $object = NULL;
  private
$fields = array();
  private
$arrays = array();

  function __construct($obj) {
   
$this->object = $obj;
  }

  private function __get($var) {
    if (
in_array($var, array_keys($this->fields))) {
      return
$this->fields[$var];
    }
    else {
      return
$this->object->$var;
    }
  }

  private function __set($var, $val) {
    if (
in_array($var, array_keys($this->fields))) {
     
$this->fields[$var] = $val;
    }
    else {
     
$this->$object->$var = $val;
    }
  }

  private function __isset($var) {
    if (
in_array($var, array_keys($this->fields))) {
      return
TRUE;
    }
    return isset(
$this->object->$var);
  }

  private function __unset($var) {
    unset(
$this->object->$var);
    unset(
$this->fields[$var]);
  }

  public function registerFields(&$fields) {
   
$this->fields = $fields;
  }

  function __call($method, $args) {
    return
call_user_func_array(array($this->object, $method), $args);
  }
}

function test($obj) {
  print
$obj->message . PHP_EOL;
}

Dynamic::registerMethod('test');
$d = new Dynamic();
$d->test();
?>

Here, we now build a facade object within the __call() method. That call object has a reference to our dynamic class object. Then we create an array that contains references to our dynamic object’s properties. Those references ignore public/private concerns. Then we pass that array, by reference, to the facade object. The facade object then has an array whose values reference directly to the properties of the dynamic object. We then use two other overloading methods, __get() and __set(), to route requests against the facade object back to the original object’s properties. Try it!

A more complex implementation could even make pseudo-methods conditional, say, on the type of an object. It’s not multiple inheritance… but it tastes almost like it.

Limitations

There’s still one problem with this mechanism. Because of the way arrays work, we can’t modify private arrays. They don’t resolve properly. I still haven’t found a solution to that issue, sadly. If anyone else has a solution, I’d love to hear it.

There’s also the performance question. Any level of indirection adds CPU cycles, sometimes a lot. How does creating a facade object for each pseudo-method request stack up? It turns out that creating the facade object has only a negligible impact on performance. PHP 5’s engine is really quite good at creating objects. Unfortunately, it’s not so good at calling dynamic methods. Just triggering __call() itself is, in fact, quite slow. In my benchmarks, it came out as 12 times slower than a normal method call. To be fair, we’re talking about 12 times a fraction of a millisecond, and I had to crank the number of iterations up to a million in order to test it, but it’s still disappointing.

Update: Looks like it’s not __call() that’s killing performance. It’s call_user_func_array(). Yay for benchmarks. 🙂

Suggestions for improvement are always welcome. In the mean time, let me know if you were able to leverage this capability in a production system! I always like knowing my tutorials prove useful somewhere.

Happy Coding!