Overloading

Both method calls and member accesses can be overloaded via the __call, __get and __set methods. These methods will only be triggered when your object or inherited object doesn't contain the member or method you're trying to access. All overloading methods must not be defined as static. All overloading methods must be defined as public.

Since PHP 5.1.0 it is also possible to overload the isset() and unset() functions via the __isset and __unset methods respectively. Method __isset is called also with empty().

Member overloading

void __set ( string name, mixed value )

mixed __get ( string name )

bool __isset ( string name )

void __unset ( string name )

Class members can be overloaded to run custom code defined in your class by defining these specially named methods. The $name parameter used is the name of the variable that should be set or retrieved. The __set() method's $value parameter specifies the value that the object should set the $name.

例 19-20. overloading with __get, __set, __isset and __unset example

<?php
class Setter
{
    
public $n;
    
private $x = array("a" => 1, "b" => 2, "c" => 3);

    
public function __get($nm)
    {
        echo
"Getting [$nm]\n";

        if (isset(
$this->x[$nm])) {
            
$r = $this->x[$nm];
            print
"Returning: $r\n";
            return
$r;
        } else {
            echo
"Nothing!\n";
        }
    }

    
public function __set($nm, $val)
    {
        echo
"Setting [$nm] to $val\n";

        if (isset(
$this->x[$nm])) {
            
$this->x[$nm] = $val;
            echo
"OK!\n";
        } else {
            echo
"Not OK!\n";
        }
    }

    
public function __isset($nm)
    {
        echo
"Checking if $nm is set\n";

        return isset(
$this->x[$nm]);
    }

    
public function __unset($nm)
    {
        echo
"Unsetting $nm\n";

        unset(
$this->x[$nm]);
    }
}

$foo = new Setter();
$foo->n = 1;
$foo->a = 100;
$foo->a++;
$foo->z++;

var_dump(isset($foo->a)); //true
unset($foo->a);
var_dump(isset($foo->a)); //false

// this doesn't pass through the __isset() method
// because 'n' is a public property
var_dump(isset($foo->n));

var_dump($foo);
?>

上例将输出:

Setting [a] to 100
OK!
Getting [a]
Returning: 100
Setting [a] to 101
OK!
Getting [z]
Nothing!
Setting [z] to 1
Not OK!

Checking if a is set
bool(true)
Unsetting a
Checking if a is set
bool(false)
bool(true)

object(Setter)#1 (2) {
  ["n"]=>
  int(1)
  ["x:private"]=>
  array(2) {
    ["b"]=>
    int(2)
    ["c"]=>
    int(3)
  }
}

Method overloading

mixed __call ( string name, array arguments )

The magic method __call() allows to capture invocation of non existing methods. That way __call() can be used to implement user defined method handling that depends on the name of the actual method being called. This is for instance useful for proxy implementations. The arguments that were passed in the function will be defined as an array in the $arguments parameter. The value returned from the __call() method will be returned to the caller of the method.

例 19-21. overloading with __call example

<?php
class Caller
{
    
private $x = array(1, 2, 3);

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

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

上例将输出:

Method test called:
array(4) {
    [0]=>
    int(1)
    [1]=>
    string(1) "2"
    [2]=>
    float(3.4)
    [3]=>
    bool(true)
}
array(3) {
    [0]=>
    int(1)
    [1]=>
    int(2)
    [2]=>
    int(3)
}

add a note add a note User Contributed Notes
php at sleep is the enemy dot co dot uk
23-Jul-2007 02:23
Just to reinforce and elaborate on what DevilDude at darkmaker dot com said way down there on 22-Sep-2004 07:57.

The recursion detection feature can prove especially perilous when using __set. When PHP comes across a statement that would usually call __set but would lead to recursion, rather than firing off a warning or simply not executing the statement it will act as though there is no __set method defined at all. The default behaviour in this instance is to dynamically add the specified property to the object thus breaking the desired functionality of all further calls to __set or __get for that property.

Example:

<?php

class TestClass{

   public
$values = array();
  
   public function
__get($name){
       return
$this->values[$name];
   }
  
   public function
__set($name, $value){
      
$this->values[$name] = $value;
      
$this->validate($name);
   }

