类与对象
在线手册:中文 英文
PHP手册

重载

PHP所提供的"重载"(overloading)是指动态地"创建"类属性和方法。我们是通过 魔术方法(magic methods)来实现的。

当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。本节后面将使用 "不可访问属性(inaccessible properties)"和"不可访问方法(inaccessible methods)"来称呼这些未定义或不可见的类属性或方法。

所有的重载方法都必须被声明为public

Note:

这些魔术方法的参数都不能 通过引用传递

Note:

PHP中的"重载"与其它绝大多数面向对象语言不同。传统的"重载"是用于提供多个同名的 类方法,但各方法的参数类型和个数不同。

更新日志

版本 说明
5.3.0 新增 __callStatic()魔术方法. 可见性未设置为public或未声明为static的时候会产生一个警告
5.1.0 新增 __isset()__unset()两个魔术方法.

属性重载

public void __set ( string $name , mixed $value )
public mixed __get ( string $name )
public bool __isset ( string $name )
public void __unset ( string $name )

在给未定义的变量赋值时,__set() 会被调用。

读取未定义的变量的值时,__get() 会被调用。

当对未定义的变量调用isset()empty()时,__isset() 会被调用。

当对未定义的变量调用unset()时,__unset() 会被调用。

参数$name是指要操作的变量名称。__set() 方法的$value 参数指定了$name变量的值。

属性重载只能在对象中进行。在静态方法中,这些魔术方法将不会被调用。所以这些方法都不能被 声明为static。 从PHP 5.3.0起, 将这些魔术方法定义为static会产生一个警告。

Note:

因为PHP处理赋值运算的方式, __set() 的返回值将被忽略。 类似的, 在下面这样的链式赋值中, __get() 不会被调用:

 $a = $obj->b = 8; 

Note:

在除isset外的其它语言结构中无法使用重载的属性, 这意味着当对一个重载的属性 使用empty时,重载魔术方法将不会被调用。 (译注:此特性在新版本已改变,英文手册未更新)

为避开此限制,可以将重载属性赋值到本地变量再使用empty。

Example #1 使用__get(), __set(), __isset()__unset()进行重载

<?php
class PropertyTest {
      
/**  被重载的数据保存在此  */
    
private $data = array();

 
    
/**  重载不能被用在已经定义的属性  */
    
public $declared 1;

       
/**  只有从类外部访问这个属性时,重载才会发生 */
    
private $hidden 2;

