Skip to content

Commit

Permalink
Improve dernormalization
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Sep 26, 2024
1 parent 4d05533 commit ba8fd3b
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 146 deletions.
60 changes: 31 additions & 29 deletions src/Serializer/CallbackCasting.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public function setOptions(
}

try {
$this->callback = self::resolveSupportedType($this->type); /* @phpstan-ignore-line */
$this->callback = self::resolveTypeCallback($this->type); /* @phpstan-ignore-line */
$this->options = $options;

return;
Expand All @@ -85,31 +85,10 @@ public function setOptions(
$this->type = self::aliases()[$this->alias];
}

/** @var Closure $callback */
$callback = self::resolveAliasCallback($this->type);
$this->callback = $callback;
$this->callback = self::resolveAliasCallback($this->type);
$this->options = $options;
}

private static function resolveAliasCallback(string $type): Closure
{
foreach (self::aliases() as $alias => $registeredType) {
if ($type === $registeredType) {
return self::$aliases[$registeredType][$alias];
}

try {
$reflType = new ReflectionClass($type); /* @phpstan-ignore-line */
if ($reflType->implementsInterface($registeredType)) {
return self::$aliases[$registeredType][$alias];
}
} catch (Throwable) {
}
}

throw new MappingFailed('The `'.$type.'` could not be resolved.');
}

/**
* @return TValue
*/
Expand Down Expand Up @@ -202,8 +181,8 @@ public static function unregisterAliases(): void

public static function unregisterAll(): void
{
self::$types = [];
self::$aliases = [];
self::unregisterTypes();
self::unregisterAliases();
}

public static function supportsAlias(?string $alias): bool
Expand All @@ -218,7 +197,7 @@ public static function supportsType(?string $type): bool
}

