[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