   public function
validate($name){
      
/*
       __get will be called on the following line
       but as soon as we attempt to call __set
       again PHP will refuse and simply add a
       property called $name to $this
       */
      
$this->$name = trim($this->$name);
   }
}

$tc = new TestClass();

$tc->foo = 'bar';
$tc->values['foo'] = 'boing';

echo
'$tc->foo == ' . $tc->foo . '<br>';
echo
'$tc ' . (property_exists($tc, 'foo') ? 'now has' : 'still does not have') . ' a property called "foo"<br>';

/*
OUPUTS:
$tc->foo == bar
$tc now has a property called "foo"
*/

?>
Adeel Khan
10-Jul-2007 08:18
Observe:

<?php
class Foo {
   function
__call($m, $a) {
       die(
$m);
   }
}

$foo = new Foo;
print
$foo->{'wow!'}();

// outputs 'wow!'
?>

This method allows you to call functions with invalid characters.
alexandre at nospam dot gaigalas dot net
08-Jul-2007 05:59
PHP 5.2.1

Its possible to call magic methods with invalid names using variable method/property names:

<?php

class foo
{
   function
__get($n)
   {
      
print_r($n);
   }
   function
__call($m, $a)
   {
      
print_r($m);
   }
}

$test = new foo;
$varname = 'invalid,variable+name';
$test->$varname;
$test->$varname();

?>

I just don't know if it is a bug or a feature :)
BenBE at omorphia dot de
05-May-2007 05:48
While playing a bit with the __call magic method I found you can not emulate implementing methods of an interface as you might think:

<?
class Iteratable implements Iterator {
   public function
__call($funcname) {
       if(
in_array($funcname, array('current', 'next', /*...*/)) {
          
//Redirect the call or perform the actual action
      
}
   }
}
?>

Using this code you'll get a "class Iteratable contains abstract methods ..." fatal error message. You'll ALWAYS have to implement those routines by hand.
j dot stubbs at linkthink dot co dot jp
27-Feb-2007 05:53
In reply to james at thunder-removeme-monkey dot net,

Unfortunately it seems that there is no way to have completely transparent array properties with 5.2.x. The code you supplied works until working with built-in functions that perform type-checks:

<?

// Using the same View class
$view = new View();
$view->bar = array();
$view->bar[] = "value";

if (
is_array($view->bar))
   print
"is array!\n"; // Not printed

// Warning: in_array(): Wrong datatype for second argument in ...
if (in_array("value", $view->bar))
   print
"found!\n"; // Not printed

// Successful
if (in_array("value", (array)$view->bar))
   print
"found!\n";

?>

It also seems that 5.1.x is no longer maintained as it's not listed on the downloads page.. Quite frustrating. :/
mafu at spammenot-mdev dot dk
24-Feb-2007 10:24
As a reply to james at thunder-removeme-monkey dot net, I found that there is a much simpler way to restore the behavior of __get() to 5.1.x state; just force __get() to return by reference, like this:

<?php
class View {
 
/* Somewhere to store our overloaded properties */
 
private $v = array();

 
/* Store a new property */
 
function __set($varName, $varValue) {
  
$this->v[$varName] = $varValue;
  }

 
/* Retrieve a property */
 
function & __get($varName) {
   if(!isset(
$this->v[$varName])) {
    
$this->v[$varName] = NULL;
   }
   return
$this->v[$varName];
  }
}
?>

The only problem is that the code generates a notice if null is returned in __get(), because null cannot be returned by reference. If somebody finds a solution, feel free to email me. :)

Cheers
james at thunder-removeme-monkey dot net
31-Jan-2007 09:07
Following up on the comment by "jstubbs at work-at dot co dot jp" and after reading "http://weierophinney.net/matthew/archives/ 131-Overloading-arrays-in-PHP-5.2.0.html", the following methods handle property overloading pretty neatly and return variables in read/write mode.

<?php
class View {
 
/* Somewhere to store our overloaded properties */
 
private $v = array();

 
/* Store a new property */
 
function __set($varName, $varValue) {
  
$this->v[$varName] = $varValue;
  }

 
/* Retrieve a property */
 
function __get($varName) {
   if(!isset(
$this->v[$varName])) {
    
$this->v[$varName] = NULL;
   }
   return
is_array($this->v[$varName]) ? new ArrayObject($this->v[$varName]) : $this->v[$varName];
  }
}
?>

This is an amalgm of previous solutions with the key difference being the use of ArrayObject in the return value. This is more flexible than having to extend the whole class from ArrayObject.

Using the above class, we can do ...

<?php
$obj
= new SomeOtherObject();
$view = new View();

$view->list = array();
$view->list[] = "hello";
$view->list[] = "goat";
$view->list[] = $group;
$view->list[] = array("a", "b", "c");
$view->list[3][] = "D";
$view->list[2]->aprop = "howdy";

/*
$view->list now contains:
[0] => "hello"
[1] => "goat"
[2] => SomeOtherObject { aprop => "howdy" }
[3] => array("a", "b", "c", "D")

and
$obj === $view->list[2] // equates to TRUE
*/
?>
jbailey at raspberryginger dot com
08-Jan-2007 04:39
The __set method doesn't seem to respect visibility at all (and the examples suggest this).  I'd hoped that marking the __set method as "private" would cause an exception to get thrown.  Instead the __set method needs to throw an Exception itself.

Being able to lock other things away from putting things randomly into the object would be handy for helping find typos right at the source.
mhherrera31 at hotmail dot com
25-Nov-2006 06:11
example for read only properties in class object. Lets you manage read only properties with var names like $ro_var.

The property must be PRIVATE, otherwise the overload method __get doesn't be called.

class Session {
 private $ro_usrName;

