Skip to content

Commit 301a5bb

Browse files
committed
ASV: support PHP 7.4 serialized format
1 parent 2f747f9 commit 301a5bb

File tree

3 files changed

+102
-2
lines changed

3 files changed

+102
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
v2.2.0 (2019-11)
22

33
* Improved unserialization
4+
* Forward-compatibility to shorter PHP7.4 format
45
* Re-validate serialized value
56
* Tests
67

src/AbstractSerializableValue.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,37 @@ public function jsonSerialize()
4444
* contains a value which is no longer considered valid.
4545
* That's why this method re-applies the {@see isValid} check.
4646
*
47+
* This method also provides forward compatibility to the new PHP 7.4
48+
* `__serialize`/`__unserialize` format we'll implement in v3.
49+
* It is only called in a PHP7.3 (or older) environment
50+
* as PHP 7.4+ ignores it if an __unserialize method is present.
51+
*
4752
* @internal
4853
*/
4954
public function __wakeup()
5055
{
56+
if (isset($this->{'0'})) {
57+
/* We're running PHP 7.3 or older and this instance was just unserialized
58+
* from a serialization created with PHP 7.4+,
59+
* which just contains [0 => $value].
60+
* Luckily PHP 7.3's unserialize() can sort of handle that:
61+
* it will write the value into a magic property called '0'.
62+
* We'll just call the constructor (which hasn't happened yet)
63+
* to run validation and to set $isSet. */
64+
$inputValue = $this->{'0'};
65+
unset($this->{'0'});
66+
$this->__construct($inputValue);
67+
return;
68+
}
69+
5170
/*
52-
* The serialization contained both $value and $isSet
71+
* We're running PHP 7.3 or older and this instance was just unserialized
72+
* from a serialization also created with PHP 7.3 or older.
73+
* In this case, the serialization contained both $value and $isSet
5374
* and there's nothing left to assign.
5475
* But we'll still run the validation
5576
* just in case the serialized value is no longer considered valid.
5677
*/
57-
5878
$storedValue = $this->value();
5979
if (!static::isValid($storedValue)) {
6080
$storedValue = (is_string($storedValue) || is_int($storedValue) || is_float($storedValue))

test/15-SerializableValueTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace mle86\Value\Tests;
44

55
use mle86\Value\AbstractSerializableValue;
6+
use mle86\Value\AbstractValue;
67
use mle86\Value\InvalidArgumentException;
78
use mle86\Value\Tests\Helpers\TestSWrapper6;
89
use mle86\Value\Value;
@@ -100,6 +101,10 @@ public function testPhpSerialize(AbstractSerializableValue $tw): string
100101
$ser = serialize($tw);
101102
$this->assertNotEmpty($ser);
102103
return $ser;
104+
105+
# TODO: if we raise our PHP dependency to 7.4+
106+
# we can assume that $set came directly from the __serialize method;
107+
# that means we can assert that it doesn't contain the $isSet flag.
103108
}
104109

105110
/**
@@ -147,4 +152,78 @@ public function testSerializedValueValidity()
147152
unserialize($invalidSerialization);
148153
}
149154

155+
/**
156+
* Ensures that the future PHP7.4 serialization format
157+
* is already accepted (and correctly processed)
158+
* by this version of the library.
159+
*
160+
* @depends testPhpUnserialize
161+
* @depends testSerializedValueValidity
162+
*/
163+
public function testUnserializationForwardCompatibility()
164+
{
165+
$twFqcn = TestSWrapper6::class;
166+
$twFqcnLen = strlen($twFqcn);
167+
$inputValue = self::VALID_INPUT2;
168+
$inputLen = strlen($inputValue);
169+
$invalidInputValue = self::INVALID_INPUT2;
170+
$invalidInputLen = strlen($inputValue);
171+
172+
$validPhp74Serialization =
173+
"O:{$twFqcnLen}:\"{$twFqcn}\":1:{i:0;s:{$inputLen}:\"{$inputValue}\";}";
174+
$invalidPhp74Serialization =
175+
"O:{$twFqcnLen}:\"{$twFqcn}\":1:{i:0;s:{$invalidInputLen}:\"{$invalidInputValue}\";}";
176+
177+
/** @var TestSWrapper6 $unserializedObject */
178+
$unserializedObject = unserialize($validPhp74Serialization);
179+
$this->assertInstanceOf(TestSWrapper6::class, $unserializedObject,
180+
"FC Unserialization returned object of wrong class!");
181+
$this->assertEquals($inputValue, $unserializedObject->value(),
182+
"FC Unserialized object has incorrect value!");
183+
184+
$this->expectException(InvalidArgumentException::class);
185+
unserialize($invalidPhp74Serialization);
186+
}
187+
188+
/**
189+
* Ensures that the pre-PHP7.4 serialization format
190+
* is still accepted and correctly processed,
191+
* even if this library is being used within PHP 7.4+.
192+
*
193+
* @depends testPhpUnserialize
194+
* @depends testSerializedValueValidity
195+
*/
196+
public function testUnserializationBackwardCompatibility()
197+
{
198+
$twFqcn = TestSWrapper6::class;
199+
$twFqcnLen = strlen($twFqcn);
200+
$valVarName = "\0" . AbstractValue::class . "\0" . 'value';
201+
$valVarLen = strlen($valVarName);
202+
$setVarName = "\0" . AbstractValue::class . "\0" . 'isSet';
203+
$setVarLen = strlen($valVarName);
204+
$inputValue = self::VALID_INPUT2;
205+
$inputLen = strlen($inputValue);
206+
$invalidInputValue = self::INVALID_INPUT2;
207+
$invalidInputLen = strlen($inputValue);
208+
209+
$validPhp73Serialization =
210+
"O:{$twFqcnLen}:\"{$twFqcn}\":2:{".
211+
"s:{$valVarLen}:\"{$valVarName}\";s:{$inputLen}:\"{$inputValue}\";".
212+
"s:{$setVarLen}:\"{$setVarName}\";b:1;}";
213+
$invalidPhp73Serialization =
214+
"O:{$twFqcnLen}:\"{$twFqcn}\":2:{".
215+
"s:{$valVarLen}:\"{$valVarName}\";s:{$invalidInputLen}:\"{$invalidInputValue}\";".
216+
"s:{$setVarLen}:\"{$setVarName}\";b:1;}";
217+
218+
/** @var TestSWrapper6 $unserializedObject */
219+
$unserializedObject = unserialize($validPhp73Serialization);
220+
$this->assertInstanceOf(TestSWrapper6::class, $unserializedObject,
221+
"BC Unserialization returned object of wrong class!");
222+
$this->assertEquals($inputValue, $unserializedObject->value(),
223+
"BC Unserialized object has incorrect value!");
224+
225+
$this->expectException(InvalidArgumentException::class);
226+
unserialize($invalidPhp73Serialization);
227+
}
228+
150229
}

0 commit comments

Comments
 (0)