Repository: JsonMapper/JsonMapper (php)

Issue:
Add debug middleware
Currently there is no easy way to debug the results of some middleware

A new debugger middleware that takes an PSR-3 compatible logger and is able to dump the state of the JSON object and/or the state of the property map would be very helpful during development processes.

Up until now I've been using the unit tests and breakpoints during the debug runs to see the states.

Pull request description:
Add Debugger middleware; Resolves #15

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c92b3f1..d91d7b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
+### Added
+- New Debugger middleware to help debug the in between middleware
 
 ## [0.3.0] - 2020-04-13
 ### Added 
diff --git a/composer.json b/composer.json
index e899a91..d8d32e4 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,8 @@
         "php": "^7.2 || ^8.0",
         "ext-json": "*",
         "myclabs/php-enum": "^1.7",
-        "nikic/php-parser": "^4.3"
+        "nikic/php-parser": "^4.3",
+        "psr/log": "^1.1"
     },
     "require-dev": {
         "squizlabs/php_codesniffer": "^3.5",
diff --git a/src/Middleware/Debugger.php b/src/Middleware/Debugger.php
new file mode 100644
index 0000000..3a6f3bf
--- /dev/null
+++ b/src/Middleware/Debugger.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace JsonMapper\Middleware;
+
+use JsonMapper\JsonMapperInterface;
+use JsonMapper\ValueObjects\PropertyMap;
+use JsonMapper\Wrapper\ObjectWrapper;
+use Psr\Log\LoggerInterface;
+
+class Debugger extends AbstractMiddleware
+{
+    /** @var LoggerInterface */
+    private $logger;
+
+    public function __construct(LoggerInterface $logger)
+    {
+        $this->logger = $logger;
+    }
+
+    public function handle(\stdClass $json, ObjectWrapper $object, PropertyMap $map, JsonMapperInterface $mapper): void
+    {
+        $this->logger->debug(
+            'Current state attributes passed through JsonMapper middleware',
+            [
+                'json' => json_encode($json),
+                'object' => get_class($object->getObject()),
+                'propertyMap' => $map->toString()
+            ]
+        );
+    }
+}
diff --git a/src/ValueObjects/Property.php b/src/ValueObjects/Property.php
index 8eacdb5..197a448 100644
--- a/src/ValueObjects/Property.php
+++ b/src/ValueObjects/Property.php
@@ -7,7 +7,7 @@ namespace JsonMapper\ValueObjects;
 use JsonMapper\Builders\PropertyBuilder;
 use JsonMapper\Enums\Visibility;
 
-class Property
+class Property implements \JsonSerializable
 {
     /** @var string */
     private $name;
@@ -54,4 +54,14 @@ class Property
             ->setIsNullable($this->isNullable)
             ->setVisibility($this->visibility);
     }
+
+    public function jsonSerialize(): array
+    {
+        return [
+            'name' => $this->name,
+            'type' => $this->type,
+            'isNullable' => $this->isNullable,
+            'visibility' => $this->visibility,
+        ];
+    }
 }
diff --git a/src/ValueObjects/PropertyMap.php b/src/ValueObjects/PropertyMap.php
index e0f7b0c..713e0de 100644
--- a/src/ValueObjects/PropertyMap.php
+++ b/src/ValueObjects/PropertyMap.php
@@ -4,9 +4,7 @@ declare(strict_types=1);
 
 namespace JsonMapper\ValueObjects;
 
-use IteratorAggregate;
-
-class PropertyMap implements IteratorAggregate
+class PropertyMap implements \IteratorAggregate, \JsonSerializable
 {
     /** @var Property[] */
     private $map = [];
@@ -37,4 +35,16 @@ class PropertyMap implements IteratorAggregate
     {
         return new \ArrayIterator($this->map);
     }
+
+    public function jsonSerialize(): array
+    {
+        return [
+            'properties' => $this->map,
+        ];
+    }
+
+    public function toString(): string
+    {
+        return (string) json_encode($this);
+    }
 }


