Skip to content

Commit

Permalink
Merge pull request #31 from sunrise-php/release/v3.4.0
Browse files Browse the repository at this point in the history
v3.4.0
  • Loading branch information
fenric authored Oct 11, 2023
2 parents 9f35d8b + 961cef1 commit 57da41a
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 25 deletions.
26 changes: 1 addition & 25 deletions src/Hydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,12 @@
use JsonException;
use LogicException;
use ReflectionClass;
use ReflectionNamedType;
use ReflectionProperty;
use Sunrise\Hydrator\Annotation\Alias;
use Sunrise\Hydrator\Annotation\Context;
use Sunrise\Hydrator\Annotation\Ignore;
use Sunrise\Hydrator\AnnotationReader\BuiltinAnnotationReader;
use Sunrise\Hydrator\AnnotationReader\DoctrineAnnotationReader;
use Sunrise\Hydrator\AnnotationReader\NullAnnotationReader;
use Sunrise\Hydrator\Dictionary\BuiltinType;
use Sunrise\Hydrator\Exception\InvalidDataException;
use Sunrise\Hydrator\Exception\InvalidObjectException;
use Sunrise\Hydrator\Exception\InvalidValueException;
Expand Down Expand Up @@ -237,7 +234,7 @@ public function hydrate($object, array $data, array $path = [], array $context =

try {
// phpcs:ignore Generic.Files.LineLength
$property->setValue($object, $this->castValue($data[$key], $this->getPropertyType($property), [...$path, $key], $context));
$property->setValue($object, $this->castValue($data[$key], Type::fromProperty($property), [...$path, $key], $context));
} catch (InvalidValueException $e) {
$violations[] = $e;
} catch (InvalidDataException $e) {
Expand Down Expand Up @@ -312,27 +309,6 @@ private function instantObject($object): array
return [$class->newInstanceWithoutConstructor(), $class];
}

/**
* Gets the given property's type
*
* @param ReflectionProperty $property
*
* @return Type
*/
private function getPropertyType(ReflectionProperty $property): Type
{
$type = $property->getType();
if ($type === null) {
return new Type($property, BuiltinType::MIXED, true);
}

if ($type instanceof ReflectionNamedType) {
return new Type($property, $type->getName(), $type->allowsNull());
}

return new Type($property, (string) $type, $type->allowsNull());
}

/**
* Gets default values from the given class's constructor
*
Expand Down
48 changes: 48 additions & 0 deletions src/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@

namespace Sunrise\Hydrator;

use ReflectionNamedType;
use ReflectionParameter;
use ReflectionProperty;
use Sunrise\Hydrator\Dictionary\BuiltinType;

/**
* @since 3.1.0
Expand Down Expand Up @@ -57,6 +59,52 @@ public function __construct($holder, string $name, bool $allowsNull)
$this->allowsNull = $allowsNull;
}

/**
* Creates a new type from the given property
*
* @param ReflectionProperty $property
*
* @return self
*
* @since 3.4.0
*/
public static function fromProperty(ReflectionProperty $property): self
{
$type = $property->getType();
if ($type === null) {
return new Type($property, BuiltinType::MIXED, true);
}

if ($type instanceof ReflectionNamedType) {
return new Type($property, $type->getName(), $type->allowsNull());
}

return new Type($property, (string) $type, $type->allowsNull());
}

/**
* Creates a new type from the given parameter
*
* @param ReflectionParameter $parameter
*
* @return self
*
* @since 3.4.0
*/
public static function fromParameter(ReflectionParameter $parameter): self
{
$type = $parameter->getType();
if ($type === null) {
return new Type($parameter, BuiltinType::MIXED, true);
}

if ($type instanceof ReflectionNamedType) {
return new Type($parameter, $type->getName(), $type->allowsNull());
}

return new Type($parameter, (string) $type, $type->allowsNull());
}

/**
* Gets the type holder
*
Expand Down
98 changes: 98 additions & 0 deletions tests/HydratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use ReflectionFunction;
use ReflectionIntersectionType;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionUnionType;
use Sunrise\Hydrator\Annotation\Alias;
use Sunrise\Hydrator\Annotation\Ignore;
use Sunrise\Hydrator\Annotation\Subtype;
Expand Down Expand Up @@ -2340,6 +2343,101 @@ public function testInvalidObjectExceptionUnsupportedFunctionParameterType(): vo
$this->assertSame('The parameter {foo($bar[0])} is associated with an unsupported type {mixed}.', $e->getMessage());
}

public function testTypeFromParameter(): void
{
$parameter = $this->createMock(ReflectionParameter::class);
$parameter->method('getType')->willReturn(null);
$type = Type::fromParameter($parameter);
$this->assertSame(BuiltinType::MIXED, $type->getName());
$this->assertTrue($type->allowsNull());

$namedType = $this->createMock(ReflectionNamedType::class);
$namedType->method('getName')->willReturn('foo');
$namedType->method('allowsNull')->willReturn(false);
$parameter = $this->createMock(ReflectionParameter::class);
$parameter->method('getType')->willReturn($namedType);
$type = Type::fromParameter($parameter);
$this->assertSame('foo', $type->getName());
$this->assertFalse($type->allowsNull());

$namedType = $this->createMock(ReflectionNamedType::class);
$namedType->method('getName')->willReturn('foo');
$namedType->method('allowsNull')->willReturn(true);
$parameter = $this->createMock(ReflectionParameter::class);
$parameter->method('getType')->willReturn($namedType);
$type = Type::fromParameter($parameter);
$this->assertSame('foo', $type->getName());
$this->assertTrue($type->allowsNull());

if (PHP_VERSION_ID < 80000) {
return;
}

$namedTypes = [];
$namedTypes[0] = $this->createMock(ReflectionNamedType::class);
$namedTypes[0]->method('getName')->willReturn('foo');
$namedTypes[1] = $this->createMock(ReflectionNamedType::class);
$namedTypes[1]->method('getName')->willReturn('bar');
$unionType = $this->createMock(ReflectionUnionType::class);
$unionType->method('getTypes')->willReturn($namedTypes);
$unionType->method('allowsNull')->willReturn(false);
$unionType->method('__toString')->willReturn('foo|bar');
$parameter = $this->createMock(ReflectionParameter::class);
$parameter->method('getType')->willReturn($unionType);
$type = Type::fromParameter($parameter);
$this->assertSame('foo|bar', $type->getName());
$this->assertFalse($type->allowsNull());

$namedTypes = [];
$namedTypes[0] = $this->createMock(ReflectionNamedType::class);
$namedTypes[0]->method('getName')->willReturn('foo');
$namedTypes[1] = $this->createMock(ReflectionNamedType::class);
$namedTypes[1]->method('getName')->willReturn('bar');
$unionType = $this->createMock(ReflectionUnionType::class);
$unionType->method('getTypes')->willReturn($namedTypes);
$unionType->method('allowsNull')->willReturn(true);
$unionType->method('__toString')->willReturn('foo|bar|null');
$parameter = $this->createMock(ReflectionParameter::class);
$parameter->method('getType')->willReturn($unionType);
$type = Type::fromParameter($parameter);
$this->assertSame('foo|bar|null', $type->getName());
$this->assertTrue($type->allowsNull());

if (PHP_VERSION_ID < 80100) {
return;
}

$namedTypes = [];
$namedTypes[0] = $this->createMock(ReflectionNamedType::class);
$namedTypes[0]->method('getName')->willReturn('foo');
$namedTypes[1] = $this->createMock(ReflectionNamedType::class);
$namedTypes[1]->method('getName')->willReturn('bar');
$intersectionType = $this->createMock(ReflectionIntersectionType::class);
$intersectionType->method('getTypes')->willReturn($namedTypes);
$intersectionType->method('allowsNull')->willReturn(false);
$intersectionType->method('__toString')->willReturn('foo&bar');
$parameter = $this->createMock(ReflectionParameter::class);
$parameter->method('getType')->willReturn($intersectionType);
$type = Type::fromParameter($parameter);
$this->assertSame('foo&bar', $type->getName());
$this->assertFalse($type->allowsNull());

$namedTypes = [];
$namedTypes[0] = $this->createMock(ReflectionNamedType::class);
$namedTypes[0]->method('getName')->willReturn('foo');
$namedTypes[1] = $this->createMock(ReflectionNamedType::class);
$namedTypes[1]->method('getName')->willReturn('bar');
$intersectionType = $this->createMock(ReflectionIntersectionType::class);
$intersectionType->method('getTypes')->willReturn($namedTypes);
$intersectionType->method('allowsNull')->willReturn(true);
$intersectionType->method('__toString')->willReturn('(foo&bar)|null');
$parameter = $this->createMock(ReflectionParameter::class);
$parameter->method('getType')->willReturn($intersectionType);
$type = Type::fromParameter($parameter);
$this->assertSame('(foo&bar)|null', $type->getName());
$this->assertTrue($type->allowsNull());
}

public function testHydrateStore(): void
{
$this->phpRequired('8.1');
Expand Down

0 comments on commit 57da41a

Please sign in to comment.