 function __construct (){
  $this->ro_usrName = "Marcos";
 }

 function __set($set, $val){
  if(property_exists($this,"ro_".$set))
   echo "The property '$set' is read only";
  else
   if(property_exists($this,$set))
     $this->{$set}=$val;
   else
     echo "Property '$set' doesn't exist";
 }

 function __get{$get}{
  if(property_exists($this,"ro_".$get))
   return $this->{"ro_".$get};
  else
   if(property_exists($this,$get))
     return $this->{$get};
   else
     echo "Property '$get' doesn't exist";
 }
}
MagicalTux at ooKoo dot org
06-Sep-2006 09:35
Since many here probably wanted to do real overloading without having to think too much, here's a generic __call() function for those cases.

Little example :
<?php
class OverloadedClass {
       public function
__call($f, $p) {
               if (
method_exists($this, $f.sizeof($p))) return call_user_func_array(array($this, $f.sizeof($p)), $p);
              
// function does not exists~
              
throw new Exception('Tried to call unknown method '.get_class($this).'::'.$f);
       }

       function
Param2($a, $b) {
               echo
"Param2($a,$b)\n";
       }

       function
Param3($a, $b, $c) {
               echo
"Param3($a,$b,$c)\n";
       }
}

$o = new OverloadedClass();
$o->Param(4,5);
$o->Param(4,5,6);
$o->ParamX(4,5,6,7);
?>

Will output :
Param2(4,5)
Param3(4,5,6)

Fatal error: Uncaught exception 'Exception' with message 'Tried to call unknown method OverloadedClass::ParamX' in overload.php:7
Stack trace:
#0 [internal function]: OverloadedClass->__call('ParamX', Array)
#1 overload.php(22): OverloadedClass->ParamX(4, 5, 6, 7)
#2 {main}
  thrown in overload.php on line 7
jstubbs at work-at dot co dot jp
02-Sep-2006 04:12
$myclass->foo['bar'] = 'baz';
 
When overriding __get and __set, the above code can work (as expected) but it depends on your __get implementation rather than your __set. In fact, __set is never called with the above code. It appears that PHP (at least as of 5.1) uses a reference to whatever was returned by __get. To be more verbose, the above code is essentially identical to:
 
$tmp_array = &$myclass->foo;
$tmp_array['bar'] = 'baz';
unset($tmp_array);
 
Therefore, the above won't do anything if your __get implementation resembles this:
 
function __get($name) {
   return array_key_exists($name, $this->values)
       ? $this->values[$name] : null;
 }
 
You will actually need to set the value in __get and return that, as in the following code:
 
function __get($name) {
   if (!array_key_exists($name, $this->values))
       $this->values[$name] = null;
   return $this->values[$name];
 }
mnaul at nonsences dot angelo dot edu
11-Jul-2006 07:58
This is just my contribution. It based off of many diffrent suggestions I've see thought the manual postings.
It should fit into any class and create default get and set methods for all you member variables. Hopfuly its usefull.
<?php
  
public function __call($name,$params)
   {
       if(
preg_match('/(set|get)(_)?/',$name) )
       {
           if(
substr($name,0,3)=="set")
           {
              
$name = preg_replace('/set(_)?/','',$name);
               if(
property_exists(__class__,$name))
               {
                      
$this->{$name}=array_pop($params);
                   return
true;
               }
               else
               {
                  
//call to class error handler
                  
return false;
               }
               return
true;
           }
           elseif(
substr($name,0,3)=="get")
           {
              
$name = preg_replace('/get(_)?/','',$name);
               if(
property_exists(__class__,$name) )
               {
                   return
$this->{$name};
               }
               else
               {
                  
//call to class error handler
                  
return false;
               }
           }
           else
           {
              
//call to class error handler
              
return false;
           }
       }
       else
       {
           die(
"method $name dose not exist\n");
       }
       return
false;
   }
me at brenthagany dot com
12-Apr-2006 05:52
Regarding the post by TJ earlier, about the problems extending DOMElement.  Yes, it is true that you can't set extDOMElement::ownerDocument directly; however, you could append the extDOMElement to a DOMDocument in __construct(), which indirectly sets ownerDocument.  It should work something like so:

class extDOMElement extends DOMElement {

