NYCPHP Meetup

NYPHP.org

[nycphp-talk] "Calling" an object

Mike Naberezny mike.n at zend.com
Fri Dec 16 17:46:20 EST 2005


Hi Andrew,

Andrew Yochum wrote:
> I want to reproduce the equivalent of Python's magic __call__ method in
> PHP 5.  More or less this might look like:

That's a great question.  In Python, the magic __call__ method defines the 
behavior of an object when the object is "called like a method".

>     class foo {
>         function __call__() { return "bar"; }
>     }
> 
>     $o = new foo();
>     $s = $o();
>     // $s = "bar"

Unfortunately, you can't do this in the PHP global scope, as your example and 
this one demonstrate:

<?php
$o = new stdClass();  // or anything other than a string
$o();
?>

This will raise a fatal error (function name must be string), which is not 
interceptable.  PHP thinks you are trying to use a "variable function".

However, you probably won't be using most of your code in the global scope 
anyway, so there is still some hope.  You can do some interesting behaviors by 
using the magic methods __get() and __call() as proxies in a container object.

The __get() magic acts as a proxy for undefined properties of an object. 
Similarly, the __call() magic is a proxy for undefined methods.  By keeping a 
child object as a protected member of a container and returning it only 
through __get(), you can then use __call() as a broker to the child object's 
methods when "it is called like a method".

First, we can set up your class Foo from above, with the "__call__" method:

class Foo {
     public function calledAsMethod($args) {
         return 'bar';
     }

     public function hello() {
         return 'hi';
     }
}

Next, another class is built to contain it.

class Bar {
     /* @var Foo */
     protected $_foo = null;

     public function __construct() {
         $this->_foo = new Foo();
     }

     public function __get($offset) {
         if ($offset=='foo') {
             return $this->_foo;
         } else throw new Exception("Undefined property: $offset");
     }

     public function __call($offset, $args) {
         if ($offset=='foo') {
             return $this->_foo->calledAsMethod($args);
         } else throw new Exception("Undefined method: $offset");
     }
}

Here's how it works:

$bar = new Bar();
echo $bar->foo->hello();  // "hi"  - Foo is returned, Foo::hello() is called
echo $bar->foo();         // "bar" - __call() brokers to calledAsMethod()

This is almost the same functionality as Python's __call__, except that you 
have to access it through the container Bar.

Depending on how you intend to use it, there is a drawback to the code as 
shown above, in that the visibility of Bar's magic property "foo" is always 
public (like the usage examples).  That might be what you want, however it 
isn't if you are only using magic "foo" from inside the scope of Bar (as in 
$this->foo and $this->foo()).  If that's the case, and your Bar class is not 
using __get() or __call() to provide any other public magics, you can set 
their visibility to protected or private.  They will still function within the 
scope of Bar, however calls to the magic property "foo" from outside the scope 
of Bar will then raise a fatal error: Call to protected method Bar::__get().

Regards,
Mike

-- 
Mike Naberezny
Senior PHP Developer

Zend Technologies, The PHP Company
19200 Stevens Creek Blvd, Suite 100
Cupertino, CA 95014
Tel: 408-342-8891
Fax: 408-253-8801
mike.n at zend.com



More information about the talk mailing list