Skip to content

Commit 7d2be52

Browse files
bug symfony#61453 [VarExporter] Fix serializing classes with __serialize() returning unprefixed private properties (nicolas-grekas)
This PR was merged into the 6.4 branch. Discussion ---------- [VarExporter] Fix serializing classes with __serialize() returning unprefixed private properties | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | - | License | MIT When `__serialize()` returns `['foo' => 123]`, the native implicit `__unserialize()` function will happily propagate this `foo` value to the first `private $foo` property in the inheritance chain. This didn't work when using VarExporter, and this PR fixes it. Commits ------- 3410a72 [VarExporter] Fix serializing classes with __serialize() returning unprefixed private properties
2 parents 254ae09 + 3410a72 commit 7d2be52

File tree

3 files changed

+49
-33
lines changed

3 files changed

+49
-33
lines changed

src/Symfony/Component/VarExporter/Internal/Exporter.php

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -76,33 +76,23 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
7676
$class = $value::class;
7777
$reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class);
7878
$properties = [];
79+
$sleep = null;
80+
$proto = Registry::$prototypes[$class];
7981

8082
if ($reflector->hasMethod('__serialize')) {
8183
if (!$reflector->getMethod('__serialize')->isPublic()) {
8284
throw new \Error(\sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class));
8385
}
8486

85-
if (!\is_array($serializeProperties = $value->__serialize())) {
87+
if (!\is_array($arrayValue = $value->__serialize())) {
8688
throw new \TypeError($class.'::__serialize() must return an array');
8789
}
8890

8991
if ($reflector->hasMethod('__unserialize')) {
90-
$properties = $serializeProperties;
91-
} else {
92-
foreach ($serializeProperties as $n => $v) {
93-
$p = $reflector->hasProperty($n) ? $reflector->getProperty($n) : null;
94-
$c = $p && (\PHP_VERSION_ID >= 80400 ? $p->isProtectedSet() || $p->isPrivateSet() : $p->isReadOnly()) ? $p->class : 'stdClass';
95-
$properties[$c][$n] = $v;
96-
}
92+
$properties = $arrayValue;
93+
goto prepare_value;
9794
}
98-
99-
goto prepare_value;
100-
}
101-
102-
$sleep = null;
103-
$proto = Registry::$prototypes[$class];
104-
105-
if (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) {
95+
} elseif (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) {
10696
// ArrayIterator and ArrayObject need special care because their "flags"
10797
// option changes the behavior of the (array) casting operator.
10898
[$arrayValue, $properties] = self::getArrayObjectProperties($value, $proto);
@@ -118,10 +108,7 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
118108
}
119109
$properties = ['SplObjectStorage' => ["\0" => $properties]];
120110
$arrayValue = (array) $value;
121-
} elseif ($value instanceof \Serializable
122-
|| $value instanceof \__PHP_Incomplete_Class
123-
|| \PHP_VERSION_ID < 80200 && $value instanceof \DatePeriod
124-
) {
111+
} elseif ($value instanceof \Serializable || $value instanceof \__PHP_Incomplete_Class || \PHP_VERSION_ID < 80200 && $value instanceof \DatePeriod) {
125112
++$objectsCount;
126113
$objectsPool[$value] = [$id = \count($objectsPool), serialize($value), [], 0];
127114
$value = new Reference($id);
@@ -145,16 +132,15 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
145132
$i = 0;
146133
$n = (string) $name;
147134
if ('' === $n || "\0" !== $n[0]) {
148-
$p = $reflector->hasProperty($n) ? $reflector->getProperty($n) : null;
149-
$c = $p && (\PHP_VERSION_ID >= 80400 ? $p->isProtectedSet() || $p->isPrivateSet() : $p->isReadOnly()) ? $p->class : 'stdClass';
135+
$parent = $reflector;
136+
do {
137+
$p = $parent->hasProperty($n) ? $parent->getProperty($n) : null;
138+
} while (!$p && $parent = $parent->getParentClass());
139+
140+
$c = $p && (!$p->isPublic() || (\PHP_VERSION_ID >= 80400 ? $p->isProtectedSet() || $p->isPrivateSet() : $p->isReadOnly())) ? $p->class : 'stdClass';
150141
} elseif ('*' === $n[1]) {
151142
$n = substr($n, 3);
152143
$c = $reflector->getProperty($n)->class;
153-
if ('Error' === $c) {
154-
$c = 'TypeError';
155-
} elseif ('Exception' === $c) {
156-
$c = 'ErrorException';
157-
}
158144
} else {
159145
$i = strpos($n, "\0", 2);
160146
$c = substr($n, 1, $i - 1);
@@ -167,8 +153,14 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
167153
}
168154
unset($sleep[$name], $sleep[$n]);
169155
}
170-
if (!\array_key_exists($name, $proto) || $proto[$name] !== $v || "\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) {
156+
if ("\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) {
171157
$properties[$c][$n] = $v;
158+
} elseif (!\array_key_exists($name, $proto) || $proto[$name] !== $v) {
159+
$properties[match ($c) {
160+
'Error' => 'TypeError',
161+
'Exception' => 'ErrorException',
162+
default => $c,
163+
}][$n] = $v;
172164
}
173165
}
174166
if ($sleep) {

src/Symfony/Component/VarExporter/Tests/Fixtures/__serialize-but-no-__unserialize.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,21 @@
66
],
77
null,
88
[
9-
'stdClass' => [
9+
'Symfony\\Component\\VarExporter\\Tests\\ParentOf__SerializeButNo__Unserialize' => [
1010
'foo' => [
11+
'foo',
12+
],
13+
],
14+
'stdClass' => [
15+
'baz' => [
1116
'ccc',
1217
],
1318
],
19+
'Symfony\\Component\\VarExporter\\Tests\\__SerializeButNo__Unserialize' => [
20+
'bar' => [
21+
'ddd',
22+
],
23+
],
1424
],
1525
$o[0],
1626
[]

src/Symfony/Component/VarExporter/Tests/VarExporterTest.php

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -468,19 +468,33 @@ public function __unserialize(array $data): void
468468
}
469469
}
470470

471-
class __SerializeButNo__Unserialize
471+
class ParentOf__SerializeButNo__Unserialize
472472
{
473-
public $foo;
473+
private $foo = 'foo';
474+
475+
public function getFoo()
476+
{
477+
return $this->foo;
478+
}
479+
}
480+
481+
class __SerializeButNo__Unserialize extends ParentOf__SerializeButNo__Unserialize
482+
{
483+
public $baz;
484+
private $bar;
474485

475486
public function __construct()
476487
{
477-
$this->foo = 'ccc';
488+
$this->baz = 'ccc';
489+
$this->bar = 'ddd';
478490
}
479491

480492
public function __serialize(): array
481493
{
482494
return [
483-
'foo' => $this->foo,
495+
'foo' => $this->getFoo(),
496+
'baz' => $this->baz,
497+
'bar' => $this->bar,
484498
];
485499
}
486500
}

0 commit comments

Comments
 (0)