|
The magic functions __sleep and __wakeup
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 Code Examples / Notes » language.oop.magic_functionsphp
To cut down the code in jan at sorgalla dot com's solution: <?php class foo { function __sleep() { return( array_keys( get_object_vars( &$this ) ) ); } } ?> darrylkuhn
This is pretty obvious, but as a note the __wakeup method cannot be used to require in the definition of the class for deserialization (although it would be nice... perhaps in future versions of php serialization might store the __wakeup method when an object is serialized to accomplish this).
06-jan-2005 04:09
There is a weird behavior occuring when serializing/unserializing a class derived from a (abstract) base class. The base may not unserialize itself if it has private members, because even if the derived class' __wakeup function is contains only parent::__wakeup() as its code, the unserialization takes place from the children's __wakeup() function and the parent's __wakeup() wont be called (as of php 5.0.3) and hence, private members from the parent wont ever be restored, even though they do serialize properly. This might/might not be a bug, but it is evil and is works not nicely with object oriented concepts. A workaround would be to put the base class' members protected or to (un)serialize manually the base class' via its derived class' __sleep() and __wakeup()
rkelly
The documentation above says this function is 'supposed' to return an array with the names of variables to be saved. What this in reality means is that if you dont, bad things will happen. I had forgotten to return the requested array after using __sleep to shutdown LDAP connections in an object, and was attempting to save the object as part of a session. Since __sleep was not returning anything, the serialisation was evidently not returning anything either. This broke the stored session data, as one of the fields was completely missing and the unserialize parsing then failed. Only by hacking through the serialised session data by hand was I able to track down the error, and my silly mistake. Perhaps PHP should have some way of dealing with this eventuality (treat it like an empty array?) so that it doesnt break sessions...? Anyway, hopefully this might save a few people the hours of headache I just went through. Make sure you return something from this function! michael_muir
The description isn't particularly clear about this, but it appears that if any variables identified in the array returned by __sleep() are objects, __sleep() will in turn be called on those objects. This is particularly useful if you have many levels of subordinate objects (not subclasses) stored in (for example) arrays. Simply return the array name from the 'top level' object's __sleep() and define a __sleep() for the object's class within the subordinate arrays and it shall be called for each.
jacobmather
Something interesting to point out... the magic __wakeup() function has access to global variables... such as... $_POST! How interesing... I think i'm going to use this to enhance template handeling. Interesting to think of though. Just make your template object referance the information it needs from inside the __wakeup routine, and make it display the initial page when the constructor is run... store it in a session variable. Sorry I don't have any code examples at the time... but it's just another odd option you have, like variable variable functions ... (i.e. $c = 'count'; $NumOfEntries = $c($array);) j dot gizmo
re: last post are you sure the problem with global variables doesn't arise from the use of references? <? class test { function __wakeup () { global $link; //will not work $link =& $this; //will work $GLOBALS['link'] =& $this; } } ?> see the section on references for further details mccann
It is difficult to store objects in sessions as PHP doesn't necessarily have the correct class definitions on hand to unserialize the objects. Below is a simple session class that can act as a container for objects, but you have to pass the path to the class definition for this to work... Assume you have this class defined in Session.php: class Session { # attributes var $_objects; # hash of named objects- of anything you want! Woot! var $_classes; # hash of classes- This should import all classes required for objects in the session var $_saved_objects; function Session() { if (isset($_SESSION['__SESSSION_GLOBAL_OBJ'])) {$this=$_SESSION['__SESSSION_GLOBAL_OBJ'];} else { $this->_objects=array(); $this->_classes=array(); $this->_saved_objects=array(); $_SESSION['__SESSSION_GLOBAL_OBJ']=$this; } } function setObject($name,&$object/*, Required Class Paths */) { for ($i=2;$i<func_num_args();$i++) { if (!in_array(func_get_arg($i),$this->_classes)) {$this->_classes[]=func_get_arg($i);} } $this->_objects[$name]=&$object; } function getObject($name) { if (isset($this->_objects[$name])) {return $this->_objects[$name];} return false; } function __sleep() { # Serialize all objects $keys = array_keys($this->_objects); for ($i=0;$i<count($keys);$i++) {$this->_saved_objects[$keys[$i]] = serialize($this->_objects[$keys[$i]]);} return array("_saved_forms","_saved_objects","_classes"); } function __wakeup() { # include all classes needed to unserialize objects in this session for ($i=0;$i<count($this->_classes);$i++) {include_once($this->_classes[$i]);} # unserialize objects $keys = array_keys($this->_saved_objects); for ($i=0;$i<count($keys);$i++) { $this->_objects[$keys[$i]] = unserialize($this->_saved_objects[$keys[$i]]); unset($this->_saved_objects[$keys[$i]]); } } } session_start(); if (!isset($_SESSION['__SESSSION_GLOBAL_OBJ'])) {$_SESSION['__SESSSION_GLOBAL_OBJ'] = new MSSession();} $SESSION = &$_SESSION['__SESSSION_GLOBAL_OBJ']; ========================================================= Now assume you have another class called MyClass defined in MyClass.php.... you can store it in the session as such: include_once('Session.php'); include_once('stuff/MyClass.php'); $joe = new MyClass(/*arguments*/); $SESSION->setObject('joe',$joe,'stuff/MyClass.php'); ========================================================= On another page you can retrieve this object from the session... include_once('Session.php'); $joe = &$SESSION->getObject('joe'); ========================================================= This works, but you HAVE to pass setObject the path of every class definition you need to use the object you're embedding. eric
It appears, that although __wakeup() can read from global defined variables // global $variable; , changing those variables don't affect them outside __wakeup(). The same thing seems to happen when an object is being serialized in a session when you start your session. if your __wakeup() function tries to set something within your session it might get overwritten/lost. In my situation it happened when an object in an object in a session tried to change a session variable on __wakup(). In my case, putting the variable in $GLOBAL and moving the variable in the session at a later time, is a satisfying workaround. jan
If you want to ensure that all object vars are saved completely, try this: <?php class Foo { function __sleep() { $objectVars = get_object_vars($this); $serializeVars = array(); foreach ($objectVars as $key => $val) { $serializeVars[] = $sKey; } return $serializeVars; } } ?> phreak
I just noticed the following: if you use $this inside __sleep, it does not change the original instance, because its a copy. very naughty. the only workaround for this reference - copy problem with __sleep i found is to use global variables, which is not very beautiful. <?php class A { var $e; function __sleep() { $this->e = 'sleeping...'; return array('e'); } } $a = new A(); $a->e = 'MyValue'; echo serialize($a)."\n"; echo $a->e; // echoes "MyValue" - not "sleeping..." ?> interesting to note: <?php echo serialize(&$a); // does what is expected ?> BUT: thats deprecated. scott
Here is a sample class and some test statements to demonstrate how __sleep() is used. If you do not return anything from __sleep(), your object will *not* be serialized. You must return something from __sleep() to tell serialize what to serialize. <? // to test the class $x = new Scott(); print_r($x); $y = serialize($x); $z = unserialize($y); print_r($z); // Simple class to test __sleep() class Scott { // class variables var $error; var $svar = array(); // constructor function Scott() { $this->svar['Hello'] = "World"; } function __sleep() { $this->svar['Hello'] = "Yawn"; // return list of instance-variables to be serialized return array('error', 'svar'); } function __wakeup() { $this->svar['test'] = "I'm here!"; } }// end class ?> cwfsp
Following up to rkelly at NO dot whitley dot unimelb dot SPAM dot edu dot au's note regarding __sleep() __sleep expects you to return an array of object variables that are allowed to be serialized. Not returning this array -will- result in your object not being serialized, and -will- cause headaches. If you need __sleep() to do cleanup: 1) do your cleanup 2) return the object variables in an array using the code from the comment from php at sharpdreams dot com (below) search phrases to help people find this info: php object will not (does not) work in session session object will not work in subsequent page views my object won't show up on next page agland
echoing output (even debugging statements) seems to cause problems in the __sleep() magic function. I suspect this is because, in the case of sessions, PHP is saving the session information (and running __sleep() in your object) after all the output has theoretically all been sent. This resulted in the serialized object appearing as an empty string in our session files. adamh
Correction to the above: If you want to make sure all variables in your class are serialized, don't write a __sleep() function. |