Test patch:
diff --git a/tests/Integration/Middleware/DebuggerTest.php b/tests/Integration/Middleware/DebuggerTest.php
new file mode 100644
index 0000000..43cd0dd
--- /dev/null
+++ b/tests/Integration/Middleware/DebuggerTest.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace JsonMapper\Tests\Integration\Middleware;
+
+use JsonMapper\JsonMapperFactory;
+use JsonMapper\Middleware\Debugger;
+use JsonMapper\Tests\Implementation\ComplexObject;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\Test\TestLogger;
+
+class DebuggerTest extends TestCase
+{
+    public function testDebuggerLogsDetails(): void
+    {
+        $mapper = (new JsonMapperFactory())->default();
+        $logger = new TestLogger();
+        $mapper->push(new Debugger($logger));
+        $json = (object) ['User' => (object) ['Name' => __METHOD__]];
+        $object = new ComplexObject();
+
+        $mapper->mapObject($json, $object);
+
+        self::assertCount(1, $logger->records);
+        self::assertTrue($logger->hasDebug('Current state attributes passed through JsonMapper middleware'));
+    }
+}
diff --git a/tests/Unit/Middleware/DebuggerTest.php b/tests/Unit/Middleware/DebuggerTest.php
new file mode 100644
index 0000000..c0af715
--- /dev/null
+++ b/tests/Unit/Middleware/DebuggerTest.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+namespace JsonMapper\Tests\Unit\Middleware;
+
+use JsonMapper\Handler\PropertyMapper;
+use JsonMapper\JsonMapperInterface;
+use JsonMapper\Middleware\Debugger;
+use JsonMapper\Middleware\FinalCallback;
+use JsonMapper\Tests\Implementation\SimpleObject;
+use JsonMapper\ValueObjects\PropertyMap;
+use JsonMapper\Wrapper\ObjectWrapper;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\Test\TestLogger;
+
+class DebuggerTest extends TestCase
+{
+    /**
+     * @covers \JsonMapper\Middleware\Debugger
+     */
+    public function testLoggerIsInvoked(): void
+    {
+        $logger = new TestLogger();
+        $middleware = new Debugger($logger);
+        $object = new ObjectWrapper(new SimpleObject());
+        $function = $middleware->__invoke(new PropertyMapper());
+
+        $function(new \stdClass(), $object, new PropertyMap(), $this->createMock(JsonMapperInterface::class));
+
+        self::assertCount(1, $logger->records);
+        self::assertTrue($logger->hasDebug('Current state attributes passed through JsonMapper middleware'));
+    }
+}
diff --git a/tests/Unit/ValueObjects/PropertyMapTest.php b/tests/Unit/ValueObjects/PropertyMapTest.php
index af7726d..4f25b10 100644
--- a/tests/Unit/ValueObjects/PropertyMapTest.php
+++ b/tests/Unit/ValueObjects/PropertyMapTest.php
@@ -48,4 +48,38 @@ class PropertyMapTest extends TestCase
         self::assertCount(1, $iterator);
         self::assertSame($property, $iterator->current());
     }
+
+    /**
+     * @covers \JsonMapper\ValueObjects\PropertyMap
+     */
+    public function testCanBeConvertedToJson(): void
+    {
+        $map = new PropertyMap();
+        $map->addProperty(new Property('id', 'int', false, Visibility::PUBLIC()));
+
+        $mapAsJson = json_encode($map);
+
+        self::assertIsString($mapAsJson);
+        self::assertJsonStringEqualsJsonString(
+            '{"properties":{"id":{"name":"id","type":"int","isNullable":false,"visibility":"public"}}}',
+            (string) $mapAsJson
+        );
+    }
+
+    /**
+     * @covers \JsonMapper\ValueObjects\PropertyMap
+     */
+    public function testCanBeConvertedToString(): void
+    {
+        $map = new PropertyMap();
+        $map->addProperty(new Property('id', 'int', false, Visibility::PUBLIC()));
+
+        $mapAsString = $map->toString();
+
+        self::assertIsString($mapAsString);
+        self::assertJsonStringEqualsJsonString(
+            '{"properties":{"id":{"name":"id","type":"int","isNullable":false,"visibility":"public"}}}',
+            (string) $mapAsString
+        );
+    }
 }
diff --git a/tests/Unit/ValueObjects/PropertyTest.php b/tests/Unit/ValueObjects/PropertyTest.php
index 6e030d9..64b02e1 100644
--- a/tests/Unit/ValueObjects/PropertyTest.php
+++ b/tests/Unit/ValueObjects/PropertyTest.php
@@ -33,4 +33,20 @@ class PropertyTest extends TestCase
 
         self::assertEquals($property, $builder->build());
     }
+
+    /**
+     * @covers \JsonMapper\ValueObjects\Property
+     */
+    public function testCanBeConvertedToJson(): void
+    {
+        $property = new Property('id', 'int', false, Visibility::PUBLIC());
+
+        $propertyAsJson = json_encode($property);
+
+        self::assertIsString($propertyAsJson);
+        self::assertJsonStringEqualsJsonString(
+            '{"name":"id","type":"int","isNullable":false,"visibility":"public"}',
+            (string) $propertyAsJson
+        );
+    }
 }