2

This is by far the strangest thing i have seen in PHP, but there surely is some sort of explanation.

Using serialize() i am storing some objects. At a later point, i revive them using unserialize().

Today i discovered a problem with an object that has been unserialized. Picture this scenario:

object__product_bundle Object (
    [collateralValue] => 
    [collateralGroup] =>
)

Now imagine $obj to be an instance of object__product_bundle as shown above.

When i did:

$obj->collateralValue = 10;

And checked the object variables, i was shown:

object__product_bundle Object (
    [collateralValue] => 10
    [collateralGroup] => 10
)

Mindboggling!

I spent an hour smashing my head against the table, as this didn't make sense. But when i started using var_dump() on the object, before making changes to it, i saw this:

object(object__product_bundle)#28 (15) {
    ["collateralValue"] => &NULL
    ["collateralGroup"] => &NULL
}

Apparently these properties/variables were somehow linked. I researched &NULL and all i found was this question which told me i am dealing with some sort of references.

But how?

My object comes from a serialized string.

Now, taking a look at the serialized string i found this:

s:15:"collateralValue";N;s:15:"collateralGroup";R:15;

What is R:15 ?

Can it be the issue?

How can this problem be addressed and where does it come from?


EDIT

After digging deeper, i found the culprit.

Orientiation:

The objects (as described above) are stored into a property of another object, which is the item of a shop cart.

class shopCart {
    public $storage;
}

$cart->storage[] = new shopCart_item();

class shopCart_item {
    public $object;
}

$object is where the products (object__product_*) are stored.

Upon placing an order, with the aim of being repeated (subscription), this entire shopCart is stored into the database as a blob.

Whenever a subscription order is scheduled, an automated task then grabs the old shopCart and generates a new order from it.

And here i found the culprit - i added the properties (collateralValue etc.) later during development, but there had already been stored orders.

Now during debugging i found that this is where PHP starts creating references, although i do not understand why.

Simply put:

static public function generateOrderFromSubscription() {
    [...]
    $order = new object__webShop_order();
    var_dump($subscription->cart); // <-- no references are in here at all
    $order->cart = serialize($subscription->cart);
    var_dump($order->cart); // <-- suddenly, here i have the references
}

Apparantely, i use __sleep() for each object__product_* - which returns those variable names (including collateralValue and so on).

The question now becomes then: Why does PHP create references, when it is dealing with new properties for objects that were asleep but whose structure has changed in the meantime?

Very confusing!


EDIT #2

Finally some hope.

My __sleep() function basically returned a hardcoded array of variable names, as there were a ton of others i never wanted to store in the database. This approach apparently led to the current problem described in this question.

I still do not know why PHP creates references for variables in objects that were awoken without having those variables at all, but with those variables being returned in __sleep().

The only sensible solution to me, seemed to be to adapt __sleep(). I now do this:

public function __sleep(){

    $vars=array(
        'dbId',
        'title',
        'articleId',
        'price_per_unit',
    );

    if(isset($this->collateralValue))
        $vars[]='collateralValue';
    if(isset($this->collateralGroup))
        $vars[]='collateralGroup';

}

This way, __sleep() will not return (any of those two new) variable names (collateralValue, collateralGroup) which are not in use in the current object.

Community
  • 1
  • 1
SquareCat
  • 5,699
  • 9
  • 41
  • 75
  • I think this means that in the original object that you serialized, `$obj->collateralGroup` was a reference to `$obj->collateralValue`. `serialize` knows how to maintain these reference links. – Barmar Jul 08 '15 at 21:57

1 Answers1

1

Well let's analyse your serialized string:

s:15:"collateralValue";N;s:15:"collateralGroup";R:15;

First property (key):

s:15:"collateralValue"
  • s just means it is a string
  • 15 is the size of the string
  • collateralValue is the string itself the value (And if you look the string is 15 characters long)

First property (value):

N
  • N just mean NULL

Second property (key):

s:15:"collateralGroup"
  • s just means it is a string
  • 15 is the size of the string
  • collateralGroup is the string itself the value (And if you look the string is 15 characters long)

Second property (value):

R:15
  • R means reference
  • 15 means to the 15 value. So here the 15 value is probably the property collateralValue, which means if you change the value of it it also changes the value of the collateralGroup property

For more information see: http://www.phpinternalsbook.com/classes_objects/serialization.html

