Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
325 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the broadway/broadway package. | ||
* | ||
* (c) Qandidate.com <opensource@qandidate.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Broadway\Serializer; | ||
|
||
use Assert\Assertion as Assert; | ||
use ReflectionClass; | ||
use ReflectionProperty; | ||
|
||
/** | ||
* Serializer that deeply serializes objects with the help of reflection. | ||
*/ | ||
class ReflectionSerializer implements Serializer | ||
{ | ||
/** | ||
* {@inheritDoc} | ||
*/ | ||
public function serialize($object) | ||
{ | ||
return $this->serializeObjectRecursively($object); | ||
} | ||
|
||
/** | ||
* @param mixed $value | ||
* | ||
* @return mixed | ||
*/ | ||
private function serializeValue($value) | ||
{ | ||
if (is_object($value)) { | ||
return $this->serializeObjectRecursively($value); | ||
} elseif (is_array($value)) { | ||
return $this->serializeArrayRecursively($value); | ||
} else { | ||
return $value; | ||
} | ||
} | ||
|
||
/** | ||
* @param array $array | ||
* | ||
* @return array | ||
*/ | ||
private function serializeArrayRecursively(array $array) | ||
{ | ||
$data = []; | ||
foreach ($array as $key => $value) { | ||
$data[$key] = $this->serializeValue($value); | ||
} | ||
|
||
return $data; | ||
} | ||
|
||
/** | ||
* @param object $object | ||
* | ||
* @return array | ||
*/ | ||
private function serializeObjectRecursively($object) | ||
{ | ||
$reflection = new ReflectionClass($object); | ||
$properties = $reflection->getProperties(); | ||
|
||
$data = []; | ||
foreach ($properties as $property) { | ||
$name = $property->getName(); | ||
|
||
$property->setAccessible(true); | ||
$value = $property->getValue($object); | ||
$property->setAccessible(false); | ||
|
||
$data[$name] = $this->serializeValue($value); | ||
} | ||
|
||
return [ | ||
'class' => get_class($object), | ||
'payload' => $data | ||
]; | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
public function deserialize(array $serializedObject) | ||
{ | ||
return $this->deserializeObjectRecursively($serializedObject); | ||
} | ||
|
||
/** | ||
* @param mixed $value | ||
* | ||
* @return mixed | ||
*/ | ||
private function deserializeValue($value) | ||
{ | ||
if (is_array($value) && isset($value['class']) && isset($value['payload'])) { | ||
return $this->deserializeObjectRecursively($value); | ||
} elseif (is_array($value)) { | ||
return $this->deserializeArrayRecursively($value); | ||
} else { | ||
return $value; | ||
} | ||
} | ||
|
||
/** | ||
* @param array $array | ||
* | ||
* @return array | ||
*/ | ||
private function deserializeArrayRecursively(array $array) | ||
{ | ||
$data = []; | ||
foreach ($array as $key => $value) { | ||
$data[$key] = $this->deserializeValue($value); | ||
} | ||
|
||
return $data; | ||
} | ||
|
||
/** | ||
* @param array $serializedObject | ||
* | ||
* @return object | ||
*/ | ||
private function deserializeObjectRecursively($serializedObject) | ||
{ | ||
Assert::keyExists($serializedObject, 'class', "Key 'class' should be set."); | ||
Assert::keyExists($serializedObject, 'payload', "Key 'payload' should be set."); | ||
|
||
$reflection = new ReflectionClass($serializedObject['class']); | ||
$properties = $reflection->getProperties(); | ||
$object = $reflection->newInstanceWithoutConstructor(); | ||
|
||
foreach ($serializedObject['payload'] as $name => $value) { | ||
$matchedProperty = $this->findProperty($properties, $name); | ||
if ($matchedProperty === null) { | ||
throw new SerializationException(sprintf( | ||
'Property \'%s\' not found for object \'%s\'', | ||
$name, | ||
$serializedObject['class'] | ||
)); | ||
} | ||
|
||
$value = $this->deserializeValue($value); | ||
|
||
$matchedProperty->setAccessible(true); | ||
$matchedProperty->setValue($object, $value); | ||
$matchedProperty->setAccessible(false); | ||
} | ||
|
||
return $object; | ||
} | ||
|
||
/** | ||
* @param ReflectionProperty[] $properties | ||
* @param string $name | ||
* | ||
* @return null|ReflectionProperty | ||
*/ | ||
private function findProperty(array $properties, $name) | ||
{ | ||
foreach ($properties as $property) { | ||
if ($property->getName() === $name) { | ||
return $property; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the broadway/broadway package. | ||
* | ||
* (c) Qandidate.com <opensource@qandidate.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Broadway\Serializer; | ||
|
||
use Broadway\TestCase; | ||
|
||
class ReflectionSerializerTest extends TestCase | ||
{ | ||
/** | ||
* @var Serializer | ||
*/ | ||
private $serializer; | ||
|
||
public function setUp() | ||
{ | ||
$this->serializer = new ReflectionSerializer(); | ||
} | ||
|
||
/** | ||
* @test | ||
* @expectedException \Assert\InvalidArgumentException | ||
* @expectedExceptionMessage Key 'class' should be set | ||
* @todo custom exception | ||
*/ | ||
public function it_throws_an_exception_if_class_not_set_in_data() | ||
{ | ||
$this->serializer->deserialize([]); | ||
} | ||
|
||
/** | ||
* @test | ||
* @expectedException \Assert\InvalidArgumentException | ||
* @expectedExceptionMessage Key 'payload' should be set | ||
* @todo custom exception | ||
*/ | ||
public function it_throws_an_exception_if_payload_not_set_in_data() | ||
{ | ||
$this->serializer->deserialize(['class' => 'SomeClass']); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function it_serializes_objects() | ||
{ | ||
$object = new TestReflectable( | ||
[new TestReflectableObject(['A', 1, 1.0], 11)], | ||
new TestReflectableObject(['B', 2, 2.0], 22), | ||
33 | ||
); | ||
|
||
$this->assertEquals([ | ||
'class' => 'Broadway\Serializer\TestReflectable', | ||
'payload' => [ | ||
'simpleValue' => 33, | ||
'arrayOfObjects' => [ | ||
[ | ||
'class' => 'Broadway\Serializer\TestReflectableObject', | ||
'payload' => [ | ||
'simpleArray' => ['A', 1, 1.0], | ||
'value' => 11 | ||
] | ||
] | ||
], | ||
'object' => [ | ||
'class' => 'Broadway\Serializer\TestReflectableObject', | ||
'payload' => [ | ||
'simpleArray' => ['B', 2, 2.0], | ||
'value' => 22 | ||
] | ||
] | ||
] | ||
], $this->serializer->serialize($object)); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function it_deserializes_array() | ||
{ | ||
$data = [ | ||
'class' => 'Broadway\Serializer\TestReflectable', | ||
'payload' => [ | ||
'simpleValue' => 33, | ||
'arrayOfObjects' => [ | ||
[ | ||
'class' => 'Broadway\Serializer\TestReflectableObject', | ||
'payload' => [ | ||
'simpleArray' => ['A', 1, 1.0], | ||
'value' => 11 | ||
] | ||
] | ||
], | ||
'object' => [ | ||
'class' => 'Broadway\Serializer\TestReflectableObject', | ||
'payload' => [ | ||
'simpleArray' => ['B', 2, 2.0], | ||
'value' => 22 | ||
] | ||
] | ||
] | ||
]; | ||
|
||
$object = new TestReflectable( | ||
[new TestReflectableObject(['A', 1, 1.0], 11)], | ||
new TestReflectableObject(['B', 2, 2.0], 22), | ||
33 | ||
); | ||
|
||
$this->assertEquals($object, $this->serializer->deserialize($data)); | ||
} | ||
} | ||
|
||
class TestReflectableObject | ||
{ | ||
private $simpleArray; | ||
private $value; | ||
|
||
public function __construct(array $simpleArray, $value) | ||
{ | ||
$this->simpleArray = $simpleArray; | ||
$this->value = $value; | ||
} | ||
} | ||
|
||
class TestReflectable | ||
{ | ||
private $arrayOfObjects; | ||
private $object; | ||
private $simpleValue; | ||
|
||
public function __construct(array $arrayOfObjects, $object, $simpleValue) | ||
{ | ||
$this->arrayOfObjects = $arrayOfObjects; | ||
$this->object = $object; | ||
$this->simpleValue = $simpleValue; | ||
} | ||
} |