try {
self::resolveSupportedType($type); /* @phpstan-ignore-line */
self::resolveTypeCallback($type); /* @phpstan-ignore-line */

return true;
} catch (Throwable) {
Expand Down Expand Up @@ -258,8 +237,12 @@ public static function supports(ReflectionParameter|ReflectionProperty $reflecti

foreach ($propertyTypeList as $propertyType) {
$type = $propertyType->getName();
if (null === $alias && self::supportsType($type)) {
return true;
if (null === $alias) {
if (self::supportsType($type)) {
return true;
}

continue;
}

if (self::isAliasValid($type) || (Type::Mixed->value === $type && self::supportsAlias($alias))) {
Expand Down Expand Up @@ -292,7 +275,7 @@ private static function isAliasValid(string $type): bool
/**
* @param class-string $type
*/
private static function resolveSupportedType(string $type): Closure
private static function resolveTypeCallback(string $type): Closure
{
foreach (self::$types as $registeredType => $callback) {
if ($type === $registeredType) {
Expand All @@ -311,6 +294,25 @@ private static function resolveSupportedType(string $type): Closure
throw new MappingFailed('The `'.$type.'` could not be resolved.');
}

private static function resolveAliasCallback(string $type): Closure
{
foreach (self::aliases() as $alias => $registeredType) {
if ($type === $registeredType) {
return self::$aliases[$registeredType][$alias];
}

try {
$reflType = new ReflectionClass($type); /* @phpstan-ignore-line */
if ($reflType->implementsInterface($registeredType)) {
return self::$aliases[$registeredType][$alias];
}
} catch (Throwable) {
}
}

throw new MappingFailed('The `'.$type.'` could not be resolved.');
}

/**
* @throws MappingFailed
*
Expand Down
78 changes: 52 additions & 26 deletions src/Serializer/CastToEnumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ final class CastToEnumTest extends TestCase
{
public function testItCanConvertAStringBackedEnum(): void
{
$cast = new CastToEnum(new ReflectionProperty(EnumClass::class, 'colour'));
$class = new class () {
public Colour $colour;
};

$cast = new CastToEnum(new ReflectionProperty($class::class, 'colour'));
$orange = $cast->toVariable('orange');

self::assertInstanceOf(Colour::class, $orange);
Expand All @@ -34,7 +38,11 @@ public function testItCanConvertAStringBackedEnum(): void

public function testItCanConvertAIntegerBackedEnum(): void
{
$cast = new CastToEnum(new ReflectionProperty(EnumClass::class, 'dayOfTheWeek'));
$class = new class () {
public DayOfTheWeek $dayOfTheWeek;
};

$cast = new CastToEnum(new ReflectionProperty($class::class, 'dayOfTheWeek'));
$monday = $cast->toVariable('1');

self::assertInstanceOf(DayOfTheWeek::class, $monday);
Expand All @@ -44,7 +52,11 @@ public function testItCanConvertAIntegerBackedEnum(): void

public function testItCanConvertAUnitEnum(): void
{
$cast = new CastToEnum(new ReflectionProperty(EnumClass::class, 'currency'));
$class = new class () {
public Currency $currency;
};

$cast = new CastToEnum(new ReflectionProperty($class::class, 'currency'));
$naira = $cast->toVariable('Naira');

self::assertInstanceOf(Currency::class, $naira);
Expand All @@ -53,14 +65,22 @@ public function testItCanConvertAUnitEnum(): void

public function testItReturnsNullWhenTheVariableIsNullable(): void
{
$cast = new CastToEnum(new ReflectionProperty(EnumClass::class, 'nullableCurrency'));
$class = new class () {
public ?Currency $nullableCurrency;
};

$cast = new CastToEnum(new ReflectionProperty($class::class, 'nullableCurrency'));

self::assertNull($cast->toVariable(null));
}

public function testItReturnsTheDefaultValueWhenTheVariableIsNullable(): void
{
$cast = new CastToEnum(new ReflectionProperty(EnumClass::class, 'nullableCurrency'));
$class = new class () {
public ?Currency $nullableCurrency;
};

$cast = new CastToEnum(new ReflectionProperty($class::class, 'nullableCurrency'));
$cast->setOptions('Naira');

self::assertSame(Currency::Naira, $cast->toVariable(null));
Expand All @@ -70,27 +90,39 @@ public function testThrowsOnNullIfTheVariableIsNotNullable(): void
{
$this->expectException(TypeCastingFailed::class);

(new CastToEnum(new ReflectionProperty(EnumClass::class, 'currency')))->toVariable(null);
$class = new class () {
public Currency $currency;
};

(new CastToEnum(new ReflectionProperty($class::class, 'currency')))->toVariable(null);
}

public function testThrowsIfTheValueIsNotRecognizedByTheEnum(): void
{
$this->expectException(TypeCastingFailed::class);

(new CastToEnum(new ReflectionProperty(EnumClass::class, 'colour')))->toVariable('green');
$class = new class () {
public Colour $colour;
};
(new CastToEnum(new ReflectionProperty($class::class, 'colour')))->toVariable('green');
}

public function testItReturnsTheDefaultValueWithUnionType(): void
{
$cast = new CastToEnum(new ReflectionProperty(EnumClass::class, 'unionType'));
$class = new class () {
public DateTimeInterface|Colour|null $unionType;
};
$cast = new CastToEnum(new ReflectionProperty($class::class, 'unionType'));
$cast->setOptions('orange');

self::assertSame(Colour::Violet, $cast->toVariable('violet'));
}

public function testItCanConvertABackedEnum(): void
{
$cast = new CastToEnum(new ReflectionProperty(EnumClass::class, 'colour'));
$class = new class () {
public Colour $colour;
};
$cast = new CastToEnum(new ReflectionProperty($class::class, 'colour'));
$orange = $cast->toVariable(Colour::Orange);

self::assertInstanceOf(Colour::class, $orange);
Expand All @@ -101,17 +133,23 @@ public function testItCanConvertABackedEnum(): void
public function testItWillThrowIfNotTheExpectedEnum(): void
{
$this->expectException(TypeCastingFailed::class);

$cast = new CastToEnum(new ReflectionProperty(EnumClass::class, 'colour'));
$class = new class () {
public Colour $colour;
};
$cast = new CastToEnum(new ReflectionProperty($class::class, 'colour'));
$cast->toVariable(DayOfTheWeek::Monday);
}

#[DataProvider('invalidPropertyName')]
public function testItWillThrowIfNotTypeAreSupported(string $propertyName): void
{
$this->expectException(MappingFailed::class);

$reflectionProperty = new ReflectionProperty(EnumClass::class, $propertyName);
$class = new class () {
public ?bool $nullableBool;
public DateTimeInterface|int $invalidUnionType;
public Countable&Traversable $intersectionType;
};
$reflectionProperty = new ReflectionProperty($class::class, $propertyName);

new CastToEnum($reflectionProperty);
}
Expand Down Expand Up @@ -144,15 +182,3 @@ enum Currency
case Euro;
case Naira;
}

class EnumClass
{
public DayOfTheWeek $dayOfTheWeek;
public Currency $currency;
public ?Currency $nullableCurrency;
public Colour $colour;
public ?bool $nullableBool;
public DateTimeInterface|Colour|null $unionType;
public DateTimeInterface|int $invalidUnionType;
public Countable&Traversable $intersectionType;
}
41 changes: 20 additions & 21 deletions src/Serializer/CastToFloatTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ public function testItFailsToInstantiateWithAnUnSupportedType(): void
{
$this->expectException(MappingFailed::class);

new CastToFloat(new ReflectionProperty(FloatClass::class, 'string'));
new CastToFloat(new ReflectionProperty((new class () {
public string $string;
})::class, 'string'));
}

#[DataProvider('providesValidStringForInt')]
Expand All @@ -42,64 +44,69 @@ public function testItCanConvertToArraygWithoutArguments(

public static function providesValidStringForInt(): iterable
{
$class = new class () {
public ?float $nullableFloat;
public DateTimeInterface|float|null $unionType;
};

yield 'positive integer' => [
'prototype' => new ReflectionProperty(FloatClass::class, 'nullableFloat'),
'prototype' => new ReflectionProperty($class::class, 'nullableFloat'),
'input' => '1',
'default' => null,
'expected' => 1.0,
];

yield 'zero' => [
'prototype' => new ReflectionProperty(FloatClass::class, 'nullableFloat'),
'prototype' => new ReflectionProperty($class::class, 'nullableFloat'),
'input' => '0',
'default' => null,
'expected' => 0.0,
];

yield 'negative integer' => [
'prototype' => new ReflectionProperty(FloatClass::class, 'nullableFloat'),
'prototype' => new ReflectionProperty($class::class, 'nullableFloat'),
'input' => '-10',
'default' => null,
'expected' => -10.0,
];

yield 'integer type' => [
'prototype' => new ReflectionProperty(FloatClass::class, 'nullableFloat'),
'prototype' => new ReflectionProperty($class::class, 'nullableFloat'),
'input' => -10,
'default' => null,
'expected' => -10.0,
];

yield 'float type' => [
'prototype' => new ReflectionProperty(FloatClass::class, 'nullableFloat'),
'prototype' => new ReflectionProperty($class::class, 'nullableFloat'),
'input' => -10.0,
'default' => null,
'expected' => -10.0,
];

yield 'null value' => [
'prototype' => new ReflectionProperty(FloatClass::class, 'nullableFloat'),
'prototype' => new ReflectionProperty($class::class, 'nullableFloat'),
'input' => null,
'default' => null,
'expected' => null,
];

yield 'null value with default value' => [
'prototype' => new ReflectionProperty(FloatClass::class, 'nullableFloat'),
'prototype' => new ReflectionProperty($class::class, 'nullableFloat'),
'input' => null,
'default' => 10,
'expected' => 10.0,
];

yield 'with union type' => [
'prototype' => new ReflectionProperty(FloatClass::class, 'unionType'),
'prototype' => new ReflectionProperty($class::class, 'unionType'),
'input' => '23',
'default' => 42.0,
'expected' => 23.0,
];

yield 'with nullable union type' => [
'prototype' => new ReflectionProperty(FloatClass::class, 'unionType'),
'prototype' => new ReflectionProperty($class::class, 'unionType'),
'input' => null,
'default' => 42.0,
'expected' => 42.0,
Expand All @@ -110,16 +117,8 @@ public function testItFailsToConvertNonIntegerString(): void
{
$this->expectException(TypeCastingFailed::class);

(new CastToFloat(new ReflectionProperty(FloatClass::class, 'nullableFloat')))->toVariable('00foobar');
(new CastToFloat(new ReflectionProperty((new class () {
public ?float $nullableFloat;
})::class, 'nullableFloat')))->toVariable('00foobar');
}
}

class FloatClass
{
public float $float;
public ?float $nullableFloat;
public mixed $mixed;
public int $int;
public string $string;
public DateTimeInterface|float|null $unionType;
}
Loading

0 comments on commit ba8fd3b

Please sign in to comment.