  public function __construct(DOMDocument $doc) {
   $doc->appendChild($this);  //extDOMElement::ownerDocument is now equal to the object that $doc points to
  }

}

Now, I admit I've never actually needed to do this, but I see no reason why it shouldn't work.
Sleepless
24-Feb-2006 07:22
Yet another way of providing support for read-only properties.  Any property that has
"pri_" as a prefix will NOT be returned, period, any other property will be returned
and if it was declared to be "protected" or "private" it will be read-only. (scope dependent of course)

function __get($var){
       if (property_exists($this,$var) && (strpos($var,"pri_") !== 0) )
           return $this->{$var};
       else
           //Do something
}
Eric Lafkoff
22-Feb-2006 06:56
If you're wondering how to create read-only properties for your class, the __get() and __set() functions are what you're looking for. You just have to create the framework and code to implement this functionality.

Here's a quick example I've written. This code doesn't take advantage of the "type" attribute in the properties array, but is there for ideas.

<?php

class Test {
  
   private
$p_arrPublicProperties = array(
          
"id" => array("value" => 4,
                  
"type" => "int",
                  
"readonly" => true),
          
"datetime" => array("value" => "Tue 02/21/2006 20:49:23",
                  
"type" => "string",
                  
"readonly" => true),
          
"data" => array("value" => "foo",
                  
"type" => "string",
                  
"readonly" => false)
           );

   private function
__get($strProperty) {
      
//Get a property:
      
if (isset($this->p_arrPublicProperties[$strProperty])) {
           return
$this->p_arrPublicProperties[$strProperty]["value"];
       } else {
           throw new
Exception("Property not defined");
           return
false;
       }
   }
  
   private function
__set($strProperty, $varValue) {
      
//Set a property to a value:
      
if (isset($this->p_arrPublicProperties[$strProperty])) {
          
//Check if property is read-only:
          
if ($this->p_arrPublicProperties[$strProperty]["readonly"]) {
               throw new
Exception("Property is read-only");
               return
false;
           } else {
              
$this->p_arrPublicProperties[$strProperty]["value"] = $varValue;
               return
true;
           }
       } else {
           throw new
Exception("Property not defined");
           return
false;
       }
   }
  
   private function
__isset($strProperty) {
      
//Determine if property is set:
      
return isset($this->p_arrPublicProperties[$strProperty]);
   }
  
