Skip to content

Commit d63f087

Browse files
committed
wip
1 parent 746ade2 commit d63f087

15 files changed

+231
-45
lines changed

packages/database/src/DatabaseInitializer.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
use Tempest\Container\DynamicInitializer;
99
use Tempest\Container\Singleton;
1010
use Tempest\Database\Config\DatabaseConfig;
11+
use Tempest\Database\Config\DatabaseDialect;
1112
use Tempest\Database\Connection\Connection;
1213
use Tempest\Database\Connection\PDOConnection;
1314
use Tempest\Database\Transactions\GenericTransactionManager;
15+
use Tempest\Mapper\SerializerFactory;
1416
use Tempest\Reflection\ClassReflector;
1517
use UnitEnum;
1618

@@ -42,6 +44,7 @@ className: Connection::class,
4244
return new GenericDatabase(
4345
$connection,
4446
new GenericTransactionManager($connection),
47+
$container->get(SerializerFactory::class),
4548
);
4649
}
4750
}

packages/database/src/GenericDatabase.php

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
namespace Tempest\Database;
66

7-
use BackedEnum;
8-
use DateTimeInterface;
97
use PDO;
108
use PDOException;
119
use PDOStatement;
@@ -14,6 +12,7 @@
1412
use Tempest\Database\Connection\Connection;
1513
use Tempest\Database\Exceptions\QueryWasInvalid;
1614
use Tempest\Database\Transactions\TransactionManager;
15+
use Tempest\Mapper\SerializerFactory;
1716
use Throwable;
1817
use UnitEnum;
1918

@@ -33,6 +32,7 @@ final class GenericDatabase implements Database
3332
public function __construct(
3433
private(set) readonly Connection $connection,
3534
private(set) readonly TransactionManager $transactionManager,
35+
private(set) readonly SerializerFactory $serializerFactory,
3636
) {}
3737

3838
public function execute(BuildsQuery|Query $query): void
@@ -124,21 +124,15 @@ private function resolveBindings(Query $query): array
124124
$bindings = [];
125125