    public function 
__set($name$value
    {
        echo 
"Setting '$name' to '$value'\n";
        
$this->data[$name] = $value;
    }

    public function 
__get($name
    {
        echo 
"Getting '$name'\n";
        if (
array_key_exists($name$this->data)) {
            return 
$this->data[$name];
        }

        
$trace debug_backtrace();
        
trigger_error(
            
'Undefined property via __get(): ' $name .
            
' in ' $trace[0]['file'] .
            
' on line ' $trace[0]['line'],
            
E_USER_NOTICE);
        return 
null;
    }

    
/**  PHP 5.1.0之后版本 */
    
public function __isset($name
    {
        echo 
"Is '$name' set?\n";
        return isset(
$this->data[$name]);
    }

    
/**  PHP 5.1.0之后版本 */
    
public function __unset($name
    {
        echo 
"Unsetting '$name'\n";
        unset(
$this->data[$name]);
    }

    
/**  非魔术方法  */
    
public function getHidden() 
    {
        return 
$this->hidden;
    }
}


echo 
"<pre>\n";

$obj = new PropertyTest;

$obj->1;
echo 
$obj->"\n\n";

var_dump(isset($obj->a));
unset(
$obj->a);
var_dump(isset($obj->a));
echo 
"\n";

echo 
$obj->declared "\n\n";

echo 
"Let's experiment with the private property named 'hidden':\n";
echo 
"Privates are visible inside the class, so __get() not used...\n";
echo 
$obj->getHidden() . "\n";
echo 
"Privates not visible outside of class, so __get() is used...\n";
echo 
$obj->hidden "\n";
?>

以上例程会输出:

Setting 'a' to '1'
Getting 'a'
1

Is 'a' set?
bool(true)
Unsetting 'a'
Is 'a' set?
bool(false)

1

Let's experiment with the private property named 'hidden':
Privates are visible inside the class, so __get() not used...
2
Privates not visible outside of class, so __get() is used...
Getting 'hidden'


Notice:  Undefined property via __get(): hidden in <file> on line 70 in <file> on line 29

方法重载

public mixed __call ( string $name , array $arguments )
public static mixed __callStatic ( string $name , array $arguments )

当调用一个不可访问方法(如未定义,或者不可见)时,__call() 会被调用。

当在静态方法中调用一个不可访问方法(如未定义,或者不可见)时,__callStatic() 会被调用。

$name参数是要调用的方法名称。$arguments参数是一个数组,包含着要传递给方法的参数。

Example #2 使用 __call()__callStatic() 对方法重载

<?php
class MethodTest 
{
    public function 
__call($name$arguments
    {
        
// 注意: $name 的值区分大小写
        
echo "Calling object method '$name' "
             
implode(', '$arguments). "\n";
    }

    
/**  PHP 5.3.0之后版本  */
    
public static function __callStatic($name$arguments
    {
        
// 注意: $name 的值区分大小写
        
echo "Calling static method '$name' "
             
implode(', '$arguments). "\n";
    }
}

$obj = new MethodTest;
$obj->runTest('in object context');

MethodTest::runTest('in static context');  // PHP 5.3.0之后版本
?>

以上例程会输出:

Calling object method 'runTest' in object context
Calling static method 'runTest' in static context

类与对象
在线手册:中文 英文
PHP手册
PHP手册 - N: 重载

用户评论:

smljackson402 at gmail dot com (28-Mar-2012 05:15)

Is the following class is valid.

class TestCase{
       public $test;

      public setMe($data)
     {
        foreach($data as $name => $value)
       {
         $this->{$name} = $value;
       }
     }

}

$data = array('test1'=>1,'test2'=>2);

$test_case = new TestCase();
$test_case->setMe($data);
echo $test_case->test1;

What i am trying to do is create dynamic class variables.

theaceofthespade at gmail dot com (24-Mar-2012 05:35)

A word of warning!  It may seem obvious, but remember, when deciding whether to use __get, __set, and __call as a way to access the data in your class (as opposed to hard-coding getters and setters), keep in mind that this will prevent any sort of autocomplete, highlighting, or documentation that your ide mite do.

Furthermore, it beyond personal preference when working with other people.  Even without an ide, it can be much easier to go through and look at hardcoded member and method definitions in code, than having to sift through code and piece together the method/member names that are assembled in __get and __set.

If you still decide to use __get and __set for everything in your class, be sure to include detailed comments and documenting, so that the people you are working with (or the people who inherit the code from you at a later date) don't have to waste time interpreting your code just to be able to use it.

_ at apakoh dot dk (17-Feb-2012 12:11)

I find the following use of __set and __get useful:

<?php
class Post {
  private
$title;
  private
$content;
  private
$author;
  private
$comments;

  private
$_getters = array('title', 'content', 'author', 'comments');
  private
$_setters = array('title', 'content', 'author');
 
  public function
__get($property) {
    if (
in_array($property, $this->_setters)) {
      return
$this->$property;
    }
    else if (
method_exists($this, '_get_' . $property))
      return
call_user_func(array($this, '_get_' . $property));
    else if (
in_array($property, $this->_getters) OR method_exists($this, '_set_' . $property))
      throw new
Exception('Property "' . $property . '" is write-only.');
    else
      throw new
Exception('Property "' . $property . '" is not accessible.');
  }

  public function
__set($property, $value) {
    if (
in_array($property, $this->_getters)) {
     
$this->$property = $value;
    }
    else if (
method_exists($this, '_set_' . $property))
     
call_user_func(array($this, '_set_' . $property), $value);
    else if (
in_array($property, $this->_setters) OR method_exists($this, '_get_' . $property))
      throw new
Exception('Property "' . $property . '" is read-only.');
    else
      throw new
Exception('Property "' . $property . '" is not accessible.');
  }
}
?>

This way the variables in the $_getters array can be read from the outside and the variables in the $_setters array can be modified from the outside, like this:

<?php
$post
= new Post();
$post->title = 'Hello, World';
echo
$post->title;

// The following will throw an exception since $comments is read-only:
$post->comments = 23;
?>

And in case you need a less generic getter or setter at some point, you can remove the variable from the $_getters or $_setters array and implement a method like:

<?php
private function _set_title($value) {
 
$this->title = str_replace('World', 'Universe', $value);
}
?>

And from the outside the property could still be used with:

<?php
$post
->title = 'Hello, World!';
?>

I've tried a couple of different ways of doing this, but this seems to be the fastest (although still around 2 times slower than using ugly old school getter- and setter-methods).
Functions like property_exists() are unnecessary and only makes thing even slower (my initial attempt at using __set and __get was up to 9 times slower than traditional getter/setter methods).

Could probably still be improved/changed in different ways depending on context.

Voitcus at wp dot pl (02-Nov-2011 08:57)

If a class is being extended by another class, its __get and __set methods (and others) can also be overloaded themselves:

<?php
class A {
  public function
__get($value){
    switch(
$value){
      case
"a1": return 1; break;
      case
"a2": return 2; break;
    default:
// error handler goes here
     
return "number out of range"; // or NULL or anything else
      
}
     }
// end of method __get
  
} // end of class A

class B extends A {
  public function
__get($value){
     switch(
$value){
       case
"a1": return 11; break; // we overload value of 1
      
case "b3": return 3; break; // we add these new properties
      
case "b4": return 4; break;
     default:
// check ancestor's properties
      
return parent::__get($value);
       }
     }
// end of method __get
  
} //end of class B

$a=new A();
$b=new B();

echo
$a->a1; // produces 1
echo $a->a2; // produces 2
echo $a->b3; // produces number out of range

echo $b->a1; // produces 11
echo $b->a2; // produces 2
echo $b->b3; // produces 3
echo $b->b4; // produces 4
echo $b->b5; // produces number out of range
?>

If class B's default switch option is reached, the class A's getter is called. So the extending class B overloads property "a1", adds its own properties "b3" and "b4", and inherits property "a2". This solution can't, however, overload class A's error handler (the line with "number out of range").
Should it be needed, consider the following error handler of the class B:

<?php
    
default: // error handler goes here
      
$p=parent::__get($value);
       if(!
is_numeric($p)){
          return
"no value"; // overload class A's error message
         
}
       else{
           return
$p; // return class A's base properties
          
}
?>
So we have new "error handler"
<?php
echo $b->b5; // produces no value
?>

ari at asu dot edu (31-Oct-2011 09:57)

It may be important to note that when __set($name, $value) is called, $name gets entered into the symbol table for that call stack. This means that if a property is set within __set, it will only work if that property's name appears in the call stack (ie if it matches $name). If not, __set will be called again on the new property recursively.

<?php
class test
{
    function
__set($name, $value)
    {
        echo
"__set($name, $value);\n";
       
$this->prop3 = $value; // This will call __set(prop3, value)
       
$this->prop2 = $value; // This will call __set(prop2, value)
       
$this->prop1 = $value; // This will NOT call __set(prop1, value) because $name == prop1.
   
}
}

$test = new test;
$test->prop1 = 'value';
?>

It could be thought of like this: within the function __set($name, $value), $this->[property name] = [value] will recurse unless property name == $name. Within the call stack of __set, __set will never recurse on the same name twice. Once you leave __set (without actually creating the property), the call stack ends and all bets are off.

What this means:
You cannot do things like setting an (uppercase) property to lowercase within __set without expecting __set to be called twice. If you were to add an underscore to the property name, you can expect an infinite recursion loop.

I hope someone finds this useful. It drove me nuts for a few hours.

matt at mcmcms dot com (13-Oct-2011 01:31)

If you need to try and imitate the functionality of the __get() and __set() magic methods whilst outside of the object-scope (i.e. using a static method) this small __callStatic() implementation may help you out.

<?php

class config
{

private static
$_propertyArray = array();

        public static function
__callStatic( $method, $args )
        {
           
$property = $args[0];
           
                switch(
$method )
                {
                    case
'set':
                       
// set property
                           
self::$_propertyArray[ $property ] = $args[1];
                    break;
           
                    case
'get':
                       
// get property
                           
if( isset( self::$_propertyArray[ $property ] ) )
                            {
                                return
self::$_propertyArray[ $property ];
                            }
                            else {
                                throw new
Exception( "Property ({$property}) does not exist", 0 );
                            }
                    break;
           
                    default:
                       
// do nothing..
                   
break;
                }
        }
}

config::set( 'my_property', 'someValue' );

print
"The value of 'my_property' is: " . config::get( 'my_property' );

// Will output: The value of 'my_property' is: someValue

?>

dans at dansheps dot com (01-Aug-2011 04:38)

Since this was getting me for a little bit, I figure I better pipe in here...

For nested calls to private/protected variables(probably functions too) what it does is call a __get()  on the first object, and if you return the nested object, it then calls a __get() on the nested object because, well it is protected as well.

EG:
<?php
class A
{
protected
$B

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

public function
__get($variable)
{
echo
"Class A::Variable " . $variable . "\n\r";
$retval = $this->{$variable};
return
$retval;
}
}

class
B
{
protected
$val

public function __construct()
{
$this->val = 1;
}

public function
__get($variable)
{
echo
"Class B::Variable " . $variable . "\n\r";
$retval = $this->{$variable};
return
$retval;
}
}

$A = new A();

echo
"Final Value: " . $A->B->val;
?>

That will return something like...

Class A::Variable B
Class B::Variable val
Final Value: 1

It seperates the calls into $A->B and $B->val

Hope this helps someone

jk at jankriedner dot de (07-Jun-2011 11:15)

You should take care when using properties retrieved via __get() in functions that expect arguments to be passed by reference (e.g. mysqli_stmt_bind_param). The reference is NOT set to the property itself, but to the value returned by __get().
Thus, binding a property retrieved via __get() to a statement will let the statement be executed always with the value the property had when calling bind_param, not with the current value it has when calling execute().
E.g.:
<?php
error_reporting
(E_ALL);
class
foo {
    protected
$bar;
   
    public function
__construct() {
       
$this->bar = "Hello World!";
    }
    public static function
factory() {
        return new
self;
    }
    public function
__get($property) {
        if(!
property_exists($this,$property)) {
            throw new
InvalidArgumentException("Property {$property} doesn't exist");
        }
        return
$this->$property;
    }
    public function
setBar($value) {
       
$this->bar = $value;
    }
}
$foo = new foo();
echo
$foo->bar;    // Ouputs: Hello World!
$db = new mysqli("localhost","root","","tests");
$sql = "INSERT INTO foo SET bar=?";
$res = $db->prepare($sql);
$res->bind_param("s",$foo->bar);  // Notice: Indirect modification of overloaded property foo::$bar has no effect in /var/www/overload.php on line 24
$res->execute();  // Writes "Hello World!" to database
$foo->setBar("Goodbye");
echo
$foo->bar;   // Outputs: Goodbye
$res->execute();  // Writes "Hello World!" to database
?>

Daniel Smith (24-May-2011 12:15)

Be careful of __call in case you have a protected/private method. Doing this:

<?php
class TestMagicCallMethod {
    public function
foo()
    {
        echo
__METHOD__.PHP_EOL;
    }

    public function
__call($method, $args)
    {
        echo
__METHOD__.PHP_EOL;
        if(
method_exists($this, $method))
        {
           
$this->$method();
        }
    }
   
    protected function
bar()
    {
        echo
__METHOD__.PHP_EOL;
    }

    private function
baz()
    {
        echo
__METHOD__.PHP_EOL;
    }
}

$test    =    new TestMagicCallMethod();
$test->foo();
/**
 * Outputs:
 * TestMagicCallMethod::foo
 */

$test->bar();
/**
 * Outputs:
 * TestMagicCallMethod::__call
 * TestMagicCallMethod::bar
 */

$test->baz();
/**
 * Outputs:
 * TestMagicCallMethod::__call
 * TestMagicCallMethod::baz
 */
?>

..is probably not what you should be doing. Always make sure that the methods you call in __call are allowed as you probably dont want all the private/protected methods to be accessed by a typo or something.

jan dot machala at email dot cz (15-Apr-2011 11:47)

Example of usage __call() to have implicit getters and setters

<?php
class Entity {
    public function
__call($methodName, $args) {
        if (
preg_match('~^(set|get)([A-Z])(.*)$~', $methodName, $matches)) {
           
$property = strtolower($matches[2]) . $matches[3];
            if (!
property_exists($this, $property)) {
                throw new
MemberAccessException('Property ' . $property . ' not exists');
            }
            switch(
$matches[1]) {
                case
'set':
                   
$this->checkArguments($args, 1, 1, $methodName);
                    return
$this->set($property, $args[0]);
                case
'get':
                   
$this->checkArguments($args, 0, 0, $methodName);
                    return
$this->get($property);
                case
'default':
                    throw new
MemberAccessException('Method ' . $methodName . ' not exists');
            }
        }
    }

    public function
get($property) {
        return
$this->$property;
    }

    public function
set($property, $value) {
       
$this->$property = $value;
        return
$this;
    }

    protected function
checkArguments(array $args, $min, $max, $methodName) {
       
$argc = count($args);
        if (
$argc < $min || $argc > $max) {
            throw new
MemberAccessException('Method ' . $methodName . ' needs minimaly ' . $min . ' and maximaly ' . $max . ' arguments. ' . $argc . ' arguments given.');
        }
    }
}

class
MemberAccessException extends Exception{}

class
Foo extends Entity {
    protected
$a;
}

$foo = new Foo();
$foo->setA('some'); // outputs some
echo $foo->getA();

class
Bar extends Entity {
    protected
$a;
   
/**
     * Custom setter.
     */
   
public function setA($a) {
        if (!
preg_match('~^[0-9a-z]+$~i', $a)) {
            throw new
MemberAccessException('A can be only alphanumerical');
        }
       
$this->a = $a;
        return
$this;
    }
}

$bar = new Bar();
$bar->setA('abc123'); // ok
$bar->setA('[]/*@...'); // throws exception
?>

dvessel (30-Mar-2011 01:46)

A controlled way to chain the results of a method call onto a new property. I thought it was impossible to do cleanly but I finally found a decent way of handling it.

<?php

class main {
  public function
newMember($value) {
    return new
sub($this, $value);
  }
}

class
sub {
  protected
$parent;
  protected
$value;

  public function
__construct($parent, $value) {
   
$this->parent = $parent;
   
$this->value  = $value;
  }
  public function
__get($name) {
   
$this->parent->$name = $this->value;
    return
$this;
  }
  public function
__call($name, $args) {
   
// Invoke parent class.
   
return call_user_func_array(array($this->parent, $name), $args);
  }
}

$object = new main();

$object->newMember('value for foo')->foo->newMember('value for bar')->bar;

print
var_dump($object->foo);
print
var_dump($object->bar);

// prints out "value for foo" and "value for bar".

?>

Without the secondary 'sub' class, the chaining can be unpredictable since it doesn't carry over any state information. This way you can encapsulate the results into the supporting class and let it do the work.

Anonymous (19-Dec-2010 11:12)

Using anon functions, you can allow your class methods to be declared after your class is already set.  Though you cannot use a class property as a function in any way (including for properties that are objects with their own __invoke() method) without triggering a fatal error, there is a workaround using an array property to store all these anon functions...

<?php

class Test {
   
// generic class allowing dynamic addition of methods

   
public $anon_list = array(); // list of all custom added methods

   
function __call($fnc, $args) {return call_user_func_array($this->anon_list[$fnc], $args);}
   
    function
__set($name, $val) {$this->anon_list[$name] = $val;}
   
    }

// Example adding a new custom method
$test = new Test;
$test->do_something = function ($var1, $var2) {echo "custom called with $var1 and $var2\n";};
$test->do_something('asdf', 'test');

?>

Unfortunately, without access to $this within the anon functions, it's not really useful.  You can "use ($test)" in your anon function, but that just makes it more complicated.

php at lanar dot com dot au (13-Jun-2010 01:39)

Note that __isset is not called on chained checks.
If isset( $x->a->b ) is executed where $x is a class with __isset() declared, __isset() is not called.

<?php

class demo
{
    var
$id ;
    function
__construct( $id = 'who knows' )
    {
       
$this->id = $id ;
    }
    function
__get( $prop )
    {
        echo
"\n", __FILE__, ':', __LINE__, ' ', __METHOD__, '(', $prop, ') instance ', $this->id ;
        return new
demo( 'autocreated' ) ; // return a class anyway for the demo
   
}
    function
__isset( $prop )
    {
        echo
"\n", __FILE__, ':', __LINE__, ' ', __METHOD__, '(', $prop, ') instance ', $this->id ;
        return
FALSE ;
    }
}
$x = new demo( 'demo' ) ;
echo
"\n", 'Calls __isset() on demo as expected when executing isset( $x->a )' ;
$ret = isset( $x->a ) ;
echo
"\n", 'Calls __get() on demo without call to __isset()  when executing isset( $x->a->b )' ;
$ret = isset( $x->a->b ) ;
?>

Outputs

Calls __isset() on demo as expected when executing isset( $x->a )
C:\htdocs\test.php:31 demo::__isset(a) instance demo
Calls __get() on demo without call to __isset()  when executing isset( $x->a->b )
C:\htdocs\test.php:26 demo::__get(a) instance demo
C:\htdocs\test.php:31 demo::__isset(b) instance autocreated

navarr at gtaero dot net (15-May-2010 09:25)

If you want to make it work more naturally for arrays $obj->variable[] etc you'll need to return __get by reference.

<?php
class Variables
{
        public function
__construct()
        {
                if(
session_id() === "")
                {
                       
session_start();
                }
        }
        public function
__set($name,$value)
        {
               
$_SESSION["Variables"][$name] = $value;
        }
        public function &
__get($name)
        {
                return
$_SESSION["Variables"][$name];
        }
        public function
__isset($name)
        {
                return isset(
$_SESSION["Variables"][$name]);
        }
}
?>

~A! (27-Apr-2010 05:03)

Dynamic getting-setting using __call and __callStatic
<?php

  
class A {
        private
$fields;

        public function
__call($func, $args) {
            if (empty(
$args)) {
                return
$this->fields[$args[0]];
            } else {
               
$this->$fields[$func] = $args[0];
        }
    }

?>

Or statically:

<?php
   
class foo {
        static
$vals;
        public static function
__callStatic($func, $args) {
            if (!empty(
$args)) {
               
self::$vals[$func] = $args[0];
            } else {
                return
self::$vals[$func];
            }
        }
    }
?>

Which would allow you to say:

<?php
    Foo
::username('john');
    print
Foo::username(); // prints 'john'
?>

zzzzbov (26-Apr-2010 08:30)

I've written a brief, generic function for __get() and __set() that works well implementing accessor and mutator functions.

This allows the programmer to use implicit accessor and mutator methods when working with attribute data.

<?php

class MyClass
{
    private
$degrees

   
public function __get($name)
    {
       
$fn_name = 'get_' . $name;
        if (
method_exists($this, $fn_name))
        {
            return
$this->$fn_name();
        }
        else
        {
            return
null;
        }
    }

    public function
__set($name, $value)
    {
       
$fn_name = 'set_' . $name;
        if (
method_exists($this, $fn_name))
        {
           
$this->$fn_name($value);
        }
    }

    private function
get_degrees()
    {
        return
$this->degrees;
    }
   
    private function
set_degrees($value)
    {
       
$this->degrees = $value % 360;
        if (
$degrees < 0) $this->degrees += 360;
    }
}

?>

f4bi0_ at hotmail dot com (01-Apr-2010 09:12)

TRICKY CHALLENGE: is there a way to check if $object->variable exists without automatically triggering the functionality of __get in case the variable doesn't exist?

TRADITIONAL WAY:
<?php

$object
= new someClass(); // imagine someClass uses the Magic Method "__get"

if($object->variable){
 
// do something

} else {

 
// "__get" has inevitably been triggered :-(

}

?>

Ok, above we have the conventional way to check if a variable exists inside the object, the problem is that sometimes we DON'T WANT TO TRIGGER __GET in case the statement returns false!

ALTERNATIVE WAY:

<?php
if(array_key_exists( 'variable', get_object_vars($object) ) ){

 
// do something

} else {

 
// "__get" has no idea what is going on, i think it is still asleep :-)

}

?>

you can even turn this into a function or something

strata_ranger at hotmail dot com (07-Oct-2009 05:56)

Combining two things noted previously:

1 - Unsetting an object member removes it from the object completely, subsequent uses of that member will be handled by magic methods.
2 - PHP will not recursively call one magic method from within itself (at least for the same $name).

This means that if an object member has been unset(), it IS possible to re-declare that object member (as public) by creating it within your object's __set() method, like this:

<?php
class Foo
{
  function
__set($name, $value)
  {
   
// Add a new (public) member to this object.
    // This works because __set() will not recursively call itself.
   
$this->$name= $value;
  }
}

$foo = new Foo();

// $foo has zero members at this point
var_dump($foo);

// __set() will be called here
$foo->bar = 'something'; // Calls __set()

// $foo now contains one member
var_dump($foo);

// Won't call __set() because 'bar' is now declared
$foo->bar = 'other thing';

?>

Also be mindful that if you want to break a reference involving an object member without triggering magic functionality, DO NOT unset() the object member directly.  Instead use =& to bind the object member to any convenient null variable.

Franz (05-Oct-2009 06:59)

It says in the documentation that assignment chaining does not work with the __set() function like this:

<?php
$a
= $foo->b = 3;
?>

If you still want to make it work, I suppose you can just turn the variables around:

<?php
$foo
->b = $a = 3;
?>

kexianbin at diyism dot com (21-Aug-2009 07:29)

By using __call, we can use php as using jQuery:

<?php
//SoWork PHP Framework
define('this', mt_rand());
$o='so_work';
class
cls_so_work
     
{function __call($fun, $pars)
                {foreach (
$pars as &$v)
                         {if (
$v===this)
                             {
$v=$this->val;
                              break;
                             }
                         }
                
$tmp=eval('return defined("'.$fun.'")?constant("'.$fun.'"):"'.$fun.'";');
                
$this->val=call_user_func_array($tmp, $pars);
                 return
$this;
                }
       function
cls_so_work($obj)
                {
$this->val=isset($obj)?$obj:null;
                }
      }
function
so_work($obj)
         {if (isset(
$obj))
             {return new
cls_so_work($obj);
             }
          else
              {if (!isset(
$GLOABALS['so_work']))
                  {
$GLOABALS['so_work']=new cls_so_work();
                  }
               else
                   {
$GLOABALS['so_work']->val=null;
                   }
               return
$GLOABALS['so_work'];
              }
         }

define('echo', 'my_echo');
function
my_echo($obj)
         {echo
$obj;
          return
$obj;
         }

$o('abcd')->substr(this, 2, 2)->strlen(this)->echo(this);
$o()->substr('abcd', 1, 3)->strlen(this)->echo(this);
?>

eric dot druid+php dot net at gmail dot com (02-Jul-2009 12:22)

I needed to know from where a member variable was set from to determine visibility.

<?php
class foo {
  private
$data;
  public function
__set($name, $value) {
   
$trace = debug_backtrace();
    if(!isset(
$trace[1]) || $trace[1]['object'] != $trace[0]['object']) {
      die(
"Trying to set protected member '$name' from public scope.");
    }
   
$data[$name] = $value;
  }
}
?>

Anonymous (05-Jun-2009 11:54)

It is possible to accomplish method polymorphism via PHP's __call method:
<?php
class Example{
    public function
__call($name, $arguments){
        switch(
$name){
            case
'foo':
                switch(
count($arguments)){
                    case
2:
                        echo
'You called "foo" with two arguments.<br>' . PHP_EOL;
                        break;
                    case
3:
                        echo
'You called "foo" with three arguments.<br>' . PHP_EOL;
                        break;
                    default:
                        echo
'Error: Invalid number of arguments to "foo."<br>' . PHP_EOL;
                        break;
                }
                break;
            default:
                echo
"Error: Call to undefined function \"$name.\"<br>" . PHP_EOL;
        }
    }
}

$test = new Example;
$test->foo('bar', 'baz');
$test->foo('bar', 'baz', 'fez', 'fap');
$test->bar();
?>

troggy dot brains at gmx dot de (19-May-2009 08:17)

Just another note about using __get() & __set() on array properties with PHP v5.2.0.
Took me quite a while to find out why I couldn't write an array property that was created via __set(). Now a few notes earlier someone described a working solution using the ArrayObject-class. This does work, but has a big downside, you cannot expect other programmers to know that they will be getting an instance of ArrayObject instead of a native array, and trying to do an implode() on an ArrayObject will just not work. So, here is my solution to the problem:

<?php
class A {
  private
$properties = array();

  public function
__set($key, $value) {
    if (
is_array($value)) {
     
$this->$key = $value;
    } else {
     
$this->properties[$key] = $value;
    }
  }

  public function
__get($key) {
    if (
array_key_exists($key, $this->properties)) {
      return
$this->properties[$key];
    }

    return
null;
  }
}
?>

The idea behind this is, that you can register new properties at any time from within a class without getting a warning or some of that kind. Now, if you try to set a property from outside the class, the __set() method will be called and decides whether to place the new property in the properties array or declare it as a new "native" property. When trying to get that property, the __get() method will only be called if the property is not "native", which won't be the case for an array.

HTH

daevid at daevid dot com (15-May-2009 05:16)

Here's a handy little routine to suggest properties you're trying to set that don't exist. For example:

Attempted to __get() non-existant property/variable 'operator_id' in class 'User'.

checking for operator and suggesting the following:

    * id_operator
    * operator_name
    * operator_code

enjoy.

<?php
   
/**
     * Suggests alternative properties should a __get() or __set() fail
     *
     * @param     string $property
     * @return string
     * @author Daevid Vincent [daevid@daevid.com]
     * @date    05/12/09
     * @see        __get(), __set(), __call()
     */
   
public function suggest_alternative($property)
    {
       
$parts = explode('_',$property);
        foreach(
$parts as $i => $p) if ($p == '_' || $p == 'id') unset($parts[$i]);

        echo
'checking for <b>'.implode(', ',$parts)."</b> and suggesting the following:<br/>\n";

        echo
"<ul>";
        foreach(
$this as $key => $value)
            foreach(
$parts as $p)
                if (
stripos($key, $p) !== false) print '<li>'.$key."</li>\n";
        echo
"</ul>";
    }

just put it in your __get() or __set() like so:

    public function
__get($property)
    {
            echo
"<p><font color='#ff0000'>Attempted to __get() non-existant property/variable '".$property."' in class '".$this->get_class_name()."'.</font><p>\n";
           
$this->suggest_alternative($property);
            exit;
    }
?>

niehztog (14-Feb-2009 06:59)

If you got a parent class agregating(not inheriting) a number of child classes in an array, you can use the following to allow calling methods of the parent object on agregated child objects:
<?php
class child {
    public
$holder = null;

    public function
__call($name, $arguments) {
        if(
$this->holder instanceof parentClass && method_exists($this->holder, $name)) {
            return
call_user_func_array(array($this->holder, $name), $arguments);
        }
        else {
           
trigger_error();
        }
    }
}

class
parentClass {
    private
$children = array();

    function
__construct() {
       
$this->children[0] = new child();
       
$this->children[0]->holder = $this;
    }

    function
getChild($number) {
        if(!isset(
$this->children[$number])) {
            return
false;
        }
        return
$this->children[$number];
    }

    function
test() {
        return
'it works';
    }
}

$parent = new parentClass();
$firstChild = $parent->getChild(0);
echo
$firstChild->test(); //should output 'it works'
?>

jorge dot hebrard at gmail dot com (24-Dec-2008 12:55)

This is a great way to give different permissions to parent classes.

<?php
class A{
    private
$b;
    function
foo(){
       
$this->b = new B;
        echo
$this->b->protvar;
    }
}
class
B extends A{
    protected
$protvar="protected var";
    public function
__get($nm) {
       echo
"Default $nm value";
    }
}
$a = new A;
$b = new B;
$a->foo(); // prints "protected var"
echo $b->protvar; // prints "Default protvar value"
?>
This way, you can help parent classes to have more power with protected members.

Ant P. (21-Dec-2008 04:40)

Be extra careful when using __call():  if you typo a function call somewhere it won't trigger an undefined function error, but get passed to __call() instead, possibly causing all sorts of bizarre side effects.
In versions before 5.3 without __callStatic, static calls to nonexistent functions also fall through to __call!
This caused me hours of confusion, hopefully this comment will save someone else from the same.

erick2711 at gmail dot com (14-Nov-2008 03:33)

<?php
/***********************************************
 *And here follows a child class which implements a menu based in the 'nodoMenu' class (previous note).
 *
 *[]s
 *
 *erick2711 at gmail dot com
 ************************************************/
class menu extends nodoMenu{
    private
$cssc = array();
   
    public function
__toString(){  //Just to show, replace with something better.
       
$stringMenu = "<pre>\n";$stringMenu .= $this->strPrint();$stringMenu .= "</pre>\n";
        return
$stringMenu;
    }

    public function
__construct($cssn = null){
       
parent::__construct();
        if (isset(
$cssn) && is_array($cssn)){$this->cssc = $cssn;}
       
$this->buildMenu();
    }
   
    public function
buildMenu(){
       
$this->add('server',
                  
'Server',
                  
'server.php');
           
$this->server->add('personalD',
                              
'Personal Data',
                              
'server/personal.php');
           
$this->server->add('personalI',
                              
'Personal Interviews',
                              
'server/personalI.php');
               
$this->server->personalI->add('detailsByIer',
                                             
'Detalis by Interviewer',
                                             
'server/personalI.php?tab=detailsByIer');
       
//(...)
       
return $this;
    }
}

//Testing
$meuMenu = new menu;
echo
$meuMenu;
/***********************************************
 *Will output (to the browser):
 *
 *<pre>
 *1 Server<br>
 *??1.1 Personal Data<br>
 *??1.2 Personal Interviews<br>
 *??????1.2.1 Details by Interviewer<br>
 *</pre>
 *
 *Which shows:
 *
 *1 Server
 *  1.1 Personal Data
 *  1.2 Personal Interviews
 *      1.2.1 Details by Interviewer
 ************************************************/
?>

erick2711 at gmail dot com (14-Nov-2008 03:09)

<?php
/*
    Here folows a little improvement of the 'strafvollzugsbeamter at gmx dot de' code, allowing each node to hold both 'parameters' and 'child nodes', and differentiate $s->A->B->C ('FOO') from $s->A (same 'FOO', but shouldn't exist) and from $s-A->B (idem).
    This allows the class, using the interesting suggested syntax ($root->dad->child->attribute, in which 'dad's and 'child's names are dynamically generated), to do something actually useful, like implementing a n-tree data structure (a menu, for instance).
    It was tested under PHP 5.2.6 / Windows.
    I know that must there be something better which already do this (probably in the DOM Model classes, or something like), but it was fun to develop this one, for the sake of studying the "magic" methods.
    Its a compressed version of the code (no comments, too short variable names, almost no identation). I had to compress it in order to add the note. If anyone cares about the full version, just email me.
    []s
   
    erick2711 at gmail dot com
*/
class nodoMenu{

    protected
$p  = array();
    protected
$c = array();

    public function
__construct($t = '', $uri = '', $css = null, $n = 0, $i=0){
       
$this->p['t'] = $t;$this->p['uri'] = $uri;$this->p['css'] = $css;$this->p['n'] = $n;$this->p['i'] = $i;$this->p['q'] = 0;return $this;
    }

    public function
add($cn, $ct = '', $cl = '', $css = null){
       
$nc = new nodoMenu($ct, $cl, $css, $this->p['n'] + 1, $this->p['q']);$this->c[$cn] = $nc;$this->p['q'] += 1;return $this->c[$cn];
    }

    private function
isParameter($pn){
        return
array_key_exists($pn, $this->p);
    }
   
    public function
__isset($pn){
        if (
$this->isParameter($pn)){return(!is_null($this->p[$pn]));}
        else{return(
array_key_exists($pn, $this->c));}
    }

    public function
remove($cn){
        if (
array_key_exists($cn, $this->c)){$this->p['q'] -= 1;unset($this->c[$cn]);}
    }

    public function
__unset($pn){
        if (
$this->isParameter($pn)){$this->p[$pn] = null;}
        else{
$this->remove($pn);}
    }

    public function
__set($pn, $v){
       
$r = null;
        if (
$this->isParameter($pn)){$this->p[$pn] = $v;$r = $v;}
        else{if (
array_key_exists($pn, $this->c)){$this->c[$pn] = $v;$r = $this->c[$pn];}
            else{
$r = $this->add($pn);}}   
        return
$r;
    }       

    public function
__get($pn){
       
$v = null;
        if (
$this->isParameter($pn)){$v = $this->p[$pn];}
        else{if (
array_key_exists($pn, $this->c)){$v = $this->c[$pn];}
            else{
$v = $this->add($pn);}}
        return
$v;
    }
   
    public function
hasChilds(){
        return(isset(
$this->c[0]));
    }
   
    public function
child($i){
        return
$this->c[$i];
    }
   
    public function
strPrint($bm = ''){   //Just to show, replace with something better.
       
$m = '';$r = '';$n = $this->p['n'];
        if (
$n > 0){switch($n){case 0:case 1: $qs = 0; break;case 2: $qs = 2; break;case 3: $qs = 6; break;case 4: $qs = 12; break;case 5: $qs = 20; break;case 6: $qs = 30; break;case 7: $qs = 42; break;case 8: $qs = 56; break;}
           
$tab = str_repeat('&nbsp;', $qs);$r .= $tab;
            if (
$bm <> ''){$m = $bm.'.';}
           
$im = $this->p['i'] + 1;$m .= $im;$r .= $m.' ';$r .= $this->p['t']."<br>\n";
        }
        foreach (
$this->c as $child){$r .= $child->strPrint($m);}
        return
$r;
    }
   
    public function
__toString(){
        return
$this->strPrint();
    }
}
?>

Ant P. (30-Aug-2008 03:01)

There's nothing wrong with calling these functions as normal functions:

If you end up in a situation where you need to know the return value of your __set function, just write <?php $a = $obj->__set($var, $val); ?> instead of <?php $a = $obj->$var = $val; ?>.

strafvollzugsbeamter at gmx dot de (16-Jul-2008 08:57)

The following works on my installation (5.2.6 / Windows):
<?php
class G
{
    private
$_p = array();
   
    public function
__isset($k)
    {
        return isset(
$this->_p[$k]);
    }
       
    public function
__get($k)
    {
       
$v = NULL;
        if (
array_key_exists($k, $this->_p))
        {
           
$v = $this->_p[$k];
        }
        else
        {
           
$v = $this->{$k} = $this;
        }
       
        return
$v;
    }
   
    public function
__set($k, $v)
    {
       
$this->_p[$k] = $v;
       
        return
$this;
    }   
}

$s = new G();
$s->A->B->C = 'FOO';
$s->X->Y->Z = array ('BAR');

if (isset(
$s->A->B->C))
{
    print(
$s->A->B->C);
}
else
{
    print(
'A->B->C is NOT set');
}

if (isset(
$s->X->Y->Z))
{
   
print_r($s->X->Y->Z);
}
else
{
    print(
'X->Y->Z is NOT set');
}

// prints: FOOArray ( [0] => BAR )
?>

... have fun and  ...

Anonymous (30-Apr-2008 09:02)

This is a generic implementation to use getter, setter, issetter and unsetter for your own classes.

<?php
abstract class properties
{
  public function
__get( $property )
  {
    if( !
is_callable( array($this,'get_'.(string)$property) ) )
      throw new
BadPropertyException($this, (string)$property);

    return
call_user_func( array($this,'get_'.(string)$property) );
  }

  public function
__set( $property, $value )
  {
    if( !
is_callable( array($this,'set_'.(string)$property) ) )
      throw new
BadPropertyException($this, (string)$property);

   
call_user_func( array($this,'set_'.(string)$property), $value );
  }
 
  public function
__isset( $property )
  {
    if( !
is_callable( array($this,'isset_'.(string)$property) ) )
      throw new
BadPropertyException($this, (string)$property);

    return
call_user_func( array($this,'isset_'.(string)$property) );
  }

  public function
__unset( $property )
  {
    if( !
is_callable( array($this,'unset_'.(string)$property) ) )
      throw new
BadPropertyException($this, (string)$property);

   
call_user_func( array($this,'unset_'.(string)$property) );
  }
}
?>

nospam michael AT netkey DOT at nospam (01-Apr-2008 06:10)

you CAN write into ARRAYS by using __set and __get magic functions.

as has been mentioned before $obj->var['key'] = 'test'; does call the __get method of $obj, and there is no way to find out, if the method has been called for setting purposes.

the solution is quite simple: use __get to return the array by reference. then you can write into it:

<?php
class setter{
  private
$_arr = array();

  public function
__set($name, $value){
   
$this->_arr[$name] = $value;
  }

  public function &
__get($name){
    if (isset(
$this->_arr[$name])){
      return
$this->_arr[$name];
    } else return
null;
  }

}

?>

Matt Creenan (29-Feb-2008 04:34)

PHP4 supports using __call but with a twist that I did not see mentioned anywhere on this page.

In 4, you must make the __call method signature with 3 parameters, the 3rd of which is the return value and must be declared by-reference.  Instead of using "return $value;" you would assign the 3rd argument to $value.

Example (both implementations below have the same result when run in the respective PHP versions:

<?php

// Will only work in PHP4
class Foo
{
    function
__call($method_name, $parameters, &$return_value)
    {
       
$return_value = "Method $method_name was called with " . count($parameters) . " parameters";
    }
}

// Will only work in PHP5
class Foo
{
    function
__call($method_name, $parameters)
    {
        return
"Method $method_name was called with " . count($parameters) . " parameters";
    }
}

?>

dave at mozaiq dot org (10-Feb-2008 11:58)

Several users have mentioned ways to allow setting of array properties via magic methods. In particular, PHP calls the __get() method instead of the __set() method when you try to do: $obj->prop['offset'] = $val.

The suggestions that I've read below all work, except that they do not allow you make properties read-only. After a bit of struggling, I have found a solution. Essentially, if the property is supposed to be a read-only array, create an new ArrayObject() out of it, then clone it and return the clone.

<?php

public function __get($var) {
    if(isset(
$this->read_only_props[$var])) {
       
$ret = null;
        if (
is_array($this->read_only_props[$var]))
            return clone new
ArrayObject($this->read_only_props[$var]);
        else if (
is_object($this->read_only_props[$var]))
            return clone
$this->read_only_props[$var];
        else
            return
$this->read_only_props[$var];
    }
    else if (!isset(
$this->writeable_props[$var]))
       
$this->writeable_props[$var] = NULL;
    return
$this->writeable_props[$var];
}

public function
__set($var, $val) {
    if (isset(
$this->read_only_props[$var]))
        throw new
Exception('tried to set a read only property on the event object');
    return
$this->writeable_props[$var] = $val;
}
?>

Note that __get() does not explicitly return by reference as many examples have suggested. Also, I have not found a way to detect when __get() is being called for setting purposes, thus my code can not throw an exception when necessary in these cases.

timshaw at mail dot NOSPAMusa dot com (29-Jan-2008 05:47)

The __get overload method will be called on a declared public member of an object if that member has been unset.

<?php
class c {
  public
$p ;
  public function
__get($name) { return "__get of $name" ; }
}

$c = new c ;
echo
$c->p, "\n" ;    // declared public member value is empty
$c->p = 5 ;
echo
$c->p, "\n" ;    // declared public member value is 5
unset($c->p) ;
echo
$c->p, "\n" ;    // after unset, value is "__get of p"
?>

jj dhoT maturana aht gmail dhot com (25-Jan-2008 12:16)

There isn't some way to overload a method when it's called as a reflection method:

<?php

class TestClass {
  function
__call($method, $args) {
    echo
"Method {$method} called with args: " . print_r($args, TRUE);
  }
}

$class = new ReflectionClass("TestClass");
$method = $class->getMethod("myMehtod");

//Fatal error:  Uncaught exception 'ReflectionException' with message 'Method myMethod' does not exist'

?>

Juan.

v dot umang at gmail dot com (12-Jan-2008 05:20)

If you want to be able to overload a variable from within a class and this is your code:
<?php
class myClass
{
    private
$data;
    public function
__set($var, $val)
    {
       
$this->data[$var] = $val;
    }
    public function
__get($var)
    {
       
$this->data[$var] = $val;
    }
}
?>

There is a problem if you want to call these variables from within the class, as you you want to access data['data'] then you can't say $this->data as it will return the array $data. Therefore a simple solution is to name the array $_data. So in your __get and __set you will say $this->_data ... rather than $this->data. I.E:
<?php
class myClass
{
    private
$_data;
    public function
__set($var, $val)
    {
       
$this->_data[$var] = $val;
    }
    public function
__get($var)
    {
       
$this->_data[$var] = $val;
    }
}
?>

Umang

soldair at NOSPAM gmail.com (10-Jan-2008 05:21)

the documentation states a falsehood:
 "All overloading methods must be defined as public."

<?php
class test{
   
#################
    #public use methods#
    #################
   
public static function echoData(){
       
$obj = self::getInstance();
        echo
$obj->find('data');
        return
true;
    }
   
######################
    #only use a single instance#
    ######################
   
private static $instance;
    private static function
getInstance(){
        if(!isset(
self::$instance)){
           
self::$instance = new test;
        }
        return
self::$instance;
    }
   
#################
    #private instantiation#
    #################
   
private $data = array('data'=>'i am data');
    private function
__construct(){}
    private function
__call($nm,$args){
        if(isset(
$this->data[$args[0]])){
            return
$this->data[$args[0]];
        }
        return
null;
    }
}

test::echoData();
?>
--------------OUTPUT---------------
i am data
-----------------------------------

this test was run using PHP Version 5.2.4

Anonymous (09-Jan-2008 03:45)

it should be noted that __call will trigger only for method calls on an instantiated object, and cannot be used to 'overload' static methods.  for example:

<?php

class TestClass {
  function
__call($method, $args) {
    echo
"Method {$method} called with args: " . print_r($args, TRUE);
  }
}

// this will succeed
$obj = new TestClass();
$obj->method_doesnt_exist();

// this will not
TestClass::method_doesnt_exist();

?>

It would be useful if the PHP devs would include this in a future release, but in the meantime, just be aware of that pitfall.

egingell at sisna dot com (21-Dec-2007 03:54)

The PHP devs aren't going to implement true overloading because: PHP is not strictly typed by any stretch of the imagination (0, "0", null, false, and "" are the same, for example) and unlike Java and C++, you can pass as many values as you want to a function. The extras are ignored unless you fetch them using func_get_arg(int) or func_get_args(), which is often how I "overload" a function/method, and fewer than the declared number of arguments will generate an E_WARNING, which can be suppressed by putting '@' before the function call, but the function will still run as if you had passed null where a value was expected.

<?php
class someClass {
    function
whatever() {
       
$args = func_get_args();
   
       
// public boolean whatever(boolean arg1) in Java
       
if (is_bool($args[0])) {
           
// whatever(true);
           
return $args[0];
   
       
// public int whatever(int arg1, boolean arg2) in Java
       
} elseif(is_int($args[0]) && is_bool($args[1])) {
           
// whatever(1, false)
           
return $args[0];
   
        } else {
           
// public void whatever() in Java
           
echo 'Usage: whatever([int], boolean)';
        }
    }
}
?>

// The Java version:
public class someClass {
    public boolean whatever(boolean arg1) {
        return arg1;
    }
   
    public int whatever(int arg1, boolean arg2) {
        return arg1;
    }
   
    public void whatever() {
        System.out.println("Usage: whatever([int], boolean)");
    }
}

matthijs at yourmediafactory dot com (16-Dec-2007 07:09)

While PHP does not support true overloading natively, I have to disagree with those that state this can't be achieved trough __call.

Yes, it's not pretty but it is definately possible to overload a member based on the type of its argument. An example:
<?php
class A {
  
  public function
__call ($member, $arguments) {
    if(
is_object($arguments[0]))
     
$member = $member . 'Object';
    if(
is_array($arguments[0]))
     
$member = $member . 'Array';
   
$this -> $member($arguments);
  }
  
  private function
testArray () {
    echo
"Array.";
  }
  
  private function
testObject () {
    echo
"Object.";
  }
}

class
B {
}

$class = new A;
$class -> test(array()); // echo's 'Array.'
$class -> test(new B); // echo's 'Object.'
?>

Of course, the use of this is questionable (I have never needed it myself, but then again, I only have a very minimalistic C++ & JAVA background). However, using this general principle and optionally building forth on other suggestions a 'form' of overloading is definately possible, provided you have some strict naming conventions in your functions.

It would of course become a LOT easier once PHP'd let you declare the same member several times but with different arguments, since if you combine that with the reflection class 'real' overloading comes into the grasp of a good OO programmer. Lets keep our fingers crossed!

anthony dot parsons at manx dot net (09-Nov-2007 10:57)

You can't use __set to set arrays, but if you really want to, you can emulate it yourself:
<?php
class test {
    public
$x = array();
    public
$y = array();
    function
__set($var, $value)
    {
        if (
preg_match('/(.*)\[(.*)\]/', $var, $names) ) {
           
$this->y[$names[1]][$names[2]] = $value;
        }
        else {
           
$this->x[$var] = $value;
        }
    }
}

$z = new test;
$z->variable = 'abc';
$z->{'somearray[key]'} = 'def';

var_dump($z->x);
var_dump($z->y);
?>

php_is_painful at world dot real (19-Oct-2007 03:49)

This is a misuse of the term overloading. This article should call this technique "interpreter hooks".

bgoldschmidt at rapidsoft dot de (28-Sep-2007 09:23)

"These methods will only be triggered when your object or inherited object doesn't contain the member or method you're trying to access."
is not quite correct:
they get called when the member you trying to access in not visible:

<?php
class test {

  public
$a;
  private
$b;

  function
__set($name, $value) {
    echo(
"__set called to set $name to $value\n");
   
$this->$name = $value;
  }
}

$t = new test;
$t->a = 'a';
$t->b = 'b';

?>

Outputs:
__set called to set b to b

Be aware that set ist not called for public properties

lokrain at gmail dot com (26-Sep-2007 10:05)

Let us look at the following example:

<?php
class objDriver {
    private
$value;

    public function
__construct()
    {
       
$value = 1;
    }

    public function
doSomething($parameterList)
    {
       
//We make actions with the value
   
}
}

class
WantStaticCall {
    private static
$objectList;

    private function
__construct()

    public static function
init()
    {
        
self::$objectList = array();
    }

    public static function
register($alias)
    {
       
self::$objectList[$alias] = new objDriver();
    }

    public static function
__call($method, $arguments)
    {
       
$alias = $arguments[0];
   
array_shift($arguments);
       
call_user_method($method, self::$objectList[$alias], $arguments);
    }
}

// The deal here is to use following code:
WantStaticCall::register('logger');
WantStaticCall::doSomething('logger', $argumentList);

// and we will make objDriver to call his doSomething function with arguments
// $argumentList. This is not common pattern but very usefull in some cases.
// The problem here is that __call() cannot be static, Is there a way to work it around
?>

Typer85 at gmail dot com (18-Sep-2007 04:28)

Just to clarify something the manual states about method overloading.

"All overloading methods must be defined as public."

As of PHP 5.2.2, this should be considered more of a coding convention rather than a requirement. In PHP 5.2.2, declaring a __get or __set function with a visibility other than public, will be silently ignored by the parser and will not trigger a parse error!

What is more, PHP will completely ignore the visibility modifier either of these functions are declared with and will always treat them as if they were public.

I am not sure if this is a bug or not so to be on the safe side,
stick with always declaring them public.

egingell at sisna dot com (15-Sep-2007 01:12)

Small vocabulary note: This is *not* "overloading", this is "overriding".

Overloading: Declaring a function multiple times with a different set of parameters like this:
<?php

function foo($a) {
    return
$a;
}

function
foo($a, $b) {
    return
$a + $b;
}

echo
foo(5); // Prints "5"
echo foo(5, 2); // Prints "7"

?>

Overriding: Replacing the parent class's method(s) with a new method by redeclaring it like this:
<?php

class foo {
    function new(
$args) {
       
// Do something.
   
}
}

class
bar extends foo {
    function new(
$args) {
       
// Do something different.
   
}
}

?>

ac221 at sussex dot ac dot uk (01-Sep-2007 06:07)

Just Noting the interesting behavior of __set __get , when modifying objects contained in overloaded properties.

<?php
class foo {
    public
$propObj;
    public function
__construct(){
       
$propObj = new stdClass();
    }
    public function
__get($prop){
        echo(
"I'm Being Got ! \n");
        return
$this->propObj->$prop;
    }
    public function
__set($prop,$val){
        echo(
"I'm Being Set ! \n");
       
$this->propObj->$prop = $val;
    }
}
$test = new foo();
$test->barProp = new stdClass(); // I should invoke set
$test->barProp->barSubProp = 'As Should I';
$test->barProp->barSubProp = 'As Should I';
$test->barProp = new stdClass(); // As should i
?>

Outputs:

I'm Being Set !
I'm Being Got !
I'm Being Got !
I'm Being Set !

Whats happening, is PHP is acquiring a reference to the object, triggering __get; Then applying the changes to the object via the reference.

Which is the correct behaviour; objects being special creatures, with an aversion to being cloned...

Unfortunately this will never invoke the __set handler, even though it is modifying a property within 'foo', which is slightly annoying if you wanted to keep track of changes to an objects overloaded properties.

I guess Journaled Objects will have to wait till PHP 6 :)

stephen dot cuppett at webmastersinc dot net (16-Aug-2007 03:10)

Please note, this example will not work on later PHP versions.  You must return from __get() by reference using &__get()

php at sleep is the enemy dot co dot uk (23-Jul-2007 03: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 09: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 06: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 06: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:

<?php
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.

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
*/
?>

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.

<?php
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 10: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 05:12)

<?php $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:
 
<?php
$tmp_array
= &$myclass->foo;
$tmp_array['bar'] = 'baz';
unset(
$tmp_array);
?>

Therefore, the above won't do anything if your __get implementation resembles this:

<?php
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:

<?php
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 08: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 06: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:

<?php
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)

<?php
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'
?>

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.

<?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();

?>

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.

(26-Aug-2005 03: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;
            }
           
        }
       
    }

?>

NOTE: getter cannot call getter (04-Aug-2005 08: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:

<?php
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 11:50)

Please note that PHP5's overloading behaviour is not compatible at all with PHP4's overloading behaviour.

Marius (02-May-2005 10: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;

$objWE3 = new A_WebFormElement('Element 3');
$objWE3->objOwner = $objWFrm2;

$objWE4 = new A_WebFormElement('Element 4');
$objWE4->objOwner = $objWFrm2;

echo
"The name of the form that '" . $objWE1->GetWebFormElementName() . "' is in is '" . $objWE1->GetWebFormName() . "'<br />";
echo
"The name of the document that '" . $objWE2->GetWebFormElementName() . "' is in is '" . $objWE2->GetWebDocumentName(). "'<br />";
echo
"The name of the frame that '" . $objWE3->GetWebFormElementName() . "' is in is '" . $objWE3->GetWebFrameName(). "'<br />";
echo
"The name of the page container that '" . $objWE4->GetWebFormElementName() . "' is in is '" .$objWE4->GetWebPageContainerName(). "'<br />";
?>

Results in.

The name of the form that 'Element 1' is in is 'Form 1'
The name of the document that 'Element 2' is in is 'Doc 1'
The name of the frame that 'Element 3' is in is 'Frame 2'
The name of the page container that 'Element 4' is in is 'The outer web page container.'

By using the abstract BubbleMethod class as the starting point for further classes that are contained inside others (i.e. elements on a form are contained in forms, which are contained in documents which are contained in frames which are contained in a super wonder global container), you can find properties of owner without knowing their direct name.

Some work needs to be done on what to do if no method exists though.

xorith at gmail dot com (06-Oct-2004 03:40)

A few things I've found about __get()...

First off, if you use $obj->getOne->getAnother, both intended to be resolved by __get, the __get() function only sees the first one at first. You can't access the second one. You can, however, return the pointer to an object that can handle the second one. In short, you can have the same class handle both by returning a new object with the data changed however you see fit.

Secondly, when using arrays like: $obj->getArray["one"], only the array name is passed on to __get. However, when you return the array, PHP treats it just as it should. THat is, you'd have to make an array with the index of "one" in __get in order to see any results. You can also have other indexes in there as well.

Also, for those of you like me, I've already tried to use func_get_args to see if you can get more than just that one.

If you're like me and were hoping you could pass some sort of argument onto __get in order to help gather the correct data, you're out of look. I do recommend using __call though. You could easily rig __call up to react to certain things, like: $account->properties( "type" );, which is my example. I'm using DOM for data storage (for now), and I'm trying to make an interface that'll let me easily switch to something else - MySQL, flat file, anything. This would work great though!

Hope I've been helpful and I hope I didn't restate something already stated.

anthony at ectrolinux dot com (26-Sep-2004 02:40)

For anyone who is interested in overloading a class method based on the number of arguments, here is a simplified example of how it can be accomplished:

<?php

function Error($message) { trigger_error($message, E_USER_ERROR); exit(1); }

class
Framework
{
   
// Pseudo function overloading
   
public function __call($func_name, $argv)
    {
       
$argc = sizeof($argv);

        switch (
$func_name) {
        case
'is_available':
           
$func_name = ($argc == 2) ? 'is_available_single' : 'is_available_multi';
            break;

        default:
// If no applicable function was found, generate the default PHP error message.
           
Error('Call to undefined function Framework::'. $func_name .'().');
        }

        return
call_user_func_array(array(&$this, $func_name), $argv);
    }

   
// protected array is_available_multi($mod_name, $mod_addon_0 [, $mod_addon_1 [, ...]])
   
protected function is_available_multi()
    {
        if ((
$argc = func_num_args()) < 2) {
           
Error('A minimum of two arguments are required for Framework::is_available().');
        }

       
$available_addons = array();
       
// --snip--

       
return $available_addons;
    }

    protected function
is_available_single($mod_name, $mod_addon)
    {
       
// --snip--

       
return true;
    }
}

$fw = new Framework;

$test_one = $fw->is_available('A_Module_Name', 'An_Addon');
var_dump($test_one);
// Test one produces a boolean value, supposedly representing whether 'An_Addon' is available and can be used.

$test_two = $fw->is_available('A_Module_Name', 'Addon_0', 'Addon_1', 'Addon_2');
var_dump($test_two);
// Test two produces an array, supposedly listing any of the three 'Addon_N' modules that are available and can be used.

// Here are the actual results of the above:
//    bool(true)
//    array(0) {
//    }

?>

---

By adding additional case statements to Framework::__call(), methods can easily be overloaded as needed. It's also possible to add any other overloading criteria you require inside the switch statement, allowing for more intricate overloading functionality.

DevilDude at darkmaker dot com (23-Sep-2004 03:57)

Php 5 has a simple recursion system that stops you from using overloading within an overloading function, this means you cannot get an overloaded variable within the __get method, or within any functions/methods called by the _get method, you can however call __get manualy within itself to do the same thing.