Just remembered: you can't serialize objects with references in them. By default, the referenced objects will be 'pulled in' to the serialized data, which will lead to very big files, and can even lead to errors if two objects reference each other (an infinite loop).
To serialize objects with references in them you have two options:
- Implement the magic class methods '__sleep' and/or '__wakeup'.
- Create your own serialization system.
When PHP serializes an object, it calls __sleep on that object if that method exists. You can implement it to return an array of variable-names that you want serrialized:
PHP Code:
class Object
{
var $reference;
var $data;
function __sleep()
{
return array('data'); // omit $reference
}
}
Vice versa, you can implement __wakeup to initialize members that weren't serialized, like references or database links. This sucks big time, however. How would you implement __wakeup for the class above?
PHP Code:
class Object
{
var $reference;
var $data;
function __wakeup()
{
// We'll have to restore $this->reference here,
// but how do we get to that object?
// Using a global? (Yikes!)
$this->reference =& ?? // You tell me!
}
}
Because I didn't quite like this (although __sleep is fine, in essence), and because there were problems with it (see the annotated PHP reference) I decided to implement serialization myself, something like this:
PHP Code:
class Object
{
var $reference;
var $data;
function Object(&$reference)
{
$this->reference =& $reference;
}
function create($arguments)
{
// Initialize the object from $arguments, e.g.:
$this->data = $arguments;
}
function getPersistentData()
{
return $this->data;
}
function setPersistentData($data)
{
$this->data = $data;
}
}
I separated the 'static' part from the 'dynamic' part. To fully create an object, I can write this:
PHP Code:
$object =& new Object($parent);
if (file_exists('object.dat'))
{
// Read the contents of object.dat into $data
$object->setPersistentData($data);
}
else
{
// Initialize the object's arguments in $arguments
$object->create($arguments);
// Execute other initialization methods
// ...
$data = $object->getPersistentData();
// Write $data to 'object.dat'
}
So on construction of an object I pass it all the references it needs, after which I can either initialize it directly, or through persistent data. Note that the class Object here knows nothing about reading and writing to files, which is completely intentional. (What if I want to store persistent data in a database, for example?)
The class Object I use myself is a bit smarter than this one. It provides a basic system for serializing objects, so I can use it as a base class for other classes that need serialization. Most of the time I don't need to override a single method to get those classes to serialize properly, but when required I can.
Because reading and writing files is a lot of work to type in each time (and current(file('object.dat')) is very inefficient), I also wrote a static class Cache that does that for me, allowing me to write this:
PHP Code:
$object =& new Object($parent);
if (!Cache::loadObject($object))
{
// Set some test arguments
$arguments = array(
'url' => 'http://www.sitepointforums.com',
'subject' => 'Serializing objects'
);
// Create the object and save it in the cache
$object->create($arguments);
Cache::saveObject($object);
}
This looks pretty clean and is easy to understand as well. For completeness, here is class Cache (simplified; the one I use is a bit different):
PHP Code:
class Cache
{
function saveData($filename, $data)
{
$fp = fopen(APP_CACHE . $filename, 'w');
if (!$fp)
{
exit("Could not open file $filename for writing!");
}
fputs($fp, serialize($data));
fclose($fp);
}
function loadData($filename)
{
if (!file_exists(APP_CACHE . $filename))
{
return false;
}
$fp = fopen(APP_CACHE . $filename, 'r');
if (!$fp)
{
exit("Could not open file $filename for reading!");
}
$data = fgets($fp, CACHE_BUFFER_SIZE);
fclose($fp);
return unserialize($data);
}
function saveObject(&$object)
{
// Construct a filename from the object's name. For me, that's:
$file = $object->getObjectId();
Cache::saveData($file, $object->getPersistentData());
}
function loadObject(&$object)
{
// Construct a filename from the object's name. For me, that's:
$file = $object->getObjectId();
$data = Cache::loadData($file);
if ($data !== false)
{
$object->setPersistentData($data);
return true;
}
return false;
}
}
?>
And that's all!
Note that this class requires two constants: APP_CACHE, which is the directory where the cache files are stored, and CACHE_BUFFER_SIZE, which is the maximum number of bytes to read when loading cached data. When you serialize an object, all data in it is stored on a single line; so if you have a big object, you'll end up with a very long line. You must take care that CACHE_BUFFER_SIZE is big enough to read the biggest object from file.
The class Cache I use myself is a bit bigger, because it uses localization (according to the language set by the system it reads and writes the correct localized cache files), and because it doesn't call 'exit' when something goes wrong, but 'throws an Exception' instead. Exceptions are other things I implemented for a system I'm working on, but they're of no interest here.
Anyway, hopefully you can find some use for this!
Vincent
Bookmarks