This is a misuse of the term overloading. This article should call this technique "interpreter hooks".
Overloading in PHP provides means to dynamically "create" properties and methods. These dynamic entities are processed via magic methods one can establish in a class for various action types.
The overloading methods are invoked when interacting with properties or methods that have not been declared or are not visible in the current scope. The rest of this section will use the terms "inaccessible properties" and "inaccessible methods" to refer to this combination of declaration and visibility.
All overloading methods must be defined as public.
Note:
None of the arguments of these magic methods can be passed by reference.
Note:
PHP's interpretation of "overloading" is different than most object oriented languages. Overloading traditionally provides the ability to have multiple methods with the same name but different quantities and types of arguments.
Version | Description |
---|---|
5.3.0 | Added __callStatic(). Added warning to enforce public visibility and non-static declaration. |
5.1.0 | Added __isset() and __unset(). Added support for __get() for overloading of private properties. |
5.0.0 | Added __get(). |
$name
)$name
)__set() is run when writing data to inaccessible properties.
__get() is utilized for reading data from inaccessible properties.
__isset() is triggered by calling isset() or empty() on inaccessible properties.
__unset() is invoked when unset() is used on inaccessible properties.
The $name argument is the name of the property being interacted with. The __set() method's $value argument specifies the value the $name'ed property should be set to.
Property overloading only works in object context. These magic methods will not be triggered in static context. Therefore these methods should not be declared static. As of PHP 5.3.0, a warning is issued if one of the magic overloading methods is declared static.
Note:
The return value of __set() is ignored because of the way PHP processes the assignment operator. Similarly, __get() is never called when chaining assignments together like this:
$a = $obj->b = 8;
Example #1 Overloading properties via the __get(), __set(), __isset() and __unset() methods
<?php
class PropertyTest
{
/** Location for overloaded data. */
private $data = array();
/** Overloading not used on declared properties. */
public $declared = 1;
/** Overloading only used on this when accessed outside the class. */
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;
}
/** As of PHP 5.1.0 */
public function __isset($name)
{
echo "Is '$name' set?\n";
return isset($this->data[$name]);
}
/** As of PHP 5.1.0 */
public function __unset($name)
{
echo "Unsetting '$name'\n";
unset($this->data[$name]);
}
/** Not a magic method, just here for example. */
public function getHidden()
{
return $this->hidden;
}
}
echo "<pre>\n";
$obj = new PropertyTest;
$obj->a = 1;
echo $obj->a . "\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";
?>
The above example will output:
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
__call() is triggered when invoking inaccessible methods in an object context.
__callStatic() is triggered when invoking inaccessible methods in a static context.
The $name argument is the name of the method being called. The $arguments argument is an enumerated array containing the parameters passed to the $name'ed method.
Example #2 Overloading methods via the __call() and __callStatic() methods
<?php
class MethodTest
{
public function __call($name, $arguments)
{
// Note: value of $name is case sensitive.
echo "Calling object method '$name' "
. implode(', ', $arguments). "\n";
}
/** As of PHP 5.3.0 */
public static function __callStatic($name, $arguments)
{
// Note: value of $name is case sensitive.
echo "Calling static method '$name' "
. implode(', ', $arguments). "\n";
}
}
$obj = new MethodTest;
$obj->runTest('in object context');
MethodTest::runTest('in static context'); // As of PHP 5.3.0
?>
The above example will output:
Calling object method 'runTest' in object context Calling static method 'runTest' in static context
This is a misuse of the term overloading. This article should call this technique "interpreter hooks".
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.
Using magic methods, especially __get(), __set(), and __call() will effectively disable autocomplete in most IDEs (eg.: IntelliSense) for the affected classes.
To overcome this inconvenience, use phpDoc to let the IDE know about these magic methods and properties: @method, @property, @property-read, @property-write.
/**
* @property-read name
* @property-read price
*/
class MyClass
{
private $properties = array('name' => 'IceFruit', 'price' => 2.49)
public function __get($name)
{
return $this->properties($name);
}
}
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.
}
}
?>
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 :)
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) );
}
}
?>
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
?>
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]);
}
}
?>
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"
?>
Please note that PHP5's overloading behaviour is not compatible at all with PHP4's overloading behaviour.
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.
If you need to also use the magic setter in your constructor, you may use this example:
<?php
class person {
private $name;
private $addr;
private $city;
function __construct($n,$a,$c) {
$this->__set('name', $n); // Pushing "name" through the setter where it will be validated and sanitated.
$this->addr = $a; // Adress is not validated
$this->city = $c; // Neither is this...
// $this->__set('city',$c); // But this is...
}
public function __get($property) {if (property_exists($this, $property)) {return $this->$property;}}
public function __set($property, $value) {
if (property_exists($this, $property)) {
echo "<pre>Setting $value</pre>";
// ... Clever code for sanitation...
if ($value == "Evil input from user"){
echo "<pre>Oh no! Bad user! Go sit in the corner!</pre>";
$this->$property = null;
} else {
$this->$property = $value;
}
}
return $this;
}
}
$cathy = new person('Cathy','9 Dark and Twisty','Evil input from user');
$cathy->name = "Kitty Kat";
echo "<pre>" . print_r($cathy,1) . "</pre>";
echo "<pre>" . $cathy->city . "</pre>";
?>
This code will output:
Setting Kitty Kat
person Object
(
[name:person:private] => Kitty Kat
[addr:person:private] => 9 Dark and Twisty
[city:person:private] => Evil input from user
)
Evil input from user
So it is important to push variables from the user through the setters even in the constructor.
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
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);
?>
<?php
//How can implement __call function you understand better
class Employee {
protected $_name;
protected $_email;
protected $_compony;
public function __call($name, $arguments) {
$action = substr($name, 0, 3);
switch ($action) {
case 'get':
$property = '_' . strtolower(substr($name, 3));
if(property_exists($this,$property)){
return $this->{$property};
}else{
$trace = debug_backtrace();
trigger_error('Undefined property ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE);
return null;
}
break;
case 'set':
$property = '_' . strtolower(substr($name, 3));
if(property_exists($this,$property)){
$this->{$property} = $arguments[0];
}else{
$trace = debug_backtrace();
trigger_error('Undefined property ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE);
return null;
}
break;
default :
return FALSE;
}
}
}
$s = new Employee();
$s->setName('Nanhe Kumar');
$s->setEmail('nanhe.kumar@gmail.com');
echo $s->getName(); //Nanhe Kumar
echo $s->getEmail(); // nanhe.kumar@gmail.com
$s->setAge(10); //Notice: Undefined property setAge in
?>
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.
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
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";
?>
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;
}
?>
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.
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.
<?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];
}
?>
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().
?>
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.
<?php
//How can implement __call function
class Student {
protected $_name;
protected $_email;
public function __call($name, $arguments) {
$action = substr($name, 0, 3);
switch ($action) {
case 'get':
$property = '_' . strtolower(substr($name, 3));
if(property_exists($this,$property)){
return $this->{$property};
}else{
echo "Undefined Property";
}
break;
case 'set':
$property = '_' . strtolower(substr($name, 3));
if(property_exists($this,$property)){
$this->{$property} = $arguments[0];
}else{
echo "Undefined Property";
}
break;
default :
return FALSE;
}
}
}
$s = new Student();
$s->setNam('Nanhe Kumar');
$s->setEmail('nanhe.kumar@gmail.com');
echo $s->getName(); //Nanhe Kumar
echo $s->getEmail(); // nanhe.kumar@gmail.com
$s->setAge(10); //Undefined Property
?>
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.
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!
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"
*/
?>
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.
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();
?>
<?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
************************************************/
?>
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)");
}
}
I test those code:
<?php
class A {
public function test () {
static::who();
A::who();
self::who();
$this->who();
}
public static function __callStatic($a, $b) {
var_dump('A static');
}
public function __call($a, $b) {
var_dump('A call');
}
}
$a = new A;
$a->test();
?>
And the answer is
string(6) "A call"
string(6) "A call"
string(6) "A call"
string(6) "A call"
I think it means that __call will be called before __callStatic in an instance.
As stated in another note, this is a gross misuse of the term "overload" as seen in traditional OOP. It's really bad to see an explanation that "PHP interprets this word differently". The use of __get(), __set(), __call() and friends is actually a technique known elsewhere as "metaprogramming", not overloading.
Note that you can enable "overloading" on a class instance at runtime for an existing property by unset()ing that property.
eg:
<?php
class Test {
public $property1;
public function __get($name)
{
return "Get called for " . get_class($this) . "->\$$name \n";
}
}
?>
The public property $property1 can be unset() so that it can be dynamically handled via __get().
<?php
$Test = new Test();
unset($Test->property1); // enable overloading
echo $Test->property1; // Get called for Test->$property1
?>
Useful if you want to proxy or lazy load properties yet want to have documentation and visibility in the code and debugging compared to __get(), __isset(), __set() on non-existent inaccessible properties.
Actually you dont need __set ect imo.
You could use it to set (pre-defined) protected (and in "some" cases private) properties . But who wants that?
(test it by uncommenting private or protected)
(pastebin because long ...) => http://pastebin.com/By4gHrt5
Use of magic functions may make your private variables behave like public ones.
But it becomes strict; you cannot (in fact should not) assign any properties dynamically during the runtime.
<?php
class order
{
private $OrderID='';
private $OrderAmount=0.00;
public function __set($name='', $value='')
{
if(property_exists($this, $name))
{
$this->$name = $value;
}
}
public function __get($name='')
{
$value = null;
if(property_exists($this, $name))
{
$value = $this->$name;
}
return $value;
}
}
$order = new order();
$order->OrderID = '201305062202';
$order->OrderAmount = 23.45;
$order->InvalidMember = 'Missed Assignment';
echo '<pre>', print_r($order, true), '</pre>';
?>
Outputs:
order Object
(
[OrderID:order:private] => 201305062202
[OrderAmount:order:private] => 23.45
)
Overloading other classical typed languages can be emulated in a simple way:
<?php
class overload
{
protected function overloaded($method, $params, $type = false, $instance = false)
{
$method .= count($params);
if($type && $params)
{
foreach($params as $param)
$method .= '_'.(gettype($param) == 'object' ? ($instance ? get_class($param) : 'object') : gettype($param));
}
if(method_exists($this, $method))
call_user_func_array(array($this,$method),$params);
}
}
class test_overload extends overload
{
public function test_function() { $this->overloaded('test', func_get_args(), true); }
protected function test0()
{
echo('<br>Hello, I did not pass any parameters.');
}
protected function test1_string($string)
{
echo('<br>Hello, I spent this string: '.$string);
}
protected function test1_integer($integer)
{
echo('<br>Hello, I spent this integer: '.$integer);
}
protected function test2_integer_string($integer, $string)
{
echo('<br>Hello, I spent this integer: '.$integer.' and this string: '.$string);
}
protected function test2_string_integer($string, $integer)
{
echo('<br>Hello, I spent this string: '.$integer.' and this integer: '.$string);
}
}
$test = new test_overload();
$test->test_function();
$test->test_function('test');
$test->test_function(5);
$test->test_function(5, 'test');
$test->test_function('test', 5);
$test->test_function('2');
You can use __call and __callStatic for an very simple function based caching approach. If your method result needs to be cached based on its arguments then you only have to add cached_ infront of the name.
class thebase
{
private static $cache = array();
private static function argsAndCache($name, $args)
{
$hasArray = false;
$argsX = $cache = '';
if (count($args) > 0)
{
foreach ($args as $v) if ($hasArray = (is_array($v) || is_object($v))) break;
$argsX = '$args[' . implode('], $args[', array_keys($args)) . ']';
$cache = ($hasArray)
? md5(json_encode($args))
: '[$args[' . implode(']], [$args[', array_keys($args)) . ']]';
}
$cache = 'self::$cache[\'' . $name . '\']' . $cache;
return array($argsX, $cache);
}
public function __call($name, $args)
{
list($argsX, $cache) = self::argsAndCache($name, $args);
eval('$result = isset(' . $cache . ')
? ' . $cache . '
: $this->cached_' . $name . '(' . $argsX . ');');
return $result;
}
public static function __callStatic($name, $args)
{
list($argsX, $cache) = self::argsAndCache($name, $args);
eval('$result = isset(' . $cache . ')
? ' . $cache . '
: ' . get_called_class() . '::cached_' . $name . '(' . $argsX . ');');
return $result;
}
}
class theChild extends thebase
{
public function cached_abc()
{
return 1;
}
public static function cached_s_abc()
{
return 1;
}
public static function normal()
{
return 1;
}
}
$x = new theChild();
echo $x->abc();
echo theChild::s_abc();
echo theChild::normal();
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
?>
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;
}
}
?>
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
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;
?>
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;
}
}
?>
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'
?>
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.
<?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(' ', $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();
}
}
?>
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"
?>
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;
}
}
?>
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.
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
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 :)
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
}
?>
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'
?>
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;
}
}
}
?>
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.
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.
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.
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
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;
}
you can call the method _call passing parameters by reference:
<?php
class MagicCallReference()
{
public function __call($name, $params)
{
var_dump($params);
$params[0][0] = '222';
}
}
$magicReference = new MagicCallReference();
$test = 3;
var_dump($test);
$magicReference->setReference(array(&$info));
var_dump($test);
?>
Output:
int 3
array (size=1)
0 =>
array (size=1)
0 => &int 3
string '222' (length=3)
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 ...
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.
<?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();
?>