Uh oh!
There was an error while loading. Please reload this page.
Skip uninitialized typed properties when serializing objects - #5396
Skip uninitialized typed properties when serializing objects#5396nicolas-grekas wants to merge 1 commit into
Conversation
nikic
commented
Apr 16, 2020
cc @TysonAndre |
nikic
commented
Apr 16, 2020
As before, I'm not really sure this is the right behavior, but I'm willing to accept it in the interest of pragmatism. |
nicolas-grekas
commented
Apr 16, 2020
that's all I need :) I was wondering: can/should we narrow this case even further and restrict to properties that have no default value only? If yes, how do we achieve this? |
nicolas-grekas
commented
Apr 16, 2020
I added a second commit to serialize uninitialized nullable typed properties with a default value to null. This means that such uninitialized properties will unserialize to |
Uh oh!
There was an error while loading. Please reload this page.
nikic
commented
Apr 16, 2020
This would be inconsistent with normal serialization behavior though. I also don't like making the behavior dependent on whether the type is nullable or not. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
If this is going to return success, it might make some sense to add a placeholder such as IS_UNDEF zend_hash_add(ht, name, undef_zval) when building this, then check for IS_UNDEF when using the resulting table
Otherwise, __sleep will inconsistently fail to warn about return ['prop', 'prop']; when prop is an uninitialized typed property
There was a problem hiding this comment.
How would that not break on unserialize(), since that's the point of this PR?
There was a problem hiding this comment.
I don't think trying to preserve that warning is worth the complications (using IS_UNDEF for this is not possible -- IS_UNDEF in hashtable means the element doesn't exist).
How that? The normal serialization behavior is to serialize a
Isn't this the best we can do with the current serialization format? |
There was a problem hiding this comment.
This looks like it will do something I wouldn't expect for an unset public array $notNullable = ['someValue'];
class Example {
publicarray$notNullable = ['someValue'];
publicfunction__sleep() { return ['notNullable']; } // also ['notNullable', 'notNullable'] should emit a notice.
}
$x = newExample();
unset($x->notNullable);
serialize($x); // I'd expect this to throw because unserialize(serialize($x)) != $x, but this code looks like it won'tif (Z_ISUNDEF_P(prop) || !ZEND_TYPE_ALLOW_NULL(prop_info->type)) {
returnSUCCESS;Also, further behaviors would probably be more readable in individual test files.
There was a problem hiding this comment.
Should probably document that in the case that ZEND_TYPE_ALLOW_NULL(prop_info->type), the serializer will later convert IS_UNDEF to null in the serialized value as the closest available equivalent.
nikic
commented
Apr 16, 2020
I mean the normal, non-sleep behavior. The premise of this change, to me, is to align the behavior of sleep and non-sleep serialization, in that both would completely ignore uninitialized properties.
I don't think it's really closer. I guess the angle you're going for here is that "null" also makes "isset" return false. On the other hand, the default is what you get if you don't initialize the property, one could reasonably argue that is closest to the "uninitialized" state. If you take into account that you can only run into this special case if a) the property is nullable b) has a specified default value and c) has been explicitly unset, the special case does not seem worthwhile. |
Oops, bad push, reopening.
OK, works for me, I removed the second commit, back to simpler behavior that doesn't depend on defaults nor nullable types. |
TysonAndre
commented
Apr 16, 2020
As an example of what I was referring to for non-nullable properties - this change makes sense for some use cases, but not others. The intent of the earlier change was to make sure that unserialize(serialize()) continued to be as close as possible to the original data (and start throwing when the data would be unrepresentable) <?phpclass A {
publicint$x;
publicfunction__sleep() {
return ['x'];
}
}
class B {
publicint$x = 2;
publicfunction__sleep() {
return ['x'];
}
}
$a = newA();
$a->x = 2;
unset($a->x);
serialize($a); // should not throw$b = newB();
unset($b->x);
serialize($b); // should throw instead of serializing a representation of the properties that's different from the actual properties when unserializing |
I see what you mean @TysonAndre. I'm going to let @nikic arbitrate this one. |
TysonAndre
commented
Apr 16, 2020
My personal preference would be to only do this in the case where the property default is IS_UNDEF (i.e. That was definitely an oversight in my previous change. For the other change (for everything in __sleep), I'd understand it, but I think this might introduce an inconsistency.
|
I'm fine with that |
nicolas-grekas
commented
Apr 16, 2020
Not any specific in mind. |
nikic
commented
Apr 16, 2020
I think The issue is that you're trying to fix a Doctrine bug ... inside Symfony. If we decide that this should continue throwing, then this issue really needs to be addressed directly inside Doctrine, by fixing their __sleep implementation. If they have a throwing __sleep, that affects more than just Symfony. The rest of this discussion seems to have circled back around to all the issue we already discussed in #5027 :) |
nikic
commented
Apr 16, 2020
I think the core problem here is that this is already not true for plain serialization, no __sleep involved: <?phpclass A {
publicint$x = 2;
}
$a = newA;
unset($a->x);
var_dump(unserialize(serialize($a)));This will return Given that this is already the behavior we have for non-sleep serialization, I feel like it's not overly awful to also adopt the same behavior when it comes to __sleep. |
Accessing an uninitialized property directly is quite different from accessing it indirectly via The place where we serialize is barely related to Doctrine: that's a quite common code that computes a hash for an arbitrary value. If we want to write this logic in a failsafe and generic way, we should not have to skip any throwables... |
nikic
commented
Apr 16, 2020
Okay, I can see both sides of the argument here. It depends on whether one wants to view this as "your __sleep implementation is broken, go fix it" or as "this object does not support serialization at this time". |
TysonAndre
commented
Apr 17, 2020
Yeah, I was thinking about that as TysonAndre#14 starts working on the bare minimum to serialize and unserialize An alternative would be
|
There was a problem hiding this comment.
I don't think trying to preserve that warning is worth the complications (using IS_UNDEF for this is not possible -- IS_UNDEF in hashtable means the element doesn't exist).
Uh oh!
There was an error while loading. Please reload this page.
Partially reverts 846b647: instead of throwing, this skips uninitialized typed properties when serializing objects. This preserves the `unserialize(serialize($obj)) == $obj` identity. Fixes bug https://bugs.php.net/79447
Partially reverts 846b647: instead of throwing, this skips uninitialized typed properties when serializing objects.
This preserves the
unserialize(serialize($obj)) == $objidentity.Fixes bug https://bugs.php.net/79447