Skip to content

Commit

Permalink
Improve alias usage with dernormalization
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Sep 27, 2024
1 parent a9df61a commit 9e6ba1d
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 160 deletions.
31 changes: 3 additions & 28 deletions src/Serializer/AfterMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
use Attribute;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;

/**
* @deprecated since version 9.17.0
Expand All @@ -27,36 +25,13 @@
#[Attribute(Attribute::TARGET_CLASS)]
final class AfterMapping
{
/** @var array<string> $methods */
public readonly MapRecord $mapRecord;
public readonly array $methods;

public function __construct(string ...$methods)
{
$this->methods = $methods;
}

/**
*
* @return array<ReflectionMethod>
*/
public function afterMappingMethods(ReflectionClass $class): array
{
$methods = [];
foreach ($this->methods as $method) {
try {
$accessor = $class->getMethod($method);
} catch (ReflectionException $exception) {
throw new MappingFailed('The method `'.$method.'` is not defined on the `'.$class->getName().'` class.', 0, $exception);
}

if (0 !== $accessor->getNumberOfRequiredParameters()) {
throw new MappingFailed('The method `'.$class->getName().'::'.$accessor->getName().'` has too many required parameters.');
}

$methods[] = $accessor;
}

return $methods;
$this->mapRecord = new MapRecord($methods);
$this->methods = $this->mapRecord->afterMapping;
}

public static function from(ReflectionClass $class): ?self
Expand Down
67 changes: 49 additions & 18 deletions src/Serializer/CallbackCasting.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,15 @@ public function __construct(
/**
* @throws MappingFailed
*/
public function setOptions(
?string $type = null,
mixed ...$options
): void {
public function setOptions(?string $type = null, mixed ...$options): void
{
if (null === $this->alias) {
if (Type::Mixed->value === $this->type && null !== $type) {
$this->type = $type;
}

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,9 +83,7 @@ public function setOptions(
$this->type = self::aliases()[$this->alias];
}

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

Expand Down Expand Up @@ -183,8 +179,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 @@ -199,7 +195,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 @@ -247,18 +243,36 @@ public static function supports(ReflectionParameter|ReflectionProperty $reflecti
continue;
}

if ((self::aliases()[$alias] ?? null) === $type || (Type::Mixed->value === $type && self::supportsAlias($alias))) {
if (self::aliasSupportsType($type) || (Type::Mixed->value === $type && self::supportsAlias($alias))) {
return true;
}
}

return false;
}

private static function aliasSupportsType(string $type): bool
{
foreach (self::aliases() as $registeredType) {
if ($type === $registeredType) {
return true;
}

try {
if ((new ReflectionClass($type))->implementsInterface($registeredType)) { /* @phpstan-ignore-line */
return true;
}
} catch (Throwable) {
}
}

return false;
}

/**
* @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 @@ -277,6 +291,25 @@ private static function resolveSupportedType(string $type): Closure
throw new MappingFailed('The `'.$type.'` could not be resolved.');
}

private static function resolveAliasCallback(string $type, string $alias): Closure
{
$rType = self::aliases()[$alias] ?? null;
if (isset($rType)) {
return self::$aliases[$rType][$alias];
}

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

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

/**
* @throws MappingFailed
Expand All @@ -300,14 +333,12 @@ private static function resolve(ReflectionParameter|ReflectionProperty $reflecti
}

if (null === $type) {
if (
self::supportsType($foundType->getName())
|| array_key_exists($foundType->getName(), self::$aliases)
) {
$instanceName = $foundType->getName();
if (self::supportsType($instanceName) || array_key_exists($instanceName, self::$aliases)) {
$type = $foundType;
}

if (true !== $hasMixed && Type::Mixed->value === $foundType->getName()) {
if (true !== $hasMixed && Type::Mixed->value === $instanceName) {
$hasMixed = true;
}
}
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;
}
Loading

0 comments on commit 9e6ba1d

Please sign in to comment.