|
OverloadingBoth method calls and member accesses can be overloaded via the __call, __get and __set methods. These methods will only be triggered when your object or inherited object doesn't contain the member or method you're trying to access. All overloading methods must not be defined as static. All overloading methods must be defined as public. Since PHP 5.1.0 it is also possible to overload the isset() and unset() functions via the __isset and __unset methods respectively. Method __isset is called also with empty().
Class members can be overloaded to run custom code defined in your class
by defining these specially named methods. The Example 10.20. overloading with __get, __set, __isset and __unset example<?php The above example will output: Setting [a] to 100
The magic method __call() allows to capture invocation of non existing
methods. That way __call() can be used to implement user defined method
handling that depends on the name of the actual method being called. This
is for instance useful for proxy implementations. The arguments that were
passed in the function will be defined as an array in the
Example 10.21. overloading with __call example<?php The above example will output: Method test called: Code Examples / Notes » language.oop5.overloadingthisisroot
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. dot_whut?
You can create automatic getter and setter methods for your private and protected variables by creating an AutoGetterSetter Abstract Class and having your Class inherit from it: // Automatic Getter and Setter Class // // Note: when implementing your getter and setter methods, name your // methods the same as your desired object properties, but prefix the // function names with 'get_' and 'set_' respectively. abstract class AutoGetAndSet { public function __get ($name) { try { // Check for getter method (throws an exception if none exists) if (!method_exists($this, "get_$name")) throw new Exception("$name has no Getter method."); eval("\$temp = \$this->get_$name();"); return $temp; } catch (Exception $e) { throw $e; } } public function __set ($name, $value) { try { // Check for setter method (throws an exception if none exists) if (!method_exists($this, "set_$name")) throw new Exception("$name has no Setter method (is read-only)."); eval("\$this->set_$name(\$value);"); } catch (Exception $e) { throw $e; } } } class MyClass extends AutoGetAndSet { // private and protected members private $mbr_privateVar = "privateVar"; protected $mbr_protectedVar = "protectedVar"; // 'privateVar' property, setter only (read-only property) function get_privateVar () { return $this->mbr_privateVar; } // 'protectedVar' property, both getter AND setter function get_protectedVar () { return $this->mbr_protectedVar; } function set_protectedVar ($value) { $this->mbr_protectedVar = $value; } } $myClass = new MyClass(); // Show original values, automatically uses 'get_' methods echo '$myClass->protectedVar == ' . $myClass->protectedVar . '<br />'; echo '$myClass->privateVar == ' . $myClass->privateVar . '<br />'; // Modify 'protectedVar', automatically calls set_protectedVar() $myClass->protectedVar = "Modded"; // Show modified value, automatically calls get_protectedVar() echo '$myClass->protectedVar == ' . $myClass->protectedVar . '<br />'; // Attempt to modify 'privateVar', get_privateVar() doesn't exist $myClass->privateVar = "Modded"; // throws an Exception // Code never gets here echo '$myClass->privateVar == ' . $myClass->privateVar . '<br />'; sleepless
Yet another way of providing support for read-only properties. Any property that has "pri_" as a prefix will NOT be returned, period, any other property will be returned and if it was declared to be "protected" or "private" it will be read-only. (scope dependent of course) function __get($var){ if (property_exists($this,$var) && (strpos($var,"pri_") !== 0) ) return $this->{$var}; else //Do something } benbe
While playing a bit with the __call magic method I found you can not emulate implementing methods of an interface as you might think: <? class Iteratable implements Iterator { public function __call($funcname) { if(in_array($funcname, array('current', 'next', /*...*/)) { //Redirect the call or perform the actual action } } } ?> Using this code you'll get a "class Iteratable contains abstract methods ..." fatal error message. You'll ALWAYS have to implement those routines by hand. tj
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. 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; } } } ?> iv
To "NOTE: getter cannot call getter" When i changed 'using_getter' => 10 * $this->a to 'using_getter' => 10 * $this->_a the result became 60, how it should be mnaul
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; } php_is_painful
This is a misuse of the term overloading. This article should call this technique "interpreter hooks".
seufert
This allows you to seeminly dynamically overload objects using plugins. <PRE> <?php class standardModule{} class standardPlugModule extends standardModule { static $plugptrs; public $var; static function plugAdd($name, $mode, $ptr) { standardPlugModule::$plugptrs[$name] = $ptr; } function __call($fname, $fargs) { print "You called __call($fname)\n"; $func = standardPlugModule::$plugptrs[$fname]; $r = call_user_func_array($func, array_merge(array($this),$fargs)); print "Done: __call($fname)\n"; return $r; } function dumpplugptrs() {var_dump(standardPlugModule::$plugptrs); } } class a extends standardPlugModule { function text() { return "Text"; } } //Class P contained within a seperate file thats included class p { static function plugin1($mthis, $r) { print "You called p::plugin1\n"; print_r($mthis); print_r($r); } } a::plugAdd('callme', 0, array('p','plugin1')); //Class P contained within a seperate file thats included class p2 { static function plugin2($mthis, $r) { print "You called p2::plugin2\n"; $mthis->callme($r); } } a::plugAdd('callme2', 0, array('p2','plugin2')); $t = new a(); $testr = array(1,4,9,16); print $t->text()."\n"; $t->callme2($testr); //$t->dumpplugptrs(); ?> </pre> Will result in: ---------- Text You called __call(callme2) You called p2::plugin2 You called __call(callme) You called p::plugin1 a Object ( [var] => ) Array ( [0] => 1 [1] => 4 [2] => 9 [3] => 16 ) Done: __call(callme) Done: __call(callme2) ---------- This also clears up a fact that you can nest __call() functions, you could use this to get around the limits to __get() not being able to be called recursively. jbailey
The __set method doesn't seem to respect visibility at all (and the examples suggest this). I'd hoped that marking the __set method as "private" would cause an exception to get thrown. Instead the __set method needs to throw an Exception itself. Being able to lock other things away from putting things randomly into the object would be handy for helping find typos right at the source. egingell
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. } } ?> divedeep
Small examle showing how to work with magic method __call and parameters, that u want to pass as a reference. DOES NOT WORK: <?php class a { public function __call($m,&$p) { $p[0]=2; } } $a= new a(); $b=1; $a->foo($b); echo $b; // RETURNS 1 ?> WORKS: <?php class a { public function __call($m,$p) { $p[0]=2; } } $a= new a(); $b=1; $a->foo(&$b); // RETURNS 2 echo $b; ?> magicaltux
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 dot_whut?
Sharp readers (i.e. C++, Java programmers, etc.) may have noticed that the topic of "Overloading" mentioned in the documentation above really isn't about overloading at all... Or, at least not in the Object Oriented Programming (OOP) sense of the word. For those who don't already know, "overloading" a method (a.k.a. function) is the ability for functions of the same name to be defined within a Class as long as each of these methods have a different set of parameters (each method would have a different "signature", if you will). At run-time, the script engine could then select and execute the appropriate function depending on which type or number of arguments were passed to it. Unfortunately, PHP 5.0.2 doesn't support this sort of overloading. The "Overloading" mentioned above could be better described as "Dynamic Methods and Properties"... By using "Dynamic Methods and Properties", we can make our class instances "appear" to have methods and properties that were not originally present in our Class definitions. Although this is a great new feature (which I plan to use often), through the use of "__get", "__set", and "__call", we can also emulate "real" overloading as shown in a few of the code examples below. me
Regarding the post by TJ earlier, about the problems extending DOMElement. Yes, it is true that you can't set extDOMElement::ownerDocument directly; however, you could append the extDOMElement to a DOMDocument in __construct(), which indirectly sets ownerDocument. It should work something like so: class extDOMElement extends DOMElement { public function __construct(DOMDocument $doc) { $doc->appendChild($this); //extDOMElement::ownerDocument is now equal to the object that $doc points to } } Now, I admit I've never actually needed to do this, but I see no reason why it shouldn't work. d11wtq
Regarding the code posted by this user: mileskeaton at gmail dot com 23-Dec-2004 03:23 There's no need to loop over your entire object. I do this lots and all you need to do is convert the method name used into the name of a property, then check if (isset($this->{$property_name})) { //Get ready to do a set } I usually check if the developer called a get or set and also do some snity checking on the type of property they're requesting. trash80
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' ?> stephen dot cuppett
Please note, this example will not work on later PHP versions. You must return from __get() by reference using &__get()
06-may-2005 11:50
Please note that PHP5's overloading behaviour is not compatible at all with PHP4's overloading behaviour.
derek-php
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"; ?> alexandre
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 :) devildude
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.
adeel khan
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. lokrain
Let us look at the following example: 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 ryo
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(). ?> bigtree
Keep in mind that the __call method will not be called if the method you are calling actually exists, even if it exists in a parent class. Check the following example: <?php class BaseClass { function realMethod() { echo "realMethod has been called\n"; } } class ExtendClass extends BaseClass { function __call($m, $a) { echo "virtual method " . $m . " has been called\n"; } } $tmpObject = new ExtendClass(); $tmpObject->virtualMethod(); $tmpObject->realMethod(); ?> Will output: virtual method virtualMethod has been called realMethod has been called You might expect that all method calls on ExtendClass will be handled by __call because it has no other methods, but this is not the case, as the example above demonstrates. php
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 . ' '; echo '$tc ' . (property_exists($tc, 'foo') ? 'now has' : 'still does not have') . ' a property called "foo" '; /* OUPUTS: $tc->foo == bar $tc now has a property called "foo" */ ?> typer85
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. ac221
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 :) j dot stubbs
In reply to james at thunder-removeme-monkey dot net, Unfortunately it seems that there is no way to have completely transparent array properties with 5.2.x. The code you supplied works until working with built-in functions that perform type-checks: <? // Using the same View class $view = new View(); $view->bar = array(); $view->bar[] = "value"; if (is_array($view->bar)) print "is array!\n"; // Not printed // Warning: in_array(): Wrong datatype for second argument in ... if (in_array("value", $view->bar)) print "found!\n"; // Not printed // Successful if (in_array("value", (array)$view->bar)) print "found!\n"; ?> It also seems that 5.1.x is no longer maintained as it's not listed on the downloads page.. Quite frustrating. :/ eric lafkoff
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. ?> yuggoth23
I just wanted to share with the rest of you since I had not seen it posted yet that __call does not work as a static method. Consider a utility class that has all static methods on it where one or more of those methods are overloaded. The snippet below will not work until you remove the "static" keyword from the __call function declaration: class MyClass { public static _call($method, $args) { switch($method) { case "Load": if(count($args) == 1) $this->funcLoad1($args[0]); elseif(count($args) == 2) $this->funcLoad2($args[0],$args[1]); } } private static funcLoad1($userID) { //do something } private static funcLoad2($userID, $isActive) { //do something slightly different } } This means any class that wishes to "overload" methods in PHP must be instantiatable in some regard. I like to use a pattern whereby one of the participants is a utility class exposing only static functions which operates on data exposed from an "info" or state-bag class. This is still possible in PHP, I suppose. It just means I'll need to incur overhead to instantiate my utility classes as well. You CAN, by the way, call one static method from another using $this as I've done in the switch according to a "note" section on this page. http://us3.php.net/language.oop Again that may pre-suppose some level of instantiation for the class. php
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); ?> 31-aug-2004 07:39
For those using isset(): Currently (php5.0.1) isset will not check the __get method to see if you can retrieve a value from that function. So if you want to check if an overloaded value is set you'd need to use something like the __isset method below: <?php class foo { public $aryvars = array(); function __construct() {} function __get($key) { return array_key_exists($key, $this->aryvars) ? $this->aryvars[$key] : null; } function __isset($key) { if (!$isset = isset($this->$key)) { $isset = array_key_exists($key, $this->aryvars); } return $isset; } function __set($key, $value) { $this->aryvars[$key] = $value; } } echo '<pre>'; $foo = new foo(); $foo->a = 'test'; echo '$foo->a == ' . $foo->a . "\n"; echo 'isset($foo->a) == ' . (int) isset($foo->a) . "\n"; echo 'isset($foo->aryvars[\'a\']) == ' . isset($foo->aryvars['a']) . "\n"; echo '$foo->__isset(\'a\') == ' . $foo->__isset('a') . "\n"; var_dump($foo); ?> marius
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. anthony
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. james
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
example for read only properties in class object. Lets you manage read only properties with var names like $ro_var. The property must be PRIVATE, otherwise the overload method __get doesn't be called. class Session { private $ro_usrName; function __construct (){ $this->ro_usrName = "Marcos"; } function __set($set, $val){ if(property_exists($this,"ro_".$set)) echo "The property '$set' is read only"; else if(property_exists($this,$set)) $this->{$set}=$val; else echo "Property '$set' doesn't exist"; } function __get{$get}{ if(property_exists($this,"ro_".$get)) return $this->{"ro_".$get}; else if(property_exists($this,$get)) return $this->{$get}; else echo "Property '$get' doesn't exist"; } } note: getter cannot call getter
By Design (http://bugs.php.net/bug.php?id=33998) you cannot call a getter from a getter or any function triggered by a getter: class test { protected $_a = 6; function __get($key) { if($key == 'stuff') { return $this->stuff(); } else if($key == 'a') { return $this->_a; } } function stuff() { return array('random' => 'key', 'using_getter' => 10 * $this->a); } } $test = new test(); print 'this should be 60: '.$test->stuff['using_getter'].'<br/>'; // prints "this should be 60: 0" // [[ Undefined property: test::$a ]] on /var/www/html/test.php logged. print 'this should be 6: '.$test->a.'<br/>'; // prints "this should be 6: 6" mafu
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 xorith
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. zachary dot craig
@egingell at sisna dot com - Use of __call makes "overloading" possible also, although somewhat clunky... i.e. <?php class overloadExample { function __call($fName, $fArgs) { if ($fName == 'sum') { if (count ($fArgs) === 1) { return $fArgs[0]; } else { $retVal = 0; foreach( $fArgs as $arg ) { $retVal += $arg; } return $retVal; } } } } /* Simple and trivial I realize, but the point is made. Now an object of class overloadExample can take */ echo $obj->sum(4); // returns 4 echo $obj->sum(4, 5); // returns 9 ?> egingell
@ zachary dot craig at goebelmediagroup dot com I do something like that, too. My way might be even more clunky. <?php function sum() { $args = func_get_args(); if (!count($args)) { echo 'You have to supply something to the function.'; } elseif (count($args) == 1) { return $args[0]; } elseif (count($args) == 2) { if (is_numeric($args[0]) && is_numeric($args[1])) { return $args[0] + $args[1]; } else { return $args[0] . $args[1]; } } } ?> I like the "__call" method, though. You can "declare" multiple functions that don't pollute the namespace. Although, it might be better looking to use this instead of a series of if...elseif... statemenets. <?php class myClass { function __call($fName, $fArgs) { switch($fName) { case 'sum': if (count ($fArgs) === 1) { return $fArgs[0]; } else { $retVal = 0; foreach( $fArgs as $arg ) { $retVal += $arg; } return $retVal; } break; case 'other_method': ... default: die ('<div><b>Fetal Error:</b> unknown method ' . $fName . ' in ' . __CLASS__ . '.</div>'); } // end switch } } ?> Side note: if you don't force lower/upper case, you will have case sensitive method names. E.g. $myclass->sum() !== $myclass->Sum(). richard dot quadling
<?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. mileskeaton
<?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(); ?> jstubbs
$myclass->foo['bar'] = 'baz'; When overriding __get and __set, the above code can work (as expected) but it depends on your __get implementation rather than your __set. In fact, __set is never called with the above code. It appears that PHP (at least as of 5.1) uses a reference to whatever was returned by __get. To be more verbose, the above code is essentially identical to: $tmp_array = &$myclass->foo; $tmp_array['bar'] = 'baz'; unset($tmp_array); Therefore, the above won't do anything if your __get implementation resembles this: function __get($name) { return array_key_exists($name, $this->values) ? $this->values[$name] : null; } You will actually need to set the value in __get and return that, as in the following code: function __get($name) { if (!array_key_exists($name, $this->values)) $this->values[$name] = null; return $this->values[$name]; } bgoldschmidt
"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 |
Change LanguageIntroduction The Basics Autoloading Objects Constructors and Destructors Visibility Scope Resolution Operator (::) Static Keyword Class Constants Class Abstraction Object Interfaces Overloading Object Iteration Patterns Magic Methods Final Keyword Object cloning Comparing objects Reflection Type Hinting Late Static Bindings |