Thanks to visit codestin.com
Credit goes to github.com

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)