Skip to content

Commit

Permalink
fix: prevent cache corruption when normalizing and mapping to enum
Browse files Browse the repository at this point in the history
  • Loading branch information
romm committed Sep 18, 2024
1 parent 546c458 commit e695b29
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 3 deletions.
9 changes: 6 additions & 3 deletions src/Normalizer/Transformer/RecursiveTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use CuyZ\Valinor\Normalizer\AsTransformer;
use CuyZ\Valinor\Normalizer\Exception\CircularReferenceFoundDuringNormalization;
use CuyZ\Valinor\Normalizer\Exception\TypeUnhandledByNormalizer;
use CuyZ\Valinor\Type\Types\EnumType;
use CuyZ\Valinor\Type\Types\NativeClassType;
use DateTimeInterface;
use DateTimeZone;
Expand Down Expand Up @@ -63,10 +64,12 @@ private function doTransform(mixed $value, WeakMap $references, array $attribute

// @infection-ignore-all
$references[$value] = true;
}

if (is_object($value)) {
$classAttributes = $this->classDefinitionRepository->for(new NativeClassType($value::class))->attributes;
$type = $value instanceof UnitEnum
? EnumType::native($value::class)
: new NativeClassType($value::class);

$classAttributes = $this->classDefinitionRepository->for($type)->attributes;
$classAttributes = $this->filterAttributes($classAttributes);

$attributes = [...$attributes, ...$classAttributes];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Tests\Integration\NonRegression;

use CuyZ\Valinor\Normalizer\Format;
use CuyZ\Valinor\Tests\Fixture\Enum\BackedStringEnum;
use CuyZ\Valinor\Tests\Integration\IntegrationTestCase;

final class NormalizeEnumDoesNotBreakMapperTest extends IntegrationTestCase
{
/**
* The normalizer will at some point fetch the class definition of the enum,
* we need to ensure an `EnumType` is used and not a `NativeClassType`,
* otherwise the cache would be corrupted for further usage.
*
* @see https://github.com/CuyZ/Valinor/issues/562
*/
public function test_normalizing_enum_and_then_map_value_on_same_enum_class_does_not_break(): void
{
$mapperBuilder = $this->mapperBuilder();

$mapperBuilder->normalizer(Format::array())->normalize(BackedStringEnum::FOO);

$result = $mapperBuilder->mapper()->map(BackedStringEnum::class, 'foo');

self::assertSame(BackedStringEnum::FOO, $result);
}
}
26 changes: 26 additions & 0 deletions tests/Integration/Normalizer/NormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,16 @@ public static function normalize_basic_values_yields_expected_output_data_provid
'expected json' => '42',
];

yield 'enum with transformer attribute' => [
'input' => SomeEnumWithTransformerAttribute::FOO,
'expected array' => 'normalizedValue-foo',
'expected json' => '"normalizedValue-foo"',
'transformers' => [],
'transformerAttributes' => [
TransformEnumToString::class,
],
];

yield 'class with public properties' => [
'input' => new class () {
public string $string = 'foo';
Expand Down Expand Up @@ -1381,3 +1391,19 @@ public function __construct(

#[TransformObjectToString]
final class SomeClassWithAttributeToTransformObjectToString {}

#[Attribute(Attribute::TARGET_CLASS)]
final class TransformEnumToString
{
public function normalize(SomeEnumWithTransformerAttribute $enum): string
{
return 'normalizedValue-' . $enum->value;
}
}

#[TransformEnumToString]
enum SomeEnumWithTransformerAttribute: string
{
case FOO = 'foo';
case BAR = 'bar';
}

0 comments on commit e695b29

Please sign in to comment.