126126
foreach ($query->bindings as $key => $value) {
127-
// TODO: this should be handled by serializers (except the Query)
127+
// TODO: make serializer
128128
if ($value instanceof Id) {
129129
$value = $value->id;
130130
}
131131

132132
if ($value instanceof Query) {
133133
$value = $value->execute();
134-
}
135-
136-
if ($value instanceof BackedEnum) {
137-
$value = $value->value;
138-
}
139-
140-
if ($value instanceof DateTimeInterface) {
141-
$value = $value->format('Y-m-d H:i:s');
134+
} elseif ($serializer = $this->serializerFactory->forValue($value)) {
135+
$value = $serializer->serialize($value);
142136
}
143137

144138
$bindings[$key] = $value;

packages/database/tests/GenericDatabaseTest.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Tempest\Database\Connection\Connection;
1111
use Tempest\Database\GenericDatabase;
1212
use Tempest\Database\Transactions\GenericTransactionManager;
13+
use Tempest\Mapper\SerializerFactory;
1314

1415
/**
1516
* @internal
@@ -33,7 +34,7 @@ public function test_it_executes_transactions(): void
3334
$database = new GenericDatabase(
3435
$connection,
3536
new GenericTransactionManager($connection),
36-
DatabaseDialect::SQLITE,
37+
new SerializerFactory(),
3738
);
3839

3940
$result = $database->withinTransaction(function () {
@@ -60,7 +61,7 @@ public function test_it_rolls_back_transactions_on_failure(): void
6061
$database = new GenericDatabase(
6162
$connection,
6263
new GenericTransactionManager($connection),
63-
DatabaseDialect::SQLITE,
64+
new SerializerFactory(),
6465
);
6566

6667
$result = $database->withinTransaction(function (): never {

packages/mapper/src/SerializerFactory.php

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
namespace Tempest\Mapper;
66

77
use Closure;
8+
use Tempest\Reflection\ClassReflector;
89
use Tempest\Reflection\PropertyReflector;
10+
use Tempest\Reflection\TypeReflector;
11+
use TypeError;
912

1013
use function Tempest\get;
1114

@@ -26,6 +29,40 @@ public function addSerializer(string|Closure $for, string|Closure $serializerCla
2629
return $this;
2730
}
2831

32+
private function serializerMatches(Closure|string $for, TypeReflector|string $input): bool
33+
{
34+
if (is_callable($for)) {
35+
try {
36+
return $for($input);
37+
} catch (TypeError $e) {
38+
return false;
39+
}
40+
}
41+
42+
if ($for === $input) {
43+
return true;
44+
}
45+
46+
if ($input instanceof TypeReflector) {
47+
return $input->getName() === $for || $input->matches($for);
48+
}
49+
50+
return false;
51+
}
52+
53+
private function resolveSerializer(Closure|string $serializerClass, PropertyReflector|TypeReflector|string $input): ?Serializer
54+
{
55+
if (is_string($serializerClass)) {
56+
return get($serializerClass);
57+
}
58+
59+
try {
60+
return $serializerClass($input);
61+
} catch (TypeError) {
62+
return null;
63+
}
64+
}
65+
2966
public function forProperty(PropertyReflector $property): ?Serializer
3067
{
3168
$type = $property->getType();
@@ -46,10 +83,42 @@ public function forProperty(PropertyReflector $property): ?Serializer
4683

4784
// Resolve serializer from manual additions
4885
foreach ($this->serializers as [$for, $serializerClass]) {
49-
if (is_callable($for) && $for($property) || is_string($for) && $type->matches($for) || $type->getName() === $for) {
50-
return is_callable($serializerClass)
51-
? $serializerClass($property)
52-
: get($serializerClass);
86+
if (! $this->serializerMatches($for, $type)) {
87+
continue;
88+
}
89+
90+
$serializer = $this->resolveSerializer($serializerClass, $property);
91+
92+
if ($serializer !== null) {
93+
return $serializer;
94+
}
95+
}
96+
97+
return null;
98+
}
99+
100+
public function forValue(mixed $value): ?Serializer
101+
{
102+
if ($value === null) {
103+
return null;
104+
}
105+
106+
if (is_object($value)) {
107+
$input = new ClassReflector($value)->getType();
108+
} else {
109+
$input = gettype($value);
110+
}
111+
112+
// Resolve serializer from manual additions
113+
foreach ($this->serializers as [$for, $serializerClass]) {
114+
if (! $this->serializerMatches($for, $input)) {
115+
continue;
116+
}
117+
118+
$serializer = $this->resolveSerializer($serializerClass, $input);
119+
120+
if ($serializer !== null) {
121+
return $serializer;
53122
}
54123
}
55124

packages/mapper/src/SerializerFactoryInitializer.php

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Tempest\Container\Container;
1515
use Tempest\Container\Initializer;
1616
use Tempest\Container\Singleton;
17+
use Tempest\Database\Id;
1718
use Tempest\DateTime\DateTime;
1819
use Tempest\DateTime\DateTimeInterface;
1920
use Tempest\Mapper\Serializers\ArrayOfObjectsSerializer;
@@ -27,6 +28,7 @@
2728
use Tempest\Mapper\Serializers\SerializableSerializer;
2829
use Tempest\Mapper\Serializers\StringSerializer;
2930
use Tempest\Reflection\PropertyReflector;
31+
use Tempest\Reflection\TypeReflector;
3032

3133
final class SerializerFactoryInitializer implements Initializer
3234
{
@@ -35,19 +37,29 @@ public function initialize(Container $container): SerializerFactory
3537
{
3638
return new SerializerFactory()
3739
->addSerializer('bool', BooleanSerializer::class)
40+
->addSerializer('boolean', BooleanSerializer::class)
3841
->addSerializer('float', FloatSerializer::class)
42+
->addSerializer('double', FloatSerializer::class)
3943
->addSerializer('int', IntegerSerializer::class)
44+
->addSerializer('integer', IntegerSerializer::class)
4045
->addSerializer('string', StringSerializer::class)
4146
->addSerializer('array', ArrayToJsonSerializer::class)
42-
->addSerializer(DateTimeInterface::class, DateTimeSerializer::fromProperty(...))
43-
->addSerializer(NativeDateTimeImmutable::class, NativeDateTimeSerializer::fromProperty(...))
44-
->addSerializer(NativeDateTimeInterface::class, NativeDateTimeSerializer::fromProperty(...))
45-
->addSerializer(NativeDateTime::class, NativeDateTimeSerializer::fromProperty(...))
46-
->addSerializer(Stringable::class, StringSerializer::class)
47+
->addSerializer(DateTimeInterface::class, DateTimeSerializer::fromReflector(...))
48+
->addSerializer(NativeDateTimeImmutable::class, NativeDateTimeSerializer::fromReflector(...))
49+
->addSerializer(NativeDateTimeInterface::class, NativeDateTimeSerializer::fromReflector(...))
50+
->addSerializer(NativeDateTime::class, NativeDateTimeSerializer::fromReflector(...))
4751
->addSerializer(Serializable::class, SerializableSerializer::class)
4852
->addSerializer(JsonSerializable::class, SerializableSerializer::class)
53+
->addSerializer(Stringable::class, StringSerializer::class)
4954
->addSerializer(BackedEnum::class, EnumSerializer::class)
50-
->addSerializer(DateTime::class, DateTimeSerializer::fromProperty(...))
51-
->addSerializer(fn (PropertyReflector $property) => $property->getIterableType() !== null, ArrayOfObjectsSerializer::class);
55+
->addSerializer(DateTime::class, DateTimeSerializer::fromReflector(...))
56+
->addSerializer(
57+
fn (TypeReflector $type) => $type->isIterable(),
58+
ArrayOfObjectsSerializer::class,
59+
)
60+
->addSerializer(
61+
fn (PropertyReflector $property) => $property->getIterableType() !== null,
62+
ArrayOfObjectsSerializer::class,
63+
);
5264
}
5365
}

packages/mapper/src/Serializers/DateTimeSerializer.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,22 @@
1111
use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized;
1212
use Tempest\Mapper\Serializer;
1313
use Tempest\Reflection\PropertyReflector;
14+
use Tempest\Reflection\TypeReflector;
1415
use Tempest\Validation\Rules\DateTimeFormat;
1516

1617
final readonly class DateTimeSerializer implements Serializer
1718
{
1819
public function __construct(
19-
private FormatPattern|string $format = FormatPattern::ISO8601,
20+
private FormatPattern|string $format = FormatPattern::SQL_DATE_TIME,
2021
) {}
2122

22-
public static function fromProperty(PropertyReflector $property): self
23+
public static function fromReflector(PropertyReflector|TypeReflector $reflector): self
2324
{
24-
$format = $property->getAttribute(DateTimeFormat::class)->format ?? FormatPattern::ISO8601;
25+
if ($reflector instanceof PropertyReflector) {
26+
$format = $reflector->getAttribute(DateTimeFormat::class)->format ?: FormatPattern::SQL_DATE_TIME;
27+
} else {
28+
$format = FormatPattern::SQL_DATE_TIME;
29+
}
2530

2631
return new self($format);
2732
}

packages/mapper/src/Serializers/NativeDateTimeSerializer.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
use DateTimeInterface;
88
use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized;
99
use Tempest\Mapper\Serializer;
10+
use Tempest\Reflection\ClassReflector;
1011
use Tempest\Reflection\PropertyReflector;
12+
use Tempest\Reflection\TypeReflector;
1113
use Tempest\Validation\Rules\DateTimeFormat;
1214

1315
final readonly class NativeDateTimeSerializer implements Serializer
@@ -16,9 +18,13 @@ public function __construct(
1618
private string $format = 'Y-m-d H:i:s',
1719
) {}
1820

19-
public static function fromProperty(PropertyReflector $property): self
21+
public static function fromReflector(PropertyReflector|TypeReflector $property): self
2022
{
21-
$format = $property->getAttribute(DateTimeFormat::class)->format ?? 'Y-m-d H:i:s';
23+
if ($property instanceof PropertyReflector) {
24+
$format = $property->getAttribute(DateTimeFormat::class)->format ?? 'Y-m-d H:i:s';
25+
} else {
26+
$format = 'Y-m-d H:i:s';
27+
}
2228

2329
return new self($format);
2430
}

tests/Integration/Mapper/MapperTest.php

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -245,19 +245,19 @@ public function test_nested_value_object_mapping(): void
245245
$this->assertSame('Roose', $person->name->last);
246246
}
247247

248-
public function test_object_to_array_mapper_use_casters(): void
248+
public function test_object_to_array_mapper_use_serializers(): void
249249
{
250250
$this->assertSame(
251251
expected: [
252252
'name' => 'Guillaume',
253-
'nativeDate' => '2025-03-02',
254-
'date' => '2024-01-01',
253+
'nativeDate' => '2025-03-02 00:00:00',
254+
'date' => '2024-01-01 00:00:00',
255255
'enum' => 'foo',
256256
],
257257
actual: map(new ObjectThatShouldUseCasters(
258258
name: 'Guillaume',
259-
date: DateTime::parse('2024-01-01'),
260-
nativeDate: DateTimeImmutable::createFromFormat('Y-m-d', '2025-03-02'),
259+
nativeDate: DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2025-03-02 00:00:00'),
260+
date: DateTime::parse('2024-01-01 00:00:00'),
261261
enum: EnumToCast::FOO,
262262
))->toArray(),
263263
);
@@ -267,8 +267,8 @@ public function test_map_two_way(): void
267267
{
268268
$object = new ObjectThatShouldUseCasters(
269269
name: 'Guillaume',
270-
date: DateTime::parse('2024-01-01'),
271-
nativeDate: DateTimeImmutable::createFromFormat('Y-m-d', '2025-03-02'),
270+
nativeDate: DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2025-03-02 00:00:00'),
271+
date: DateTime::parse('2024-01-01 00:00:00'),
272272
enum: EnumToCast::FOO,
273273
);
274274

@@ -277,12 +277,12 @@ enum: EnumToCast::FOO,
277277

278278
$this->assertSame([
279279
'name' => 'Guillaume',
280-
'nativeDate' => '2025-03-02',
281-
'date' => '2024-01-01',
280+
'nativeDate' => '2025-03-02 00:00:00',
281+
'date' => '2024-01-01 00:00:00',
282282
'enum' => 'foo',
283283
], $array);
284284

285-
$this->assertSame('{"name":"Guillaume","nativeDate":"2025-03-02","date":"2024-01-01","enum":"foo"}', $json);
285+
$this->assertSame('{"name":"Guillaume","nativeDate":"2025-03-02 00:00:00","date":"2024-01-01 00:00:00","enum":"foo"}', $json);
286286

287287
$fromJson = map($json)->to(ObjectThatShouldUseCasters::class);
288288
$fromArray = map($array)->to(ObjectThatShouldUseCasters::class);
@@ -347,7 +347,7 @@ public function test_nested_object_to_array_casting(): void
347347
],
348348
);
349349

350-
$array = map($object)->with(ObjectToArrayMapper::class)->do();
350+
$array = map($object)->toArray();
351351

352352
$this->assertSame([
353353
'items' => [

0 commit comments

Comments
 (0)