diff --git a/src/JsonSerializer/JsonSerializer.php b/src/JsonSerializer/JsonSerializer.php index ec02718..4b16ea2 100644 --- a/src/JsonSerializer/JsonSerializer.php +++ b/src/JsonSerializer/JsonSerializer.php @@ -353,9 +353,41 @@ protected function getObjectProperties($ref, $value) foreach ($ref->getProperties() as $prop) { $props[] = $prop->getName(); } + + // Private properties of parent classes are not returned by getProperties() on the child class, + // so we traverse the parent chain to collect them. + $parentRef = $ref->getParentClass(); + while ($parentRef !== false) { + foreach ($parentRef->getProperties(\ReflectionProperty::IS_PRIVATE) as $prop) { + $props[] = $prop->getName(); + } + $parentRef = $parentRef->getParentClass(); + } + return array_unique(array_merge($props, array_keys(get_object_vars($value)))); } + /** + * Find a ReflectionProperty by traversing the class hierarchy (needed for parent private properties) + * + * @param ReflectionClass $ref + * @param string $name + * @return \ReflectionProperty + * @throws ReflectionException + */ + protected function getReflectionProperty($ref, $name) + { + $classRef = $ref; + while ($classRef !== false) { + try { + return $classRef->getProperty($name); + } catch (ReflectionException $e) { + $classRef = $classRef->getParentClass(); + } + } + throw new ReflectionException('Property ' . $name . ' not found in class ' . $ref->getName() . ' or its parents'); + } + /** * Extract the object data * @@ -369,7 +401,7 @@ protected function extractObjectData($value, $ref, $properties) $data = []; foreach ($properties as $property) { try { - $propRef = $ref->getProperty($property); + $propRef = $this->getReflectionProperty($ref, $property); $propRef->setAccessible(true); if (!$propRef->isInitialized($value)) { continue; @@ -515,7 +547,7 @@ protected function unserializeObject($value) $this->objectMapping[$this->objectMappingIndex++] = $obj; foreach ($value as $property => $propertyValue) { try { - $propRef = $ref->getProperty($property); + $propRef = $this->getReflectionProperty($ref, $property); $propRef->setAccessible(true); $propRef->setValue($obj, $this->unserializeData($propertyValue)); } catch (ReflectionException $e) { diff --git a/tests/JsonSerializerTest.php b/tests/JsonSerializerTest.php index 18b8f3c..f6bdb55 100644 --- a/tests/JsonSerializerTest.php +++ b/tests/JsonSerializerTest.php @@ -243,6 +243,29 @@ public function testUnserializeObjects() } + /** + * Test serialization/unserialization of objects with parent class private properties + * + * @return void + */ + public function testSerializeObjectWithParentPrivateProperty() + { + $obj = new SupportClasses\ChildWithParentPrivate(); + $serialized = $this->serializer->serialize($obj); + $this->assertStringContainsString('"parentPrivate":"parentPrivateValue"', $serialized); + $this->assertStringContainsString('"childPublic":"childPublicValue"', $serialized); + $this->assertStringContainsString('"childPrivate":"childPrivateValue"', $serialized); + $this->assertStringContainsString('"parentPublic":"parentPublicValue"', $serialized); + + /** @var SupportClasses\ChildWithParentPrivate $restored */ + $restored = $this->serializer->unserialize($serialized); + $this->assertInstanceOf(SupportClasses\ChildWithParentPrivate::class, $restored); + $this->assertSame('parentPrivateValue', $restored->getParentPrivate()); + $this->assertSame('childPublicValue', $restored->childPublic); + $this->assertSame('parentPublicValue', $restored->parentPublic); + } + + /** * Test serialization of Enums * diff --git a/tests/SupportClasses/ChildWithParentPrivate.php b/tests/SupportClasses/ChildWithParentPrivate.php new file mode 100644 index 0000000..a053982 --- /dev/null +++ b/tests/SupportClasses/ChildWithParentPrivate.php @@ -0,0 +1,27 @@ +parentPrivate; + } + + public function setParentPrivate($value) + { + $this->parentPrivate = $value; + } +} + +class ChildWithParentPrivate extends ParentWithPrivate +{ + public $childPublic = 'childPublicValue'; + + private $childPrivate = 'childPrivateValue'; +}