   private function
__unset($strProperty) {
      
//Unset (remove) a property:
      
unset($this->p_arrPublicProperties[$strProperty]);
   }   
          
}

$objTest = new Test();

print
$objTest->data . "\n";

$objTest->data = "bar"; //Works.
print $objTest->data;

$objTest->id = 5; //Error: Property is read-only.

?>
derek-php at seysol dot com
10-Feb-2006 08:08
Please note that PHP5 currently doesn't support __call return-by-reference (see PHP Bug #30959).

Example Code:

<?php

  
class test {
       public function &
__call($method, $params) {

          
// Return a reference to var2
          
return $GLOBALS['var2'];
       }
       public function &
actual() {

          
// Return a reference to var1
          
return $GLOBALS['var1'];
       }
   }

  
$obj = new test;
  
$GLOBALS['var1'] = 0;
  
$GLOBALS['var2'] = 0;

  
$ref1 =& $obj->actual();
  
$GLOBALS['var1']++;

   echo
"Actual function returns: $ref1 which should be equal to " . $GLOBALS['var1'] . "<br/>\n";

  
$ref2 =& $obj->overloaded();
  
$GLOBALS['var2']++;

   echo
"Overloaded function returns: $ref2 which should be equal to " . $GLOBALS['var2'] . "<br/>\n";

?>
PHP at jyopp dotKomm
22-Dec-2005 07:01
Here's a useful class for logging function calls.  It stores a sequence of calls and arguments which can then be applied to objects later.  This can be used to script common sequences of operations, or to make "pluggable" operation sequences in header files that can be replayed on objects later.

If it is instantiated with an object to shadow, it behaves as a mediator and executes the calls on this object as they come in, passing back the values from the execution.

This is a very general implementation; it should be changed if error codes or exceptions need to be handled during the Replay process.
<?php
class MethodCallLog {
   private
$callLog = array();
   private
$object;
  
   public function
__construct($object = null) {
      
$this->object = $object;
   }
   public function
__call($m, $a) {
      
$this->callLog[] = array($m, $a);
       if (
$this->object) return call_user_func_array(array(&$this->object,$m),$a);
       return
true;
   }
   public function
Replay(&$object) {
       foreach (
$this->callLog as $c) {
          
call_user_func_array(array(&$object,$c[0]), $c[1]);
       }
   }
   public function
GetEntries() {
      
$rVal = array();
       foreach (
$this->callLog as $c) {
          
$rVal[] = "$c[0](".implode(', ', $c[1]).");";
       }
       return
$rVal;
   }
   public function
Clear() {
      
$this->callLog = array();
   }
}

$log = new MethodCallLog();
$log->Method1();
$log->Method2("Value");
$log->Method1($a, $b, $c);
// Execute these method calls on a set of objects...
foreach ($array as $o) $log->Replay($o);
?>
trash80 at gmail dot com
04-Dec-2005 04:59
Problem: $a->b->c(); when b is not instantiated.
Answer: __get()

<?php

class a
{
   function
__get($v)
   {
      
$this->$v = new $v;
       return
$this->$v;
   }
}

class
b
{
   function
say($word){
       echo
$word;
   }
}
$a = new a();
$a->b->say('hello world');

// echos 'hello world'
?>
d11wtq
11-Nov-2005 09:07
Regarding the code posted by this user:
mileskeaton at gmail dot com
23-Dec-2004 03:23

There's no need to loop over your entire object.  I do this lots and all you need to do is convert the method name used into the name of a property, then check

if (isset($this->{$property_name}))
{
   //Get ready to do a set
}

I usually check if the developer called a get or set and also do some snity checking on the type of property they're requesting.
TJ <php at tjworld dot net>
04-Nov-2005 06:11
Using the getter/setter methods to provide read-only access to object properties breaks the conventional understanding of inheritence.

A super class using __set() to make a property read-only cannot be properly inherited because the visible (read-only) property - with conventional public or protected visibility - cannot be set in the sub-class.

The sub-class cannot overload the super class's __set() method either, and therefore the inheritence is severely compromised.

I discovered this issue while extending DOMDocument and particularly DOMElement.
When extDOMDocument->createElement() creates a new extDOMElement, extDOMElement->__construct() can't set the extDOMElement->ownerDocument property because it's read-only.

DOMElements are totally read-only if they do not have an ownerDocument, and there's no way to set it in this scenario, which makes inheritence pretty pointless.
seufert at gmail dot com
02-Nov-2005 12:25
This allows you to seeminly dynamically overload objects using plugins.

<PRE>
<?php

class standardModule{}

class
standardPlugModule extends standardModule
{
  static
$plugptrs;
  public
$var;
  static function
plugAdd($name, $mode, $ptr)
  {
  
standardPlugModule::$plugptrs[$name] = $ptr;
  }
  function
__call($fname, $fargs)
  { print
"You called __call($fname)\n";
  
$func = standardPlugModule::$plugptrs[$fname];
  
$r = call_user_func_array($func, array_merge(array($this),$fargs));
   print
"Done: __call($fname)\n";
   return
$r;
  }
  function
dumpplugptrs() {var_dump(standardPlugModule::$plugptrs); }
}

class
a extends standardPlugModule
{ function text() { return "Text"; } }
//Class P contained within a seperate file thats included
class p
{ static function plugin1($mthis, $r)
  { print
"You called p::plugin1\n";
  
print_r($mthis);
  
print_r($r);
  }
}
a::plugAdd('callme', 0, array('p','plugin1'));

//Class P contained within a seperate file thats included
class p2
{ static function plugin2($mthis, $r)
  { print
"You called p2::plugin2\n";
  
$mthis->callme($r);
  }
}
a::plugAdd('callme2', 0, array('p2','plugin2'));

$t = new a();
$testr = array(1,4,9,16);
print
$t->text()."\n";
$t->callme2($testr);
//$t->dumpplugptrs();

?>
</pre>

Will result in:
----------
Text
You called __call(callme2)
You called p2::plugin2
You called __call(callme)
You called p::plugin1
a Object
(
   [var] =>
)
Array
(
   [0] => 1
   [1] => 4
   [2] => 9
   [3] => 16
)
Done: __call(callme)
Done: __call(callme2)
----------

This also clears up a fact that you can nest __call() functions, you could use this to get around the limits to __get() not being able to be called recursively.
divedeep at yandex dot ru
24-Oct-2005 06:14
Small examle showing how to work with magic method __call and parameters, that u want to pass as a reference.

DOES NOT WORK:

<?php
  
class a {
      
