From 2cf55d514bd62c37f93853641704e70c2968b007 Mon Sep 17 00:00:00 2001 From: Anatoly Nekhay Date: Fri, 30 Jul 2021 15:28:59 +0300 Subject: [PATCH] support for json type --- src/Hydrator/Hydrator.php | 48 +++++++++++++ src/Hydrator/Json.php | 79 ++++++++++++++++++++++ tests/Hydrator/Fixture/TestJsonTypeDto.php | 13 ++++ tests/Hydrator/HydratorTest.php | 79 +++++++++++++++++++++- 4 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 src/Hydrator/Json.php create mode 100644 tests/Hydrator/Fixture/TestJsonTypeDto.php diff --git a/src/Hydrator/Hydrator.php b/src/Hydrator/Hydrator.php index d1edf8f..3204a78 100644 --- a/src/Hydrator/Hydrator.php +++ b/src/Hydrator/Hydrator.php @@ -167,6 +167,11 @@ private function hydrateProperty( return; } + if (Json::class === $type->getName()) { + $this->hydratePropertyWithJson($object, $class, $property, $type, $value); + return; + } + if ('array' === $type->getName() || is_subclass_of($type->getName(), ArrayAccess::class)) { $this->hydratePropertyWithArray($object, $class, $property, $type, $value); return; @@ -280,6 +285,49 @@ private function hydratePropertyWithScalar( } } + /** + * Hydrates the given property with the given json value + * + * @param HydrableObjectInterface $object + * @param ReflectionClass $class + * @param ReflectionProperty $property + * @param ReflectionNamedType $type + * @param mixed $value + * + * @return void + * + * @throws Exception\InvalidValueException + * If the given value isn't valid. + */ + private function hydratePropertyWithJson( + HydrableObjectInterface $object, + ReflectionClass $class, + ReflectionProperty $property, + ReflectionNamedType $type, + $value + ) : void { + if (!is_string($value)) { + throw new Exception\InvalidValueException(sprintf( + 'The <%s.%s> property only accepts a string.', + $class->getShortName(), + $property->getName(), + )); + } + + json_decode(''); // reset previous error... + $value = (array) json_decode($value, true); + if (JSON_ERROR_NONE <> json_last_error()) { + throw new Exception\InvalidValueException(sprintf( + 'The <%s.%s> property only accepts valid JSON data (%s).', + $class->getShortName(), + $property->getName(), + json_last_error_msg(), + )); + } + + $property->setValue($object, new Json($value)); + } + /** * Hydrates the given property with the given array value * diff --git a/src/Hydrator/Json.php b/src/Hydrator/Json.php new file mode 100644 index 0000000..403125f --- /dev/null +++ b/src/Hydrator/Json.php @@ -0,0 +1,79 @@ + + * @copyright Copyright (c) 2021, Anatoly Fenric + * @license https://github.com/sunrise-php/hydrator/blob/master/LICENSE + * @link https://github.com/sunrise-php/hydrator + */ + +namespace Sunrise\Hydrator; + +/** + * Import classes + */ +use ArrayAccess; +use JsonSerializable; + +/** + * Import functions + */ +use function array_key_exists; + +/** + * Json + */ +class Json implements ArrayAccess, JsonSerializable +{ + protected array $data; + + /** + * Constructor of the class + */ + public function __construct(array $data) + { + $this->data = $data; + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) : bool + { + return array_key_exists($offset, $this->data); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return $this->data[$offset] ?? null; + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) : void + { + $this->data[$offset] = $value; + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) : void + { + unset($this->data[$offset]); + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : array + { + return $this->data; + } +} diff --git a/tests/Hydrator/Fixture/TestJsonTypeDto.php b/tests/Hydrator/Fixture/TestJsonTypeDto.php new file mode 100644 index 0000000..dc7b582 --- /dev/null +++ b/tests/Hydrator/Fixture/TestJsonTypeDto.php @@ -0,0 +1,13 @@ +hydrate(new Fixture\TestJsonTypeDto(), ['json' => '[]']); + + $this->assertFalse(isset($object->json['foo'])); + $this->assertNull($object->json['foo']); + + $object->json['foo'] = 1; + $this->assertTrue(isset($object->json['foo'])); + $this->assertSame(1, $object->json['foo']); + + unset($object->json['foo']); + $this->assertFalse(isset($object->json['foo'])); + $this->assertNull($object->json['foo']); + } + + /** + * @return void + */ + public function testJsonTypeJsonSerialize() : void + { + $object = (new Hydrator)->hydrate(new Fixture\TestJsonTypeDto(), ['json' => '[]']); + + $object->json['foo'] = 1; + $object->json['bar'] = 2; + + $this->assertSame([ + 'foo' => 1, + 'bar' => 2, + ], $object->json->jsonSerialize()); + } + + /** + * @return void + */ + public function testJsonTypeDeserializeJson() : void + { + $json = '{"foo":1,"bar":2,"baz":{"qux":3}}'; + + $object = (new Hydrator)->hydrate(new Fixture\TestJsonTypeDto(), ['json' => $json]); + + $this->assertSame(1, $object->json['foo']); + $this->assertSame(2, $object->json['bar']); + $this->assertSame(3, $object->json['baz']['qux']); + } + + /** + * @return void + */ + public function testJsonTypeInvalidJsonSyntax() : void + { + $this->expectException(Exception\InvalidValueException::class); + $this->expectExceptionMessage( + 'The property only accepts valid JSON data (Syntax error).' + ); + + (new Hydrator)->hydrate(new Fixture\TestJsonTypeDto(), ['json' => '{']); + } + + /** + * @return void + */ + public function testJsonTypeInvalidJsonType() : void + { + $this->expectException(Exception\InvalidValueException::class); + $this->expectExceptionMessage('The property only accepts a string.'); + + (new Hydrator)->hydrate(new Fixture\TestJsonTypeDto(), ['json' => []]); + } + + /** + * @return void + */ + public function testJsonableObjectDeserializeJson() : void { $json = '{"foo":"foo:value","bar":"bar:value"}'; @@ -99,7 +172,7 @@ public function testJson() : void /** * @return void */ - public function testInvalidSyntaxJson() : void + public function testJsonableObjectInvalidJsonSyntax() : void { $this->expectException(Exception\InvalidValueException::class); $this->expectExceptionMessage('The property only accepts valid JSON data (Syntax error).'); @@ -110,7 +183,7 @@ public function testInvalidSyntaxJson() : void /** * @return void */ - public function testInvalidJsonType() : void + public function testJsonableObjectInvalidJsonType() : void { $this->expectException(Exception\InvalidValueException::class); $this->expectExceptionMessage('The property only accepts a string.');