php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #71617 private properties lost when ArrayObject unserialize
Submitted: 2016-02-17 09:35 UTC Modified: -
From: janpoem at gmail dot com Assigned:
Status: Closed Package: Class/Object related
PHP Version: 7.0.3 OS: Windows 10
Private report: No CVE-ID:
 [2016-02-17 09:35 UTC] janpoem at gmail dot com
Description:
------------
The private properties in an ArrayObject will lost when unserialize.

Test script:
---------------
<?php

class Test extends ArrayObject
{

	private $name = null;

	public function __construct(array $input)
	{
		parent::__construct($input, ArrayObject::ARRAY_AS_PROPS);
//		parent::__construct($input, ArrayObject::STD_PROP_LIST);
	}

	public function setName($name)
	{
		$this->name = $name;
		return $this;
	}

	public function getName()
	{
		return $this->name;
	}
}

$test = new Test(['a' => 'a', 'b' => 'b']);
$test->setName('ok');

$ser = serialize($test);
$unSer = unserialize($ser);

var_dump($unSer->getName()); // null
var_dump($unSer);
// php 7.0.3
/**
null

object(Test)[2]
	private 'name' => string 'ok' (length=2)
	private 'storage' (ArrayObject) =>
		array (size=2)
			'a' => string 'a' (length=1)
			'b' => string 'b' (length=1)

 */
// php 5.6.x
/**
string 'ok' (length=2)

object(Test)[2]
	private 'name' => string 'ok' (length=2)
	private 'storage' (ArrayObject) =>
		array (size=2)
			'a' => string 'a' (length=1)
			'b' => string 'b' (length=1)
 */


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-02-17 11:57 UTC] danack@php.net
Without commenting on whether this is a bug or not, your code isn't that safe.

Extending a class that implements Serializable and then hoping that properties in the child class are preserved across serialization/deserialization only works by chance - it is not a reliable way of programming.

The code below gives an example of where class A serializes/deserializes itself correctly, but doesn't know about class B, so doesn't do what the programmer of class B hoped, which is what you're seeing with ArrayObject. I don't believe there are any guarantees made by ArrayObject (or other classes) that what you are trying to do is going to work.

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.

class A implements Serializable {

    private $aValue;

    function __construct($aValue) {
        $this->aValue = $aValue;
    }

    function serialize() {
        $data = [];
        $data['aValue'] = $this->aValue;
        return json_encode($data);
    }

    function unserialize($serialized) {
        $data = json_decode($serialized, true);
        $this->aValue = $data['aValue'];
    }
}

class B extends A {
    function __construct($aValue, $bValue) {
        parent::__construct($aValue);
        $this->bValue = $bValue;
    }
}

$obj1 = new B("Give me an A!", "Give me a B!");
$serialized = serialize($obj1);
$obj2 = unserialize($serialized);
var_dump($obj1);
var_dump($obj2);
 [2016-02-17 13:03 UTC] janpoem at gmail dot com
I known what you are talking about. But I double check the source code in github.

https://github.com/php/php-src/blob/PHP-7.0.4/ext/spl/spl_array.c#L1700

and

https://github.com/php/php-src/blob/PHP-7.0.4/ext/spl/spl_array.c#L1804

It show when an ArrayObject was been serialized and un-serialized, it would carry the data in the storage and the members in this object. And in the serialized result string, storage data would use the keyword 'x:', and the members would use the keyword 'm:'.

So let's check the serialized string about an ArrayObject. In my case, the string is: 

C:4:"Test":80:{x:i:2;a:2:{s:1:"a";s:1:"a";s:1:"b";s:1:"b";};m:a:1:{s:10:"�Test�name";s:2:"ok";}}

We can see, the field "name" and its value "ok" was been stored.

Ok, let's drop all about it. Just think about these things:

1. In the un-serialized object, we can see the property "$name" has his value "ok". But now the problem is the method "getName" can not access his private property.

2. Let's see a more detail case: 

class Test extends ArrayObject
{

	private $name = null;

	protected $test = null;

	public function __construct(array $input)
	{
		parent::__construct($input, ArrayObject::ARRAY_AS_PROPS);
//		parent::__construct($input, ArrayObject::STD_PROP_LIST);
	}

	public function setName($name)
	{
		$this->name = $name;
		$this->test = $name;
		return $this;
	}

	public function getName()
	{
		return $this->name;
	}

	public function getTest()
	{
		return $this->test;
	}
}

$test = new Test(['a' => 'a', 'b' => 'b']);
$test->setName('ok');

$ser = serialize($test);
$unSer = unserialize($ser);

var_dump($unSer->getName()); // null
var_dump($unSer->getTest()); // ok
var_dump($unSer);

The method "getTest" will return the right result. So what would you say about it?

This is not a superficial issue, There are a lot of pecl extensions using unserialize method. Such as memcache, phpredis. This question brought a lot of chain of problems.
 [2016-02-21 12:02 UTC] nikic@php.net
Automatic comment on behalf of nikic
Revision: http://git.php.net/?p=php-src.git;a=commit;h=0bd64b50b88d243cf337e0c5dbea20e4ba809117
Log: Fixed bug #71617
 [2016-02-21 12:02 UTC] nikic@php.net
-Status: Open +Status: Closed
 [2016-07-20 11:33 UTC] davey@php.net
Automatic comment on behalf of nikic
Revision: http://git.php.net/?p=php-src.git;a=commit;h=0bd64b50b88d243cf337e0c5dbea20e4ba809117
Log: Fixed bug #71617
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Tue Aug 29 15:01:52 2017 UTC