       public function
__call($m,&$p) {
          
$p[0]=2;
       }
   }
  
  
$a= new a();
  
$b=1;
  
$a->foo($b);
   echo
$b;
  
// RETURNS 1
?>

WORKS:

<?php
  
class a {
      
       public function
__call($m,$p) {
          
$p[0]=2;
       }
   }
  
  
$a= new a();
  
$b=1;
  
$a->foo(&$b);
  
// RETURNS 2
  
echo $b;
?>
26-Aug-2005 02:32
To those who wish for "real" overloading: there's not really any advantage to using __call() for this -- it's easy enough with func_get_args(). For example:

<?php

  
class Test
  
{
      
       public function
Blah()
       {
          
          
$args = func_get_args();
          
           switch (
count($args))
           {
               case
1: /* do something */ break;
               case
2: /* do something */ break;
           }
          
       }
      
   }

?>
iv
26-Aug-2005 10:01
To "NOTE: getter cannot call getter"
When i changed
'using_getter' => 10 * $this->a
to
'using_getter' => 10 * $this->_a
the result became 60, how it should be
yuggoth23 at yahoo dot no dot spam dot com
09-Aug-2005 01:00
I just wanted to share with the rest of you since I had not seen it posted yet that __call does not work as a static method. Consider a utility class that has all static methods on it where one or more of those methods are overloaded. The snippet below will not work until you remove the "static" keyword from the __call function declaration:

class MyClass
{
   public static _call($method, $args)
   {
       switch($method)
       {
           case "Load":
               if(count($args) == 1)
                   $this->funcLoad1($args[0]);
               elseif(count($args) == 2)
                   $this->funcLoad2($args[0],$args[1]);
       }
   }

   private static funcLoad1($userID)
   {
       //do something
   }

