This continues my series which is thusfar:
- Looking at PHP's OOP from a CFMLer's perspective: classes, objects, properties
- Looking at PHP's OOP from a CFMLer's perspective: inheritance, static & abstract, interfaces
- Looking at PHP's OOP from a CFMLer's perspective: traits
- Looking at PHP's OOP from a CFMLer's perspective: namespaces
- Looking at PHP's OOP from a CFMLer's perspective: overloading (which is not what you'd assume it is)
The next section in the PHP OO docs is actually "Magic Methods" (oh please), but two of these are
__sleep()
and __wakeup()
, and these relate to object serialisation, so I decided to have a look at how this works, and __sleep()
and __wakeup()
at the same time.Firstly, here's a quick CFML example (this will run only on Railo, not ColdFusion, as it leverages stuff only Railo have thusfar implemented):
// Movie.cfc
component {
public Movie function init(required title, required year, required Person director) {
variables.title = title
variables.year = year
variables.director= director
return this
}
public function get() {
return [
title = title,
year = year,
director= director.getFullName()
]
}
public function getDirector(){
return director
}
}
// Person.cfc
component {
public Person function init(required firstName, required lastName){
variables.firstName= firstName
variables.lastName = lastName
return this
}
public function getFullName() {
return "#variables.firstName# #variables.lastName#"
}
}
<cfscript>
// movie.cfm
movie = new Movie("Once Were Warriors", 1994, new Person("Lee", "Tamahori"))
serialisedMovie = serialize(movie)
dump(serialisedMovie)
echo("<hr>")
deserialisedMovie = evaluate(serialisedMovie)
dump(var=deserialisedMovie.get(), label=getMetadata(deserialisedMovie).fullName)
director = deserialisedMovie.getDirector()
dump(var=director.getFullName(), label=getMetadata(director).fullName)
</cfscript>
Here we have a simple Movie class which has title, year and director, with the director being an instance of another class Person. I use Railo's
serialize()
function to serialise the movie, have a look at what it outputs, and then deserialise it again using - of all things - evaluate()
. I then dump out the results of the deserialisation to check it's actually worked properly.Here's the output:
string | evaluateComponent('scribble.shared.scratch.php.www.experiment.oo.serialisation.Movie','62aa1eb53f27541110d76b787d585ef6',{},{'DIRECTOR':evaluateComponent('scribble.shared.scratch.php.www.experiment.oo.serialisation.Person','b459ac28aa7dd48158ddfb3466213bfe',{},{'LASTNAME':'Tamahori','FIRSTNAME':'Lee'}),'YEAR':1994,'TITLE':'Once Were Warriors'}) |
scribble.shared.scratch.php.www.experiment.oo.serialisation.Movie | |||||||||||||||
|
scribble.shared.scratch.php.www.experiment.oo.serialisation.Person | ||
|
Excellent.
OK, now for PHP. Here's code roughly analogous to the CFML code above:
<?php
// Movie.class.php
class Movie
{
use Message;
private $title;
private $year;
private $director;
public function __construct($title, $year, $director)
{
$this->title = $title;
$this->year = $year;
$this->director = $director;
}
public function get()
{
return [
"title" => $this->title,
"year" => $this->year,
"director" => $this->director->getFullName()
];
}
}
<?php
// Person.class.php
class Person
{
private $firstName;
private $lastName;
public function __construct($firstName, $lastName)
{
$this->firstName = $firstName;
$this->lastName = $lastName;
}
public function getFullName()
{
return "$this->firstName $this->lastName";
}
}
<?php
// movie.php
require_once "./app.php";
$movie = new Movie("Once Were Warriors", 1994, new Person("Lee", "Tamahori"));
$serialisedMovie = serialize($movie);
echo "<pre>$serialisedMovie</pre><hr>";
$deserialisedMovie = unserialize($serialisedMovie);
new dBug([get_class($deserialisedMovie), $deserialisedMovie->get()]);
And the result is equivalent too:O:5:"Movie":3:{s:12:"Movietitle";s:18:"Once Were Warriors";s:11:"Movieyear";i:1994;s:15:"Moviedirector";O:6:"Person":2:{s:17:"PersonfirstName";s:3:"Lee";s:16:"PersonlastName";s:8:"Tamahori";}}
[get_class($deserialisedMovie), $deserialisedMovie->get()] (array) | |||||||||
0 | Movie | ||||||||
1 |
|
Nice one.
__sleep()
/ __wakeup()
What about these __sleep()
and __wakeup()
methods? They don't actually play much of a part in the serialisation - which clearly works fine without them - but are just basically event handlers. If you need to do anything before serialising, or before deserialising. The docs use examples of, say, committing data before serialising, or reestablishing DB connections etc before deserialising.Here I've updated Movie and Person to have stub methods which simply report they were called:
<?php
// Movie.class.php
class Movie
{
// ... snipped for clarity ...
public function __sleep()
{
SELF::message(__CLASS__, __FUNCTION__, func_get_args());
return ["title", "year", "director"];
}
public function __wakeup()
{
SELF::message(__CLASS__, __FUNCTION__, func_get_args());
}
}
<?php
// Person.class.php
class Person
{
// ... snipped for clarity ...
public function __sleep()
{
SELF::message(__CLASS__, __FUNCTION__, func_get_args());
return ["firstName", "lastName"];
}
public function __wakeup()
{
SELF::message(__CLASS__, __FUNCTION__, func_get_args());
}
}
Output:Movie->__sleep() called with arguments:
[]
Person->__sleep() called with arguments:
[]
O:5:"Movie":3:{s:12:"Movietitle";s:18:"Once Were Warriors";s:11:"Movieyear";i:1994;s:15:"Moviedirector";O:6:"Person":2:{s:17:"PersonfirstName";s:3:"Lee";s:16:"PersonlastName";s:8:"Tamahori";}}
Person->__wakeup() called with arguments:
[]
Movie->__wakeup() called with arguments:
[]
[get_class($deserialisedMovie), $deserialisedMovie->get()] (array) | |||||||||
0 | Movie | ||||||||
1 |
|
You can see that the
__sleep()
handler from both Movie and Person were called during the serialisation process, and __wakeup()
for both called during deserialisation.Cool.
Note how I'm returning a list of properties from
__sleep()
:return ["title", "year", "director"];
That's the list of properties that will be included in the serialisation. I'm just gonna change that to:
return ["title", "year"];
And run this test instead:
<?php
// movieWithoutDirector.php
require_once "./app.php";
$movie = new Movie("Once Were Warriors", 1994, new Person("Lee", "Tamahori"));
$serialisedMovie = serialize($movie);
echo "<pre>$serialisedMovie</pre><hr>";
$deserialisedMovie = unserialize($serialisedMovie);
echo "<pre>";
var_dump($deserialisedMovie);
echo "</pre>";
And the output:
Movie->__sleep() called with arguments:
[]
O:5:"Movie":2:{s:12:"Movietitle";s:18:"Once Were Warriors";s:11:"Movieyear";i:1994;}
Movie->__wakeup() called with arguments:
[]
object(Movie)#4 (3) { ["title":"Movie":private]=> string(18) "Once Were Warriors" ["year":"Movie":private]=> int(1994) ["director":"Movie":private]=> NULL }
Here the director property has been completely ignored from the serialisation process, and accordingly when it was deserialised the director is null.
Serializable
There is another approach one can take with this. PHP has a Serializable interface, which requires two methods being implemented:serialize()
and unserialize()
. In this case, when an object is (de)serialised, these methods are called instead.Here's an updated version of the Movie and Person classes:
<?php
// Movie.class.php
class Movie implements Serializable
{
// ... snipped for clarity ...
public function serialize()
{
SELF::message(__CLASS__, __FUNCTION__, func_get_args());
$arrayToSerialise = $this->get();
return json_encode($arrayToSerialise);
}
public function unserialize($serialized)
{
SELF::message(__CLASS__, __FUNCTION__, func_get_args());
$deserialisedArray = json_decode($serialized);
$this->title = $deserialisedArray->title;
$this->year = $deserialisedArray->year;
$director = Person::unpackFullName($deserialisedArray->director);
$this->director = new Person($director["firstName"], $director["lastName"]);
}
// ... snipped for clarity ...
}
<?php
// Person.class.php
class Person
{
// ... snipped for clarity ...
public static function unpackFullName($fullName)
{
$asArray = explode(" ", $fullName);
return [
"firstName" => $asArray[0],
"lastName" => $asArray[1]
];
}
}
There's nothing particularly gripping here (and note the added method to Person is just to assist unserialize()
from Movie), I simply use JSON as the serialisation method. Here's the output:Movie->serialize() called with arguments:
[]
C:5:"Movie":68:{{"title":"Once Were Warriors","year":1994,"director":"Lee Tamahori"}}
Movie->unserialize() called with arguments:
["{\"title\":\"Once Were Warriors\",\"year\":1994,\"director\":\"Lee Tamahori\"}"]
[get_class($deserialisedMovie), $deserialisedMovie->get()] (array) | |||||||||
0 | Movie | ||||||||
1 |
|
Note that
__sleep()
and __wakeup()
are not called when using Serialisable (this is documented, and by design).That's about it, as far as my findings go with PHP's object serialisation. Other than some dodgy function names, the implementation seems fine to me.
--
Adam