Skip to content

Commit c7292e2

Browse files
authored
fix(mapper): support casting bool, int, float and enums (#1414)
1 parent 1089f61 commit c7292e2

File tree

10 files changed

+130
-8
lines changed

10 files changed

+130
-8
lines changed

packages/mapper/src/CasterFactoryInitializer.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@
1414
use Tempest\DateTime\DateTime;
1515
use Tempest\DateTime\DateTimeInterface;
1616
use Tempest\Mapper\Casters\ArrayToObjectCollectionCaster;
17+
use Tempest\Mapper\Casters\BooleanCaster;
1718
use Tempest\Mapper\Casters\DateTimeCaster;
1819
use Tempest\Mapper\Casters\EnumCaster;
20+
use Tempest\Mapper\Casters\FloatCaster;
21+
use Tempest\Mapper\Casters\IntegerCaster;
1922
use Tempest\Mapper\Casters\JsonToArrayCaster;
2023
use Tempest\Mapper\Casters\NativeDateTimeCaster;
2124
use Tempest\Mapper\Casters\ObjectCaster;
2225
use Tempest\Reflection\PropertyReflector;
26+
use UnitEnum;
2327

2428
final class CasterFactoryInitializer implements Initializer
2529
{
@@ -28,9 +32,15 @@ public function initialize(Container $container): CasterFactory
2832
{
2933
return new CasterFactory()
3034
->addCaster('array', JsonToArrayCaster::class)
35+
->addCaster('bool', BooleanCaster::class)
36+
->addCaster('boolean', BooleanCaster::class)
37+
->addCaster('int', IntegerCaster::class)
38+
->addCaster('integer', IntegerCaster::class)
39+
->addCaster('float', FloatCaster::class)
40+
->addCaster('double', FloatCaster::class)
3141
->addCaster(fn (PropertyReflector $property) => $property->getIterableType() !== null, fn (PropertyReflector $property) => new ArrayToObjectCollectionCaster($property))
3242
->addCaster(fn (PropertyReflector $property) => $property->getType()->isClass(), fn (PropertyReflector $property) => new ObjectCaster($property->getType()))
33-
->addCaster(BackedEnum::class, fn (PropertyReflector $property) => new EnumCaster($property->getType()->getName()))
43+
->addCaster(UnitEnum::class, fn (PropertyReflector $property) => new EnumCaster($property->getType()->getName()))
3444
->addCaster(DateTimeInterface::class, DateTimeCaster::fromProperty(...))
3545
->addCaster(NativeDateTimeImmutable::class, NativeDateTimeCaster::fromProperty(...))
3646
->addCaster(NativeDateTime::class, NativeDateTimeCaster::fromProperty(...))

packages/mapper/src/Casters/BooleanCaster.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
{
1111
public function cast(mixed $input): bool
1212
{
13-
return boolval($input);
13+
if (is_string($input)) {
14+
$input = mb_strtolower($input);
15+
}
16+
17+
return match ($input) {
18+
1, '1', true, 'true', 'enabled', 'on', 'yes' => true,
19+
default => false,
20+
};
1421
}
1522
}

packages/mapper/src/Casters/EnumCaster.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,29 @@
55
namespace Tempest\Mapper\Casters;
66

77
use Tempest\Mapper\Caster;
8+
use UnitEnum;
89

910
final readonly class EnumCaster implements Caster
1011
{
12+
/**
13+
* @param class-string<UnitEnum> $enum
14+
*/
1115
public function __construct(
1216
private string $enum,
1317
) {}
1418

1519
public function cast(mixed $input): ?object
1620
{
17-
if ($input instanceof $this->enum) {
21+
if ($input === null) {
22+
return null;
23+
}
24+
25+
if (is_a($input, $this->enum)) {
1826
return $input;
1927
}
2028

21-
if ($input === null) {
22-
return null;
29+
if (defined("{$this->enum}::{$input}")) {
30+
return constant("{$this->enum}::{$input}");
2331
}
2432

2533
return forward_static_call("{$this->enum}::from", $input);

packages/mapper/src/SerializerFactoryInitializer.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
use Tempest\Mapper\Serializers\SerializableSerializer;
2929
use Tempest\Mapper\Serializers\StringSerializer;
3030
use Tempest\Reflection\PropertyReflector;
31-
use Tempest\Reflection\TypeReflector;
31+
use UnitEnum;
3232

3333
final class SerializerFactoryInitializer implements Initializer
3434
{
@@ -51,6 +51,7 @@ public function initialize(Container $container): SerializerFactory
5151
->addSerializer(Serializable::class, SerializableSerializer::class)
5252
->addSerializer(JsonSerializable::class, SerializableSerializer::class)
5353
->addSerializer(Stringable::class, StringSerializer::class)
54+
->addSerializer(UnitEnum::class, EnumSerializer::class)
5455
->addSerializer(BackedEnum::class, EnumSerializer::class)
5556
->addSerializer(DateTime::class, DateTimeSerializer::fromReflector(...))
5657
->addSerializer(
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Integration\Mapper;
4+
5+
use BackedEnum;
6+
use Tempest\Mapper\CasterFactory;
7+
use Tempest\Mapper\Casters\BooleanCaster;
8+
use Tempest\Mapper\Casters\DateTimeCaster;
9+
use Tempest\Mapper\Casters\EnumCaster;
10+
use Tempest\Mapper\Casters\FloatCaster;
11+
use Tempest\Mapper\Casters\IntegerCaster;
12+
use Tempest\Mapper\Casters\NativeDateTimeCaster;
13+
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
14+
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithSerializerProperties;
15+
use UnitEnum;
16+
17+
use function Tempest\reflect;
18+
19+
final class CasterFactoryTest extends FrameworkIntegrationTestCase
20+
{
21+
public function test_for_property(): void
22+
{
23+
$factory = $this->container->get(CasterFactory::class);
24+
$class = reflect(ObjectWithSerializerProperties::class);
25+
26+
$this->assertInstanceOf(IntegerCaster::class, $factory->forProperty($class->getProperty('intProp')));
27+
$this->assertInstanceOf(FloatCaster::class, $factory->forProperty($class->getProperty('floatProp')));
28+
$this->assertInstanceOf(BooleanCaster::class, $factory->forProperty($class->getProperty('boolProp')));
29+
$this->assertInstanceOf(NativeDateTimeCaster::class, $factory->forProperty($class->getProperty('nativeDateTimeImmutableProp')));
30+
$this->assertInstanceOf(NativeDateTimeCaster::class, $factory->forProperty($class->getProperty('nativeDateTimeProp')));
31+
$this->assertInstanceOf(NativeDateTimeCaster::class, $factory->forProperty($class->getProperty('nativeDateTimeInterfaceProp')));
32+
$this->assertInstanceOf(DateTimeCaster::class, $factory->forProperty($class->getProperty('dateTimeProp')));
33+
$this->assertInstanceOf(EnumCaster::class, $factory->forProperty($class->getProperty('unitEnum')));
34+
$this->assertInstanceOf(EnumCaster::class, $factory->forProperty($class->getProperty('backedEnum')));
35+
}
36+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Integration\Mapper\Casters;
4+
5+
use PHPUnit\Framework\Attributes\TestWith;
6+
use Tempest\Mapper\Casters\BooleanCaster;
7+
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
8+
9+
final class BooleanCasterTest extends FrameworkIntegrationTestCase
10+
{
11+
#[TestWith(['true', true])]
12+
#[TestWith(['false', false])]
13+
#[TestWith([true, true])]
14+
#[TestWith([false, false])]
15+
#[TestWith(['on', true])]
16+
#[TestWith(['enabled', true])]
17+
#[TestWith(['yes', true])]
18+
#[TestWith(['ON', true])]
19+
#[TestWith(['ENABLED', true])]
20+
#[TestWith(['YES', true])]
21+
#[TestWith(['off', false])]
22+
#[TestWith(['disabled', false])]
23+
#[TestWith(['no', false])]
24+
public function test_cast(mixed $input, bool $expected): void
25+
{
26+
$this->assertSame($expected, new BooleanCaster()->cast($input));
27+
}
28+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Integration\Mapper\Casters;
4+
5+
use PHPUnit\Framework\Attributes\TestWith;
6+
use Tempest\Mapper\Casters\BooleanCaster;
7+
use Tempest\Mapper\Casters\EnumCaster;
8+
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
9+
use Tests\Tempest\Integration\Mapper\Fixtures\BackedEnumToSerialize;
10+
use Tests\Tempest\Integration\Mapper\Fixtures\UnitEnumToSerialize;
11+
use UnitEnum;
12+
13+
final class EnumCasterTest extends FrameworkIntegrationTestCase
14+
{
15+
#[TestWith(['FOO', UnitEnumToSerialize::FOO, UnitEnumToSerialize::class])]
16+
#[TestWith(['BAR', UnitEnumToSerialize::BAR, UnitEnumToSerialize::class])]
17+
#[TestWith(['foo', BackedEnumToSerialize::FOO, BackedEnumToSerialize::class])]
18+
#[TestWith(['bar', BackedEnumToSerialize::BAR, BackedEnumToSerialize::class])]
19+
public function test_cast(mixed $input, UnitEnum $expected, string $class): void
20+
{
21+
$this->assertSame($expected, new EnumCaster($class)->cast($input));
22+
}
23+
}

tests/Integration/Mapper/Fixtures/ObjectWithSerializerProperties.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ final class ObjectWithSerializerProperties
4141

4242
public DateTime $dateTimeProp;
4343

44+
public UnitEnumToSerialize $unitEnum;
45+
46+
public BackedEnumToSerialize $backedEnum;
47+
4448
public function __construct()
4549
{
4650
$this->stringableProp = \Tempest\Support\str('a');
@@ -51,5 +55,7 @@ public function __construct()
5155
$this->nativeDateTimeProp = new NativeDateTime('2025-01-01');
5256
$this->nativeDateTimeInterfaceProp = new NativeDateTimeImmutable('2025-01-01');
5357
$this->dateTimeProp = DateTime::parse('2025-01-01');
58+
$this->unitEnum = UnitEnumToSerialize::BAR;
59+
$this->backedEnum = BackedEnumToSerialize::FOO;
5460
}
5561
}

tests/Integration/Mapper/Mappers/ArrayToObjectMapperTestCase.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use DateTimeImmutable;
88
use InvalidArgumentException;
9+
use PHPUnit\Framework\Attributes\TestWith;
910
use Tempest\Http\Method;
1011
use Tempest\Mapper\Exceptions\MappingValuesWereMissing;
1112
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
@@ -71,7 +72,7 @@ public function test_built_in_casters(): void
7172
'dateTimeImmutable' => '2024-01-01 10:10:10',
7273
'dateTime' => '2024-01-01 10:10:10',
7374
'dateTimeWithFormat' => '01/12/2024 10:10:10',
74-
'bool' => 'yes',
75+
'bool' => 'no',
7576
'float' => '0.1',
7677
'int' => '1',
7778
])
@@ -82,7 +83,7 @@ public function test_built_in_casters(): void
8283
$this->assertSame('2024-01-01 10:10:10', $object->dateTime->format('Y-m-d H:i:s'));
8384
$this->assertSame('2024-12-01 10:10:10', $object->dateTimeWithFormat->format('Y-m-d H:i:s'));
8485
$this->assertNull($object->nullableDateTimeImmutable);
85-
$this->assertSame(true, $object->bool);
86+
$this->assertSame(false, $object->bool);
8687
$this->assertSame(0.1, $object->float);
8788
$this->assertSame(1, $object->int);
8889
}

tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public function test_serialize(): void
3232
'nativeDateTimeProp' => '2025-01-01 00:00:00',
3333
'nativeDateTimeInterfaceProp' => '2025-01-01 00:00:00',
3434
'dateTimeProp' => '2025-01-01 00:00:00',
35+
'unitEnum' => 'BAR',
36+
'backedEnum' => 'foo',
3537
],
3638
],
3739
new ArrayOfObjectsSerializer()->serialize([new ObjectWithSerializerProperties()]),

0 commit comments

Comments
 (0)