diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index b38a5f6a4..962bf6a5b 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -126,17 +126,17 @@ protected function getAttributeValue(object $object, string $attribute, string $ $ucfirsted = ucfirst($attribute); $getter = 'get'.$ucfirsted; - if (\is_callable([$object, $getter])) { + if (method_exists($object, $getter) && \is_callable([$object, $getter])) { return $object->$getter(); } $isser = 'is'.$ucfirsted; - if (\is_callable([$object, $isser])) { + if (method_exists($object, $isser) && \is_callable([$object, $isser])) { return $object->$isser(); } $haser = 'has'.$ucfirsted; - if (\is_callable([$object, $haser])) { + if (method_exists($object, $haser) && \is_callable([$object, $haser])) { return $object->$haser(); } @@ -152,7 +152,14 @@ protected function setAttributeValue(object $object, string $attribute, $value, $key = \get_class($object).':'.$setter; if (!isset(self::$setterAccessibleCache[$key])) { - self::$setterAccessibleCache[$key] = \is_callable([$object, $setter]) && !(new \ReflectionMethod($object, $setter))->isStatic(); + try { + // We have to use is_callable() here since method_exists() + // does not "see" protected/private methods + self::$setterAccessibleCache[$key] = \is_callable([$object, $setter]) && !(new \ReflectionMethod($object, $setter))->isStatic(); + } catch (\ReflectionException $e) { + // Method does not exist in the class, probably a magic method + self::$setterAccessibleCache[$key] = false; + } } if (self::$setterAccessibleCache[$key]) { diff --git a/Tests/Normalizer/GetSetMethodNormalizerTest.php b/Tests/Normalizer/GetSetMethodNormalizerTest.php index 6fd430bb4..e6f8396fe 100644 --- a/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -453,6 +453,22 @@ public function testHasGetterNormalize() ); } + public function testCallMagicMethodDenormalize() + { + $obj = $this->normalizer->denormalize(['active' => true], ObjectWithMagicMethod::class); + $this->assertTrue($obj->isActive()); + } + + public function testCallMagicMethodNormalize() + { + $obj = new ObjectWithMagicMethod(); + + $this->assertSame( + ['active' => true], + $this->normalizer->normalize($obj, 'any') + ); + } + protected function getObjectCollectionWithExpectedArray(): array { return [[ @@ -722,3 +738,18 @@ public function hasFoo() return $this->foo; } } + +class ObjectWithMagicMethod +{ + private $active = true; + + public function isActive() + { + return $this->active; + } + + public function __call($key, $value) + { + throw new \RuntimeException('__call should not be called. Called with: '.$key); + } +}