EDIT: Problem is a documented php bug by now: https://bugs.php.net/bug.php?id=71617 thanks to for finding that one @Danack
I'm just migrating an application from PHPH 5.5 to PHP 7 and stumbled over some strange behavior when it comes to serializing objects.
I have tried to cook it down to a minimal, complete and verifiable example which can be found at http://sandbox.onlinephpfunctions.com/code/e926a7398119ea715531cafe4ce6a22c329e53b8
The problem is that if a class extends ArrayObject then all private properties seem to just vanish if you serialize()
and then unserialize()
that object:
- create a class with a private property and getter/setter method for that property
- create an object of that class
- set private property via setter method
serialize()
objectunserialize()
result of step 4- call getter method of private property, result depends on your PHP version
- PHP 5.3 - PHP 5.6: result is value set in step 3
- PHP 7: result is null
I have tried to cook it down to a minimal, complete and verifiable example which can be found at http://sandbox.onlinephpfunctions.com/code/e926a7398119ea715531cafe4ce6a22c329e53b8 where you can test the code with different PHP versions.
<?php
class demoStdObject {
public $public = ''; protected $protected = ''; private $private = '';
public function getPublic() { return $this->public; }
public function getProtected() { return $this->protected; }
public function getPrivate() { return $this->private; }
public function setPublic($public) { $this->public = $public; }
public function setProtected($protected) { $this->protected = $protected; }
public function setPrivate($private) { $this->private = $private; }
}
class demoArrayObject extends ArrayObject {
public $public = ''; protected $protected = ''; private $private = '';
public function getPublic() { return $this->public; }
public function getProtected() { return $this->protected; }
public function getPrivate() { return $this->private; }
public function setPublic($public) { $this->public = $public; }
public function setProtected($protected) { $this->protected = $protected; }
public function setPrivate($private) { $this->private = $private; }
}
$arrayObject = new demoArrayObject();
$stdObject = new demoStdObject();
testSerialize($arrayObject);
echo str_repeat('-',30) . "\n";
testSerialize($stdObject);
function testSerialize($object) {
$object->setPublic('public');
$object->setProtected('protected');
$object->setPrivate('private');
$serialized = serialize($object);
$unserialized = unserialize($serialized);
echo get_class($object) . ":\n";
echo $unserialized->getPublic() . "\n";
echo $unserialized->getProtected() . "\n";
echo $unserialized->getPrivate() . "\n";
}
Output for PHP 5.6:
demoArrayObject:
public
protected
private
------------------------------
demoStdObject:
public
protected
private
Output for PHP 7:
demoArrayObject:
public
protected
------------------------------
demoStdObject:
public
protected
private
I could not find any documented changes related to serialize()
, unserialize()
or the ArrayObject
class so I am wondering what's going on. Is it a bug? Undocumented feature? ;-)
As we do a lot of serialize()
/ unserialize()
in our project I really need to make sure bahavior of PHP 7 is 100% compatible to PHP 5.3+ behavior.
Question: How can I make PHP 7 behave like PHP 5.3+??
Although this is fixed for the next release of PHP, the fact that your code is relying on an undocumented behaviour is an error known as "Programming by Coincidence". From the fine article:
In this case, there is no guarantee that when you extend ArrayObject the values of the child class will be unserialized correctly.
Either using composition rather than inheritance would be much safer, or writing serialize/unserialize methods on the child method would allow you to control the serialization/deserialization. Alternatively, just not using serialize/unserialize and instead using your own interface can also be more predictable than the "magic" internal methods.