Skip to content

Commit

Permalink
Added reflection serializer (#328)
Browse files Browse the repository at this point in the history
  • Loading branch information
abachmann authored and othillo committed Oct 23, 2017
1 parent 05e88ce commit df69c8d
Show file tree
Hide file tree
Showing 2 changed files with 325 additions and 0 deletions.
178 changes: 178 additions & 0 deletions src/Broadway/Serializer/ReflectionSerializer.php
@@ -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;
}
}
147 changes: 147 additions & 0 deletions test/Broadway/Serializer/ReflectionSerializerTest.php
@@ -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;
}
}

0 comments on commit df69c8d

Please sign in to comment.