   private static funcLoad2($userID, $isActive)
   {
       //do something slightly different
   }
}

This means any class that wishes to "overload" methods in PHP must be instantiatable in some regard. I like to use a pattern whereby one of the participants is a utility class exposing only static functions which operates on data exposed from an "info" or state-bag class. This is still possible in PHP, I suppose. It just means I'll need to incur overhead to instantiate my utility classes as well.

You CAN, by the way, call one static method from another using $this as I've done in the switch according to a "note" section on this page.
http://us3.php.net/language.oop

Again that may pre-suppose some level of instantiation for the class.
NOTE: getter cannot call getter
04-Aug-2005 07:37
By Design (http://bugs.php.net/bug.php?id=33998) you cannot call a getter from a getter or any function triggered by a getter:

class test
{
   protected $_a = 6;

   function __get($key) {
       if($key == 'stuff') {
           return $this->stuff();
       } else if($key == 'a') {
           return $this->_a;
       }
   }

   function stuff()
   {
       return array('random' => 'key', 'using_getter' => 10 * $this->a);
   }
}

$test = new test();
print 'this should be 60: '.$test->stuff['using_getter'].'<br/>';      // prints "this should be 60: 0"
// [[ Undefined property:  test::$a ]] on /var/www/html/test.php logged.
print 'this should be 6: '.$test->a.'<br/>';                            // prints "this should be 6: 6"
06-May-2005 10:50
Please note that PHP5's overloading behaviour is not compatible at all with PHP4's overloading behaviour.
Marius
02-May-2005 09:15
for anyone who's thinking about traversing some variable tree
by using __get() and __set(). i tried to do this and found one
problem: you can handle couple of __get() in a row by returning
an object which can handle consequential __get(), but you can't
handle __get() and __set() that way.
i.e. if you want to:
<?php
  
print($obj->val1->val2->val3); // three __get() calls
?> - this will work,
but if you want to:
<?php
   $obj
->val1->val2 = $val; // one __get() and one __set() call
?> - this will fail with message:
"Fatal error: Cannot access undefined property for object with
 overloaded property access"
however if you don't mix __get() and __set() in one expression,
it will work:
<?php
   $obj
->val1 = $val; // only one __set() call
  
$val2 = $obj->val1->val2; // two __get() calls
  
$val2->val3 = $val; // one __set() call
?>

as you can see you can split __get() and __set() parts of
expression into two expressions to make it work.

by the way, this seems like a bug to me, will have to report it.
ryo at shadowlair dot info
22-Mar-2005 06:22
Keep in mind that when your class has a __call() function, it will be used when PHP calls some other magic functions. This can lead to unexpected errors:

<?php
class TestClass {
   public
$someVar;

   public function
__call($name, $args) {
      
// handle the overloaded functions we know...
       // [...]

       // raise an error if the function is unknown, just like PHP would
      
trigger_error(sprintf('Call to undefined function: %s::%s().', get_class($this), $name), E_USER_ERROR);
   }
}

$obj = new TestClass();
$obj->someVar = 'some value';

echo
$obj; //Fatal error: Call to undefined function: TestClass::__tostring().
$serializedObj = serialize($obj); // Fatal error: Call to undefined function: TestClass::__sleep().
$unserializedObj = unserialize($someSerializedTestClassObject); // Fatal error: Call to undefined function: TestClass::__wakeup().
?>
thisisroot at gmail dot com
18-Feb-2005 03:27
You can't mix offsetSet() of the ArrayAccess interface (http://www.php.net/~helly/php/ext/spl/interfaceArrayAccess.html) and __get() in the same line.

Below, "FileManagerPrefs" is an object of class UserData which implements ArrayAccess. There's a protected array of UserData objects in the User class, which are returned from __get().
<?php
// This produces an error...
Application::getInstance()->user->FileManagerPrefs[ 'base'] = 'uploads/jack';
?>

Creates this error:
Fatal error: Cannot access undefined property for object with overloaded property access in __FILE__ on line __LINE__

However, __get() and offsetGet() play deceptively well together.

<?php
// This works fine!
echo Application::getInstance()->user->FileManager['base'];
?>

I guess it's a dereferencing issue with __get(). In my case, it makes more sense to have a middle step (user->data['FileManager']['base']), but I wanted to tip off the community before I move on.
mileskeaton at gmail dot com
23-Dec-2004 08:23
<?php

## THE PROBLEM:  Class with lots of attributes. 
## You want to use $o->getVarName() or $o->get_varname() style getters
## Some attributes have custom get functions, but the rest don't

## THE SOLUTION:  __call

class Person
  
{
  
## this top stuff is just an example.  could be anything.
  
private $name;
   private
$age;
   private
$weight;
   function
__construct($name, $age, $weight)
       {
      
$this->name = $name;
      
$this->age = $age;
      
$this->weight = $weight;
       }

  
##    PORTABLE: use this __call function in any class
  
function __call($val, $x)
       {
      
# see if they're calling a getter method - and try to guess the variable requested
      
if(substr($val, 0, 4) == 'get_')
           {
          
$varname = substr($val, 4);
           }
       elseif(
substr($val, 0, 3) == 'get')
           {
          
$varname = substr($val, 3);
           }
       else
           {
           die(
"method $val does not exist\n");
           }
      
# now see if that variable exists:
      
foreach($this as $class_var=>$class_var_value)
           {
           if(
strtolower($class_var) == strtolower($varname))
               {
               return
$class_var_value;
               }
           }
       return
false;
       }

  
# IMPORTANT: you can keep some things private - or treat
   # some vars differently by giving them their own getter method
   # See how this function lies about Person's weight.
  
function getWeight()
       {
       return
intval($this->weight * .8);
       }
   }

$a = new Person('Miles', 35, 200);

# these all work (case-insensitive):
print $a->get_name() . "\n";
print
$a->getName() . "\n";
print
$a->get_Name() . "\n";
print
$a->getname() . "\n";

print
$a->get_age() . "\n";
print
$a->getAge() . "\n";
print
$a->getage() . "\n";
print
$a->get_Age() . "\n";

# defined functions still override the __call
print $a->getWeight() . "\n";

# trying to get something that doesn't exist returns false
print $a->getNothing();

# this still gets error:
print $a->hotdog();

?>
richard dot quadling at bandvulc dot co dot uk
26-Nov-2004 01:54
<?php

abstract class BubbleMethod
  
{
   public
$objOwner;

   function
__call($sMethod, $aParams)
       {
// Has the Owner been assigned?
      
if (isset($this->objOwner))
           {
           return
call_user_func_array(array($this->objOwner, $sMethod), $aParams);
           }
       else
           {
           echo
'Owner for ' . get_class($this) . ' not assigned.';
           }
       }
   }

class
A_WebPageContainer
  
{
   private
$sName;
  
   public function
__construct($sName)
       {
      
$this->sName = $sName;
       }
  
   public function
GetWebPageContainerName()
       {
       return
$this->sName;
       }
   }

class
A_WebFrame extends BubbleMethod
  
{
   private
$sName;
  
   public function
__construct($sName)
       {
      
$this->sName = $sName;
       }
  
   public function
GetWebFrameName()
       {
       return
$this->sName;
       }
   }

class
A_WebDocument extends BubbleMethod
  
{
   private
$sName;
  
   public function
__construct($sName)
       {
      
$this->sName = $sName;
       }
  
   public function
GetWebDocumentName()
       {
       return
$this->sName;
       }
   }

class
A_WebForm extends BubbleMethod
  
{
   private
$sName;
  
   public function
__construct($sName)
       {
      
$this->sName = $sName;
       }
  
   public function
GetWebFormName()
       {
       return
$this->sName;
       }
   }

class
A_WebFormElement extends BubbleMethod
  
{
   private
$sName;
  
   public function
__construct($sName)
       {
      
$this->sName = $sName;
       }
  
   public function
GetWebFormElementName()
       {
       return
$this->sName;
       }
   }

$objWPC = new A_WebPageContainer('The outer web page container.');

$objWF1 = new A_WebFrame('Frame 1');
$objWF1->objOwner = $objWPC;

$objWF2 = new A_WebFrame('Frame 2');
$objWF2->objOwner = $objWPC;

$objWD1 = new A_WebDocument('Doc 1');
$objWD1->objOwner = $objWF1;

$objWD2 = new A_WebDocument('Doc 2');
$objWD2->objOwner = $objWF2;

$objWFrm1 = new A_WebForm('Form 1');
$objWFrm1->objOwner = $objWD1;

$objWFrm2 = new A_WebForm('Form 2');
$objWFrm2->objOwner = $objWD2;

$objWE1 = new A_WebFormElement('Element 1');
$objWE1->objOwner = $objWFrm1;

$objWE2 = new A_WebFormElement('Element 2');
$objWE2->objOwner = $objWFrm1