diff --git a/Resources/config/services.exporter.types.yml b/Resources/config/services.exporter.types.yml index ed00698..326b9cf 100644 --- a/Resources/config/services.exporter.types.yml +++ b/Resources/config/services.exporter.types.yml @@ -2,6 +2,16 @@ services: _defaults: public: false + superbrave_gdpr.exporter.serializer.normalizer.iterable_normalizer: + class: Superbrave\GdprBundle\Serializer\Normalizer\IterableNormalizer + tags: + - { name: "superbrave_gdpr.serializer.normalizer" } + + superbrave_gdpr.exporter.serializer.normalizer.datetime_normalizer: + class: Symfony\Component\Serializer\Normalizer\DateTimeNormalizer + tags: + - { name: "superbrave_gdpr.serializer.normalizer" } + superbrave_gdpr.exporter.serializer.normalizer.export_annotation: class: Superbrave\GdprBundle\Serializer\Normalizer\AnnotationNormalizer arguments: diff --git a/Serializer/Normalizer/AnnotationNormalizer.php b/Serializer/Normalizer/AnnotationNormalizer.php index b3e89c5..31768ba 100644 --- a/Serializer/Normalizer/AnnotationNormalizer.php +++ b/Serializer/Normalizer/AnnotationNormalizer.php @@ -16,15 +16,20 @@ use Superbrave\GdprBundle\Annotation\AnnotationReader; use Superbrave\GdprBundle\Manipulator\PropertyManipulator; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** * Normalizes object data based on the specified property annotation. * * @author Niels Nijens + * @author Jelle van Oosterbosch */ -class AnnotationNormalizer implements NormalizerInterface +class AnnotationNormalizer implements NormalizerInterface, NormalizerAwareInterface { + use NormalizerAwareTrait; + /** * The AnnotationReader instance. * @@ -70,6 +75,8 @@ public function __construct( * @param string $format The format being (de-)serialized from or into * * @return bool + * + * @throws \ReflectionException */ public function supportsNormalization($data, $format = null) { @@ -93,6 +100,8 @@ public function supportsNormalization($data, $format = null) * @param array $context Context options for the normalizer * * @return array|string|int|float|bool + * + * @throws \ReflectionException */ public function normalize($object, $format = null, array $context = array()) { @@ -108,7 +117,11 @@ public function normalize($object, $format = null, array $context = array()) $propertyName = $propertyAnnotation->alias; } - $normalizedData[$propertyName] = $this->getMappedPropertyValue($propertyAnnotation, $propertyValue); + if (false === is_scalar($propertyValue)) { + $normalizedData[$propertyName] = $this->normalizer->normalize($propertyValue, $format, $context); + } else { + $normalizedData[$propertyName] = $this->getMappedPropertyValue($propertyAnnotation, $propertyValue); + } } return $normalizedData; @@ -124,10 +137,6 @@ public function normalize($object, $format = null, array $context = array()) */ private function getMappedPropertyValue($annotation, $propertyValue) { - if (is_scalar($propertyValue) === false) { - return $propertyValue; - } - if (property_exists($annotation, 'valueMap') === false || isset($annotation->valueMap) === false) { return $propertyValue; } diff --git a/Serializer/Normalizer/IterableNormalizer.php b/Serializer/Normalizer/IterableNormalizer.php new file mode 100644 index 0000000..ccdbd9b --- /dev/null +++ b/Serializer/Normalizer/IterableNormalizer.php @@ -0,0 +1,53 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace Superbrave\GdprBundle\Serializer\Normalizer; + +use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + +/** + * Normalizes data if it's iterable by calling the normalizer chain. + * + * @author Jelle van Oosterbosch + */ +class IterableNormalizer implements NormalizerInterface, NormalizerAwareInterface +{ + use NormalizerAwareTrait; + + /** + * {@inheritdoc} + */ + public function supportsNormalization($data, $format = null) + { + if (is_array($data) || $data instanceof \Traversable) { + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function normalize($object, $format = null, array $context = array()) + { + $normalizedData = []; + + foreach ($object as $value) { + $normalizedData[] = $this->normalizer->normalize($value, $format, $context); + } + + return $normalizedData; + } +} diff --git a/Tests/AnnotatedMock.php b/Tests/AnnotatedMock.php index 4f8300d..4efc676 100644 --- a/Tests/AnnotatedMock.php +++ b/Tests/AnnotatedMock.php @@ -19,6 +19,7 @@ * Class used to test the @see GDPR\AnnotationReader. * * @author Niels Nijens + * @author Jelle van Oosterbosch */ class AnnotatedMock { @@ -59,6 +60,15 @@ class AnnotatedMock */ private $quux; + /** + * The quuz property. + * + * @GDPR\Export() + * + * @var \DateTime + */ + private $quuz; + /** * The property that is annotated with the Export annotation, but without getter method. * @@ -90,8 +100,10 @@ public function __construct(AnnotatedMock $annotatedMock = null) $elements = array(); if ($annotatedMock instanceof AnnotatedMock) { $elements[] = $annotatedMock; + $elements[] = clone $annotatedMock; } + $this->quuz = new \DateTime('2016/01/01'); $this->quux = new ArrayCollection($elements); } @@ -135,6 +147,14 @@ public function getQuux() return $this->quux; } + /** + * @return \DateTime + */ + public function getQuuz() + { + return $this->quuz; + } + /** * Returns the value of the notAnnotatedProperty property. * diff --git a/Tests/Annotation/AnnotationReaderTest.php b/Tests/Annotation/AnnotationReaderTest.php index fa308a7..1c020d0 100644 --- a/Tests/Annotation/AnnotationReaderTest.php +++ b/Tests/Annotation/AnnotationReaderTest.php @@ -24,6 +24,7 @@ * AnnotationReaderTest. * * @author Niels Nijens + * @author Jelle van Oosterbosch */ class AnnotationReaderTest extends PHPUnit_Framework_TestCase { @@ -64,9 +65,9 @@ public function testGetPropertiesWithAnnotationExport() ); $this->assertInternalType('array', $result); - $this->assertCount(5, $result); + $this->assertCount(6, $result); $this->assertSame( - array('foo', 'baz', 'qux', 'quux', 'annotatedPropertyWithoutMethod'), + array('foo', 'baz', 'qux', 'quux', 'quuz', 'annotatedPropertyWithoutMethod'), array_keys($result) ); $this->assertInstanceOf(Export::class, current($result)); @@ -108,11 +109,12 @@ public function testGetPropertiesWithAnnotationForExtendedClass() ); $this->assertInternalType('array', $result); - $this->assertCount(6, $result); + $this->assertCount(7, $result); $this->assertSame( - array('extendedProperty', 'foo', 'baz', 'qux', 'quux', 'annotatedPropertyWithoutMethod'), + array('extendedProperty', 'foo', 'baz', 'qux', 'quux', 'quuz', 'annotatedPropertyWithoutMethod'), array_keys($result) ); + $this->assertInstanceOf(Export::class, current($result)); } } diff --git a/Tests/Resources/json/annotation_normalize_result.json b/Tests/Resources/json/annotation_normalize_result.json new file mode 100644 index 0000000..7b3713d --- /dev/null +++ b/Tests/Resources/json/annotation_normalize_result.json @@ -0,0 +1 @@ +{"foo":"bar","baz":1,"qux":[],"quuxs":[{"foo":"bar","baz":1,"qux":[],"quuxs":[],"quuz":"2016-01-01T00:00:00+00:00","annotatedPropertyWithoutMethod":"Yes"},{"foo":"bar","baz":1,"qux":[],"quuxs":[],"quuz":"2016-01-01T00:00:00+00:00","annotatedPropertyWithoutMethod":"Yes"}],"quuz":"2016-01-01T00:00:00+00:00","annotatedPropertyWithoutMethod":"Yes"} \ No newline at end of file diff --git a/Tests/Resources/json/iterable_normalize_result.json b/Tests/Resources/json/iterable_normalize_result.json new file mode 100644 index 0000000..4e73407 --- /dev/null +++ b/Tests/Resources/json/iterable_normalize_result.json @@ -0,0 +1 @@ +["bar",1,[["2020-01-01T00:00:00+00:00","2020-01-01T00:00:00+00:00"]]] \ No newline at end of file diff --git a/Tests/Resources/xml/annotation_normalizer_result.xml b/Tests/Resources/xml/annotation_normalizer_result.xml index 1114392..2db23dc 100644 --- a/Tests/Resources/xml/annotation_normalizer_result.xml +++ b/Tests/Resources/xml/annotation_normalizer_result.xml @@ -1,2 +1,2 @@ -bar1bar1YesYes +bar1bar12016-01-01T00:00:00+00:00Yesbar12016-01-01T00:00:00+00:00Yes2016-01-01T00:00:00+00:00Yes diff --git a/Tests/Serializer/Normalizer/AnnotationNormalizerTest.php b/Tests/Serializer/Normalizer/AnnotationNormalizerTest.php index 031ad19..58d2ee2 100644 --- a/Tests/Serializer/Normalizer/AnnotationNormalizerTest.php +++ b/Tests/Serializer/Normalizer/AnnotationNormalizerTest.php @@ -12,22 +12,25 @@ namespace Superbrave\GdprBundle\Tests\Serializer\Normalizer; -use Doctrine\Common\Collections\ArrayCollection; use PHPUnit_Framework_MockObject_MockObject; use ReflectionClass; use Superbrave\GdprBundle\Annotation\AnnotationReader; use Superbrave\GdprBundle\Annotation\Export; use Superbrave\GdprBundle\Manipulator\PropertyManipulator; use Superbrave\GdprBundle\Serializer\Normalizer\AnnotationNormalizer; +use Superbrave\GdprBundle\Serializer\Normalizer\IterableNormalizer; use Superbrave\GdprBundle\Tests\AnnotatedMock; use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Serializer; /** * AnnotationNormalizerTest. * * @author Niels Nijens + * @author Jelle van Oosterbosch */ class AnnotationNormalizerTest extends \PHPUnit_Framework_TestCase { @@ -153,6 +156,12 @@ public function testNormalize() ); $normalizer = new AnnotationNormalizer($annotationReader, Export::class, $propertyManipulator); + $serializer = new Serializer([ + new DateTimeNormalizer(), + new IterableNormalizer(), + $normalizer, + ]); + $normalizer->setNormalizer($serializer); $annotatedMock = new AnnotatedMock(); @@ -160,8 +169,9 @@ public function testNormalize() array( 'foo' => 'bar', 'baz' => 1, - 'qux' => array(), - 'quuxs' => new ArrayCollection(), + 'qux' => [], + 'quuxs' => [], + 'quuz' => '2016-01-01T00:00:00+00:00', 'annotatedPropertyWithoutMethod' => 'Yes', ), $normalizer->normalize($annotatedMock) @@ -169,31 +179,68 @@ public function testNormalize() } /** - * Tests if AnnotationNormalizer::normalize returns the expected normalized data + * Tests if @see AnnotationNormalizer::normalize returns the expected xml normalized data * for serialization through the Serializer. * * @return void */ - public function testNormalizeThroughSerializer() + public function testNormalizeThroughXmlSerializer() { $annotationReader = new AnnotationReader(); $propertyManipulator = new PropertyManipulator( PropertyAccess::createPropertyAccessor() ); - $normalizer = new AnnotationNormalizer($annotationReader, Export::class, $propertyManipulator); - $encoder = new XmlEncoder('mock'); + $normalizers = [ + new DateTimeNormalizer(), + new IterableNormalizer(), + new AnnotationNormalizer($annotationReader, Export::class, $propertyManipulator), + ]; + $encoders = [new XmlEncoder('mock')]; $serializer = new Serializer( - array($normalizer), - array($encoder) + $normalizers, + $encoders ); $data = new AnnotatedMock(new AnnotatedMock()); $this->assertStringEqualsFile( - __DIR__.'/../../Resources/xml/annotation_normalizer_result.xml', + __DIR__ . '/../../Resources/xml/annotation_normalizer_result.xml', $serializer->serialize($data, 'xml') ); } + + /** + * Test if @see AnnotationNormalizer::normalize returns the expected json normalized data + * for serialization through the Serializer. + * + * @return void + */ + public function testNormalizeThroughJsonSerializer() + { + $annotationReader = new AnnotationReader(); + $propertyManipulator = new PropertyManipulator( + PropertyAccess::createPropertyAccessor() + ); + + $normalizers = [ + new DateTimeNormalizer(), + new IterableNormalizer(), + new AnnotationNormalizer($annotationReader, Export::class, $propertyManipulator), + ]; + $encoders = [new JsonEncoder()]; + + $serializer = new Serializer( + $normalizers, + $encoders + ); + + $data = new AnnotatedMock(new AnnotatedMock()); + + $this->assertStringEqualsFile( + __DIR__ . '/../../Resources/json/annotation_normalize_result.json', + $serializer->serialize($data, 'json') + ); + } } diff --git a/Tests/Serializer/Normalizer/IterableNormalizerTest.php b/Tests/Serializer/Normalizer/IterableNormalizerTest.php new file mode 100644 index 0000000..4e7a26c --- /dev/null +++ b/Tests/Serializer/Normalizer/IterableNormalizerTest.php @@ -0,0 +1,126 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace Superbrave\GdprBundle\Tests\Serializer\Normalizer; + +use Doctrine\Common\Collections\ArrayCollection; +use Superbrave\GdprBundle\Serializer\Normalizer\IterableNormalizer; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; +use Symfony\Component\Serializer\Serializer; + +/** + * AnnotationNormalizerTest. + * + * @author Jelle van Oosterbosch + */ +class IterableNormalizerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var IterableNormalizer + */ + private $normalizer; + + /** + * @var Serializer + */ + private $serializer; + + /** + * {@inheritdoc} + */ + public function setUp() + { + $this->normalizer = new IterableNormalizer(); + $this->serializer = new Serializer([ + new DateTimeNormalizer(), + $this->normalizer, + ], [ + new JsonEncoder() + ]); + + $this->normalizer->setNormalizer($this->serializer); + } + + /** + * Tests if @see IterableNormalizer::supportsNormalization() returns false + * when the data is not iterable. + * + * @return void + */ + public function testSupportsNormalizationReturnsFalseWhenDataIsNotIterable() + { + $this->assertFalse($this->normalizer->supportsNormalization('no object')); + $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass())); + } + + /** + * Tests if @see IterableNormalizer::supportsNormalization() returns true + * when the data is iterable. + * + * @return void + */ + public function testSupportsNormalizationReturnsTrueWhenDataIsIterable() + { + $this->assertTrue($this->normalizer->supportsNormalization(array())); + $this->assertTrue($this->normalizer->supportsNormalization(new ArrayCollection())); + } + + /** + * Tests if @see IterableNormalizer::$normalizer returns the expected array of a iterable object. + * + * @return void + * + * @throws \Exception + */ + public function testNormalize() + { + $collection = new ArrayCollection(); + $collection->add(new \DateTime('2020/01/01')); + $collection->add(new \DateTime('2020/01/01')); + + $this->assertEquals( + [ + '2020-01-01T00:00:00+00:00', + '2020-01-01T00:00:00+00:00', + ], + $this->normalizer->normalize($collection) + ); + } + + /** + * Test is @see IterableNormalizer::$normalizer returns the expected json normalized data + * for serialization through the Serializer. + * + * @return void + * + * @throws \Exception + */ + public function testNormalizeThroughJsonSerializer() + { + $data = [ + 'foo' => 'bar', + 'baz' => 1, + 'qux' => [ + new ArrayCollection([ + new \DateTime('2020/01/01'), + new \DateTime('2020/01/01'), + ]) + ] + ]; + + $this->assertStringEqualsFile( + __DIR__ . '/../../Resources/json/iterable_normalize_result.json', + $this->serializer->serialize($data, 'json') + ); + } +}