Skip to content

Commit 5ec600e

Browse files
bug #48233 [Serializer] Prevent GetSetMethodNormalizer from creating invalid magic method call (klaussilveira)
This PR was merged into the 5.4 branch. Discussion ---------- [Serializer] Prevent `GetSetMethodNormalizer` from creating invalid magic method call | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - When serializing an object that has an `isser` for a property, but not a `getter`, and the object happens to have a `__call` magic method, the `GetSetMethodNormalizer` will attempt to call the magic method with the attribute. This may cause unexpected behavior, or, a fatal. It depends on the `__call` implementation. This undefined behavior is caused by the use of `is_callable` on `getAttributeValue`, which will return true since there is a `__call` implementation. The correct behavior can be achieved by using `method_exists` instead. A test case has been added that illustrates the issue. Commits ------- d4e1edd [Serializer] Prevent GetSetMethodNormalizer from creating invalid magic method call
2 parents f115d7a + d4e1edd commit 5ec600e

File tree

2 files changed

+42
-4
lines changed

2 files changed

+42
-4
lines changed

src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,17 +126,17 @@ protected function getAttributeValue(object $object, string $attribute, string $
126126
$ucfirsted = ucfirst($attribute);
127127

128128
$getter = 'get'.$ucfirsted;
129-
if (\is_callable([$object, $getter])) {
129+
if (method_exists($object, $getter) && \is_callable([$object, $getter])) {
130130
return $object->$getter();
131131
}
132132

133133
$isser = 'is'.$ucfirsted;
134-
if (\is_callable([$object, $isser])) {
134+
if (method_exists($object, $isser) && \is_callable([$object, $isser])) {
135135
return $object->$isser();
136136
}
137137

138138
$haser = 'has'.$ucfirsted;
139-
if (\is_callable([$object, $haser])) {
139+
if (method_exists($object, $haser) && \is_callable([$object, $haser])) {
140140
return $object->$haser();
141141
}
142142

@@ -152,7 +152,14 @@ protected function setAttributeValue(object $object, string $attribute, $value,
152152
$key = \get_class($object).':'.$setter;
153153

154154
if (!isset(self::$setterAccessibleCache[$key])) {
155-
self::$setterAccessibleCache[$key] = \is_callable([$object, $setter]) && !(new \ReflectionMethod($object, $setter))->isStatic();
155+
try {
156+
// We have to use is_callable() here since method_exists()
157+
// does not "see" protected/private methods
158+
self::$setterAccessibleCache[$key] = \is_callable([$object, $setter]) && !(new \ReflectionMethod($object, $setter))->isStatic();
159+
} catch (\ReflectionException $e) {
160+
// Method does not exist in the class, probably a magic method
161+
self::$setterAccessibleCache[$key] = false;
162+
}
156163
}
157164

158165
if (self::$setterAccessibleCache[$key]) {

src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,22 @@ public function testHasGetterNormalize()
453453
);
454454
}
455455

456+
public function testCallMagicMethodDenormalize()
457+
{
458+
$obj = $this->normalizer->denormalize(['active' => true], ObjectWithMagicMethod::class);
459+
$this->assertTrue($obj->isActive());
460+
}
461+
462+
public function testCallMagicMethodNormalize()
463+
{
464+
$obj = new ObjectWithMagicMethod();
465+
466+
$this->assertSame(
467+
['active' => true],
468+
$this->normalizer->normalize($obj, 'any')
469+
);
470+
}
471+
456472
protected function getObjectCollectionWithExpectedArray(): array
457473
{
458474
return [[
@@ -722,3 +738,18 @@ public function hasFoo()
722738
return $this->foo;
723739
}
724740
}
741+
742+
class ObjectWithMagicMethod
743+
{
744+
private $active = true;
745+
746+
public function isActive()
747+
{
748+
return $this->active;
749+
}
750+
751+
public function __call($key, $value)
752+
{
753+
throw new \RuntimeException('__call should not be called. Called with: '.$key);
754+
}
755+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy