|
Magic Methods
The function names
Caution:
PHP reserves all function names starting with __ as magical. It is recommended that you do not use function names with __ in PHP unless you want some documented magic functionality.
serialize() checks if your class has a function with
the magic name
The intended use of
Conversely, unserialize() checks for the
presence of a function with the magic name
The intended use of Example 10.27. Sleep and wakeup<?php
The Example 10.28. Simple example<?php The above example will output: Hello
It is worth noting that before PHP 5.2.0 the This static method is called for classes exported by var_export() since PHP 5.1.0.
The only parameter of this method is an array containing exported
properties in the form Code Examples / Notes » language.oop5.magicmartin dot goldinger
When you use sessions, its very important to keep the sessiondata small, due to low performance with unserialize. Every class shoud extend from this class. The result will be, that no null Values are written to the sessiondata. It will increase performance. <? class BaseObject { function __sleep() { $vars = (array)$this; foreach ($vars as $key => $val) { if (is_null($val)) { unset($vars[$key]); } } return array_keys($vars); } }; ?> andrew dot minerd
Until __sleep is "fixed" (here's hoping), a function that will return ALL members of a given object -- public, protected, AND private: <?php public function getPropertyNames(array $filter = NULL) { $rc = new ReflectionObject($this); $names = array(); while ($rc instanceof ReflectionClass) { foreach ($rc->getProperties() as $prop) { if (!$filter || !in_array($prop->getName(), $filter)) $names[] = $prop->getName(); } $rc = $rc->getParentClass(); } return $names; } ?> ksamvel
To copy base part of derived class appropriate method in base should be defined. E.g.: class A { public function setAVar( $oAVar) { $this->oAVar = $oAVar; } public function getAVar() { return $this->oAVar; } public function copyA( &$roDest) { if( $roDest instanceof A) $this->oAVar = $roDest->oAVar; } private $oAVar; } class B extends A { public function setBVar( $oBVar) { $this->oBVar = $oBVar; } public function getBVar() { return $this->oBVar; } private $oBVar; } $oA = new A(); $oB = new B(); $oA->setAVar( 4); $oB->setAVar( 5); $oB->setBVar( 6); echo "oA::oAVar " . $oA->getAVar() . " "; echo "oB::oAVar " . $oB->getAVar() . " "; echo "oB::oBVar " . $oB->getBVar() . " "; echo " "; $oB->copyA( $oA); echo "oA::oAVar " . $oA->getAVar() . " "; echo "oB::oAVar " . $oB->getAVar() . " "; echo "oB::oBVar " . $oB->getBVar() . " "; Output: oA::oAVar 4 oB::oAVar 5 oB::oBVar 6 oA::oAVar 4 oB::oAVar 4 oB::oBVar 6 domenic denicola
This small sentence tripped me up for a half an hour: "It is worth noting that the __toString method will only be called when it is directly combined with echo() or print()." So code like this will _not_ work, even though you might think it would: <? //$x is some variable with a __toString method defined. $y = "x's value is: " . $x; $y = "x's value is: " . (string)$x; ?> In _em_ both situations, $y will contain "x's value is: Object id #42" (or whatever object ID). So, the only recourse I guess is this: <? $y = "x's value is: " . $x->__toString(); ?> travis swicegood
There is no need to use eval() to mimic mixins (i.e., multiple inheritance) within PHP 5. You only need to: <?php class MyClass { private $_obj = null; public function __construct($obj) { $this->_obj = $obj; } public function __call($method, $args) { if (!method_exists($this->_obj, $method)) { throw new Exception("unknown method [$method]"); } return call_user_func_array( array($this->_obj, $method), $args ); } } ?> You could just as easily add an addMixin() method that would allow you to add multiple objects to an array, and then iterate over that array until you found the right method. As noted, these are referred to as a Mixins in other languages. alejandro dot gama
There is a trick to store static attributes with the objects in the $_SESSION array: <? session_start(); class Classes{ private $name; private $statics; function __construct($name){ $this->name=$name; $this->statics=array(); } function setStatic($key,$value){ if(!is_resource($value)) $this->statics[$key]=$value; } function __wakeup(){ foreach($this->statics as $k=>$v) eval($this->name."::\$".$k."=\$this->statics['".$k."'];"); unset($this); } } class Object{ function __sleep(){ $name=get_class($this); if(empty($_SESSION["_classes"])) $_SESSION["_classes"]=array(); if(empty($_SESSION["_classes"][$name])){ $_SESSION["_classes"][$name]=new Classes($name); $from_object=get_object_vars($this); $from_class=get_class_vars($name); if(count($from_object)>0) foreach($from_object as $k=>$value) if($clave=array_search($k,$from_class)) unset($from_class[$clave]); if(count($from_class)>0) foreach($from_class as $k=>$v) $_SESSION["_classes"][$name]->setStatic($k,$v); } return $from_object; } } ?> You must inherit all your objects from Object and when you save them in the session the static method from their classes will be store and restore to their original state. You must have one object from each class (that has static attributes) stored in the session. b dot schoppmeier
The sequence of events regarding __sleep and __destruct is unusual __ as __destruct is called before __sleep. The following code snippet: <?php $sequence = 0; class foo { public $stuff; public function __construct($param) { global $sequence; echo "Seq: ", $sequence++, " - constructor\n"; $this->stuff = $param; } public function __destruct() { global $sequence; echo "Seq: ", $sequence++, " - destructor\n"; } public function __sleep() { global $sequence; echo "Seq: ", $sequence++, " - __sleep\n"; return array("stuff"); } public function __wakeup() { global $sequence; echo "Seq: ", $sequence++, " - __wakeup\n"; } } session_start(); $_SESSION["obj"] = new foo("A foo"); ?> yields the output: Seq: 0 - constructor Seq: 1 - destructor Seq: 2 - __sleep Only when you end your script with a call to session_write_close() as in: <?php $sequence = 0; class foo { public $stuff; public function __construct($param) { global $sequence; echo "Seq: ", $sequence++, " - constructor\n"; $this->stuff = $param; } public function __destruct() { global $sequence; echo "Seq: ", $sequence++, " - destructor\n"; } public function __sleep() { global $sequence; echo "Seq: ", $sequence++, " - __sleep\n"; return array("stuff"); } public function __wakeup() { global $sequence; echo "Seq: ", $sequence++, " - __wakeup\n"; } } session_start(); $_SESSION["obj"] = new foo("A foo"); session_write_close(); ?> the sequence is as common sense would expect it to be as the following output shows: Seq: 0 - constructor Seq: 1 - __sleep Seq: 2 - destructor elias
The default toString output is very useful for visual debuggin because it shows the object id. There is no function to resolve the id directly(?), but you can do this: <?php function __toString() { sscanf((string)$this, "Object id #%d", $id); return "Object(Template) id #$id"; } ?> HTH, elias smoseley
Suggestion: add a __toArray() magic method that would default as exportation of an object's properties in an associative array.
dérico filho
Since PHP 5.2.0, you'll always get an error like this: "Object of class foo could not be converted to string" When one tries to use an object as string, for instance: class Test{} echo new Test(); Thus, one way to avoid this problem is to programme the magic method __toString. However, in the older versions, it would output a string saying that it was an object together a unique obj id. Therefore, the __toString() method must comply with this behaviour. My suggestion: class Test{ function __toString(){ if(!isset($this->__uniqid)) $this->__uniqid = md5(uniqid(rand(), true)); return(get_class($this)."@".$this->__uniqid); } } echo new Test(); would output something like this: Test@6006ba04f5569544c10a588b04849cf7 alejandro dot gama
Referering my previus note: there was an error in the code. But i find a better way: <? session_start(); class Classes{ private $name; private $statics; function __construct($name){ $this->name=$name; $this->statics=array(); } function setStatic($k,$v){ if(!is_resource($v)) $this->statics[$k]=$v; } function __wakeup(){ foreach($this->statics as $k=>$v) eval($this->name."::\$".$k."=\$this->statics['".$k."'];"); } } function storeStaticAttributes(){ $classes=get_declared_classes(); foreach($classes as $name){ $reflect=new ReflectionClass($name); if($reflect->isUserDefined()){ $statics=$reflect->getStaticProperties(); if(empty($_SESSION["_classes"])) $_SESSION["_classes"]=array(); if(empty($_SESSION["_classes"][$name])) $_SESSION["_classes"][$name]=new Classes($name); foreach($statics as $k=>$v) $_SESSION["_classes"][$name]->setStatic($k,$v); } } } register_shutdown_function('storeStaticAttributes'); ?> ddavenport
One of the principles of OOP is encapsulation--the idea that an object should handle its own data and no others'. Asking base classes to take care of subclasses' data, esp considering that a class can't possibly know how many dozens of ways it will be extended, is irresponsible and dangerous. Consider the following... <?php class SomeStupidStorageClass { public function getContents($pos, $len) { ...stuff... } } class CryptedStorageClass extends SomeStupidStorageClass { private $decrypted_block; public function getContents($pos, $len) { ...decrypt... } } ?> If SomeStupidStorageClass decided to serialize its subclasses' data as well as its own, a portion of what was once an encrypted thingie could be stored, in the clear, wherever the thingie was stored. Obviously, CryptedStorageClass would never have chosen this...but it had to either know how to serialize its parent class's data without calling parent::_sleep(), or let the base class do what it wanted to. Considering encapsulation again, no class should have to know how the parent handles its own private data. And it certainly shouldn't have to worry that users will find a way to break access controls in the name of convenience. If a class wants both to have private/protected data and to survive serialization, it should have its own __sleep() method which asks the parent to report its own fields and then adds to the list if applicable. Like so.... <?php class BetterClass { private $content; public function __sleep() { return array('basedata1', 'basedata2'); } public function getContents() { ...stuff... } } class BetterDerivedClass extends BetterClass { private $decrypted_block; public function __sleep() { return parent::__sleep(); } public function getContents() { ...decrypt... } } ?> The derived class has better control over its data, and we don't have to worry about something being stored that shouldn't be. adar
Maybe not really new and all in all definitely not the best solution,but if you cant extend a class (if your class alreay extends an abstract or other things like that) you can 'fake' a extend. <?php class MyClass extends SomeAbstractUnknownClass { private $classObject; public function __construct ( classObject $classToExtend ) { $this->classObject = $classToExtend; } public function __call($func, $var) { if ( !count($var) ) { return $this->classObject->$func($var); } else { $str = ''; $values = array_values($var); for ( $i=0; $i<count($values); $i++ ) { $str .= "'".$values[$i]."' ,"; } $str = substr($str, 0, -2); return eval('return $this->classObject->'.$func.'('.$str.');'); } } } ?> So if you'll do a $myClass->unknownMethod() and it is found neither in MyClass nor in SomeAbstractUnknownClass, MyClass will try to call this method in $classObject. I use this for 'extending' a UserObject-Class which already extends an other one. Better solutions are always welcome ;) otsu dot ni
Looks like __toString() method doesn't have to be declared as public (at least for php 5.2.1). <?php class A{ protected function __toString(){ return "Nya!\n"; } } class O{ private function __toString(){ return "Nyo!\n"; } } $nya=new A; $nyo=new O; echo $nya,$nyo; ?> jeffxlevy
Intriguing what happens when __sleep() and __wakeup() and sessions() are mixed. I had a hunch that, as session data is serialized, __sleep would be called when an object, or whatever, is stored in _SESSION. true. The same hunch applied when session_start() was called. Would __wakeup() be called? True. Very helpful, specifically as I'm building massive objects (well, lots of simple objects stored in sessions), and need lots of automated tasks (potentially) reloaded at "wakeup" time. (for instance, restarting a database session/connection).
mastabog
In reply to krisj1010 at gmail.com below: __sleep() handles protected/private properties very well. You should never rely on get_class_vars() to retrieve property names since this function only returns the public properties. Use the Reflection API instead for that purpose. Better yet, if you know which ones you want to save it is always faster to specify the return array manually. rayro
If you use the Magical Method '__set()', be shure that the call of <?php $myobject->test['myarray'] = 'data'; ?> will not appear! For that u have to do it the fine way if you want to use __set Method ;) <?php $myobject->test = array('myarray' => 'data'); ?> If a Variable is already set, the __set Magic Method already wont appear! My first solution was to use a Caller Class. With that, i ever knew which Module i currently use! But who needs it... :] There are quiet better solutions for this... Here's the Code: <?php class Caller { public $caller; public $module; function __call($funcname, $args = array()) { $this->setModuleInformation(); if (is_object($this->caller) && function_exists('call_user_func_array')) $return = call_user_func_array(array(&$this->caller, $funcname), $args); else trigger_error("Call to Function with call_user_func_array failed", E_USER_ERROR); $this->unsetModuleInformation(); return $return; } function __construct($callerClassName = false, $callerModuleName = 'Webboard') { if ($callerClassName == false) trigger_error('No Classname', E_USER_ERROR); $this->module = $callerModuleName; if (class_exists($callerClassName)) $this->caller = new $callerClassName(); else trigger_error('Class not exists: \''.$callerClassName.'\'', E_USER_ERROR); if (is_object($this->caller)) { $this->setModuleInformation(); if (method_exists($this->caller, '__init')) $this->caller->__init(); $this->unsetModuleInformation(); } else trigger_error('Caller is no object!', E_USER_ERROR); } function __destruct() { $this->setModuleInformation(); if (method_exists($this->caller, '__deinit')) $this->caller->__deinit(); $this->unsetModuleInformation(); } function __isset($isset) { $this->setModuleInformation(); if (is_object($this->caller)) $return = isset($this->caller->{$isset}); else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); return $return; } function __unset($unset) { $this->setModuleInformation(); if (is_object($this->caller)) { if (isset($this->caller->{$unset})) unset($this->caller->{$unset}); } else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); } function __set($set, $val) { $this->setModuleInformation(); if (is_object($this->caller)) $this->caller->{$set} = $val; else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); } function __get($get) { $this->setModuleInformation(); if (is_object($this->caller)) { if (isset($this->caller->{$get})) $return = $this->caller->{$get}; else $return = false; } else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); return $return; } function setModuleInformation() { $this->caller->module = $this->module; } function unsetModuleInformation() { $this->caller->module = NULL; } } // Well this can be a Config Class? class Config { public $module; public $test; function __construct() { print('Constructor will have no Module Information... Use __init() instead!<br />'); print('--> '.print_r($this->module, 1).' <--'); print('<br />'); print('<br />'); $this->test = '123'; } function __init() { print('Using of __init()!<br />'); print('--> '.print_r($this->module, 1).' <--'); print('<br />'); print('<br />'); } function testFunction($test = false) { if ($test != false) $this->test = $test; } } echo('<pre>'); $wow = new Caller('Config', 'Guestbook'); print_r($wow->test); print('<br />'); print('<br />'); $wow->test = '456'; print_r($wow->test); print('<br />'); print('<br />'); $wow->testFunction('789'); print_r($wow->test); print('<br />'); print('<br />'); print_r($wow->module); echo('</pre>'); ?> Outputs something Like: Constructor will have no Module Information... Use __init() instead! --> <-- Using of __init()! --> Guestbook <-- 123 456 789 Guestbook krisj1010
If you are attempting to write an abstract/base class which automates the __sleep process in PHP5 you will run into some trouble if the subclasses which are being serialized have private/protected variables you need to be serialized. The reason is, even though get_class($this) within the base class will return the subclass -- get_class_vars(get_class($this)) will *not* return the subclass' protected/private variables. Which makes sense -- using OO principles. However, when automating __sleep it becomes necissary to have access to the private/protected subclass variables because their names have to be returned by __sleep. So here is the work around: <?php public function __sleep() { ... code ... $sleepVars = array_keys((array)$this); return $sleepVars; } ?> Even though array_keys includes more information about the variable names than just the variable names -- it still seems to work appropriately. taylorbarstow
I've just come accross something interesting relating to storing PHP5 objects in a session. If you don't provide an __autoload(), then you MUST load the class definition before calling session_start(). I guess that when you call session_start(), any objects in the session are unserialized then and there and placed into $_SESSION. If you don't provide the class definition before calling session_start(), your object will get the class __PHP_Incomplete_Class, and you won't be able to use it for anything. Examples: <?php session_start(); require_once 'MyClass.php'; $obj = new MyClass; $_SESSION['obj'] = $obj; ?> Works fine. Then on a subsequent page load: <?php session_start(); require_once 'MyClass.php'; $_SESSION['obj']->callSomeMethod(); ?> Fatal error: The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "MyClass" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide a __autoload() function to load the class definition. But if you do this instead, it works fine: <?php require_once 'MyClass.php'; session_start(); $_SESSION['obj']->callSomeMethod(); ?> Hopefully in some future release of PHP, __PHP_Incomplete_Class will be smart enough to check for a class definition at time of use (method call or property operation), and, if the class exists, magically "complete" itself and turn into the desired object. gedoon-s
I just thought it was worth mentioning that __toString works also with <?= $class ?>. It calls __toString and echoes what was returned just like <?php echo $class; ?> does, but it's shorter.
amir_abiri
Another small thing that is important to note about __sleep() and privte member variables: <?php class A { private $a; public function __construct() { $this->a = 1; } } class B extends A { protected $b; public function __construct() { parent::__construct(); $this->b = 2; } function __sleep() { return array('a', 'b'); } } serialize(new B); ?> result: Notice: serialize(): "a" returned as member variable from __sleep() but does not exist in ... To summerize: in a given class hierarchy in which parent classes contain private member variables, those variables are serialized when __sleep() is not defined. However, once __sleep() is defined, there is no way to make those private member variables serialized as well. From that point on, serialization is performed from the visibility scope of the subclass. It is particularly important to note this little quirk when designing base classes that their derivables may be serialized, or when subclassing an external library class. docey
about __sleep and _wakeup, consider using a method like this: class core { var $sub_core; //ref of subcore var $_sleep_subcore; // place where serialize version of sub_core will be stored function core(){ $this->sub_core = new sub_core(); return true; } function __wakeup() { // on wakeup of core, core unserializes sub_core // wich it had stored when it was serialized itself $this->sub_core = unserialize($this->_sleep_subcore); return true; } function __sleep() { // sub_core will be serialized when core is serialized. // the serialized subcore will be stored as a string inside core. $this->_sleep_subcore = serialize($this->sub_core); $return_arr[] = "_sleep_subcore"; return $return_arr; } } class sub_core { var $info; function sub_core() { $this->info["somedata"] = "somedata overhere" } function __wakeup() { return true; } function __sleep() { $return_arr[] = "info" return $return_arr; } } this way subcore is being serialized by core when core is being serialized. subcore handles its own data and core stores it as a serialize string inside itself. on wakeup core unserializes subcore. this may have a performance cost, but if you have many objects connected this way this is the best way of serializing them. you only need to serialize the the main object wich will serialize all those below which will serialize all those below them again. in effect causing a sort of chainreaction in wich each object takes care of its own info. offcoarse you always need to store the eventualy serialized string in a safe place. somebody got experience with this way of __wakeup and __sleep. works in PHP4&5 alastair dot cooke
<?php echo phpversion(); class moose { function __toString() { return 'a moose'; } } $foo = new moose(); echo " i have $foo \n" ; echo $foo; ?> will give you: 5.1.6 i have Object id #278 a moose ie __toString does not happen when you interpolate things into a string... 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]; } |
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 |