Rizier123
  • 58,877
  • 16
  • 101
  • 156
  • Thank you for pointing this out. It is good to learn about the reference in serialization, but my problem remains unsolved. Is there a way to ignore or references when unserializing? Or perhaps to detach or unlink referenced values from one another, after unserializing? – SquareCat Jul 08 '15 at 21:40
  • 1
    If you `unset()` or set `$obj->collateralGroup` to anything else it will no longer be a referece to `$obj->collateralValue` – AbraCadaver Jul 08 '15 at 21:42
  • I wish i could do that. But if it contains a value i need, then this approach will create more problems. The interesting part is that i have nowhere in my code created any kind of reference between those properties, and the values i assign to them are normal integers, not objects (and thus copies of one object). The final question then, i guess, is: how can i unlink those properties without sacrificing the contained values? – SquareCat Jul 08 '15 at 21:45
  • @SquareCat *But if it contains a value i need* How can it contain a value that you need it it is just a reference? Note, that you should unset the second property, so that the reference no longer exists. – Rizier123 Jul 08 '15 at 21:46
  • 1
    Take a look at _sleep() method -> http://php.net/manual/en/oop4.magic-functions.php – k.tarkin Jul 08 '15 at 21:48
  • 1
    Looking deeper into the matter i realize that i must fix the problem at its root. That means that i must find how in the world PHP got to think i wanted these properties to reference to one another in the first place. There is only one possible origin of the problem that i found though. At a certain point i do this: `foreach($vars as $field=>$value){ this->$field=$value; }` within the object. I wonder how this can cause the issue though. – SquareCat Jul 08 '15 at 21:50
  • @SquareCat That's probably the best to do, since it doesn't seems like you want it to be a reference. (So please edit your question and add your code). Also what is the output of: `var_dump($vars)` before the above code ? – Rizier123 Jul 08 '15 at 21:51
  • @SquareCat See my updated comment above ^^: http://stackoverflow.com/questions/31304012/serialized-object-coming-out-with-internal-value-references#comment50598627_31304269 – Rizier123 Jul 08 '15 at 22:02
  • I'm trying to get into this, but the code is rather complex and the whole process is triggered in a scheduled manner for orders scheduled for delivery on a certain day.. and so on. Testing this is a pain. – SquareCat Jul 08 '15 at 22:09
  • @SquareCat Then try to dig piece for piece into it. Or post the essential code here. (^ Also check if you have some references in `$vars`, if yes there could be the problem, that you assign a reference to the property) – Rizier123 Jul 08 '15 at 22:11
  • Alright, i found the culprit.. sort of. Edited the question. This is becoming an essay. – SquareCat Jul 08 '15 at 22:29
  • @SquareCat Please also add the outputs of the `var_dump()` calls from your edited question. Also show what you do in your `__sleep()` function if you have one – Rizier123 Jul 08 '15 at 22:45
  • `var_dump()` outputs are pretty intense. Suffice it to say that when checking the cart before serialization, it contains not even the variables, but after serialization they are around. As for `__sleep()` i think i just found the solution to the problem. I'll edit in a sec. – SquareCat Jul 08 '15 at 22:48
  • @SquareCat I can't really see through your code, since I don't see all of it, but you solved your problem now right ? (Also it still seems like you don't know where it creates the references, and since I can't see it in your code I can only guess, that you maybe assign an object anywhere and they are passed by reference: http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.new <- Example #4) – Rizier123 Jul 08 '15 at 22:54
  • @SquareCat See the updated comment ^^. Especially look in the link at example 4. I can only guess that you assign an object somewhere, which are always assigned by reference. – Rizier123 Jul 08 '15 at 23:03
  • 1
    Thank you, yes - the problem is solved by applying the technique i describe in EDIT #2 of my question. The whole thing is still interesting, because none of the properties the problem occured with, are objects.. they are simply values. And how those become a reference is beyond me. It is particularly interesting to note that this only happened with properties which were not part of the object/class when it was initially serialized. Strange behaviour from the side of PHP, imo. – SquareCat Jul 08 '15 at 23:08
  • @SquareCat So my last guess would be that you have a reference laying around in your code and somewhere you overwrite it. As an example what I mean: http://stackoverflow.com/q/4969243/3933332 <- If you don't unset the reference in a foreach loop. If you don't have anywhere any references like this, I'm out of ideas. – Rizier123 Jul 08 '15 at 23:10
  • 1
    Same here. At least the problem is solved anyway. Thank you for your strong support! – SquareCat Jul 08 '15 at 23:28