Skip to content

Commit 3809cfe

Browse files
authored
Merge branch 'main' into vite-configuration
2 parents 53ea0d5 + 4786a12 commit 3809cfe

22 files changed

+293
-64
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
## [1.2.2](https://github.com/tempestphp/tempest-framework/compare/v1.2.1..1.2.2) — 2025-07-08
5+
## [1.2.3](https://github.com/tempestphp/tempest-framework/compare/v1.2.2..1.2.3) — 2025-07-08
6+
7+
### 🐛 Bug fixes
8+
9+
- **database**: fix datetime serialization for mysql database (#1383) ([dde0e84](https://github.com/tempestphp/tempest-framework/commit/dde0e84a6bba614c4fa3932914ffaee2dd4916e7))
10+
11+
12+
## [1.2.2](https://github.com/tempestphp/tempest-framework/compare/v1.2.1..v1.2.2) — 2025-07-08
613

714
### 🚀 Features
815

packages/core/src/Kernel.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
interface Kernel
1010
{
11-
public const string VERSION = '1.2.2';
11+
public const string VERSION = '1.2.3';
1212

1313
public string $root {
1414
get;

packages/database/src/Casters/IdCaster.php

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

77
use Tempest\Database\Id;
88
use Tempest\Mapper\Caster;
9-
use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized;
109

1110
final readonly class IdCaster implements Caster
1211
{
@@ -18,13 +17,4 @@ public function cast(mixed $input): Id
1817

1918
return new Id($input);
2019
}
21-
22-
public function serialize(mixed $input): string
23-
{
24-
if (! ($input instanceof Id)) {
25-
throw new ValueCouldNotBeSerialized(Id::class);
26-
}
27-
28-
return (string) $input->id;
29-
}
3020
}

packages/database/src/DatabaseInitializer.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Tempest\Database\Connection\Connection;
1212
use Tempest\Database\Connection\PDOConnection;
1313
use Tempest\Database\Transactions\GenericTransactionManager;
14+
use Tempest\Mapper\SerializerFactory;
1415
use Tempest\Reflection\ClassReflector;
1516
use UnitEnum;
1617

@@ -42,6 +43,7 @@ className: Connection::class,
4243
return new GenericDatabase(
4344
$connection,
4445
new GenericTransactionManager($connection),
46+
$container->get(SerializerFactory::class),
4547
);
4648
}
4749
}

packages/database/src/GenericDatabase.php

Lines changed: 4 additions & 15 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,10 @@ 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)
128-
if ($value instanceof Id) {
129-
$value = $value->id;
130-
}
131-
132127
if ($value instanceof Query) {
133128
$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');
129+
} elseif ($serializer = $this->serializerFactory->forValue($value)) {
130+
$value = $serializer->serialize($value);
142131
}
143132

144133
$bindings[$key] = $value;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Tempest\Database\Serializers;
4+
5+
use Tempest\Database\Id;
6+
use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized;
7+
use Tempest\Mapper\Serializer;
8+
9+
final class IdSerializer implements Serializer
10+
{
11+
public function serialize(mixed $input): array|string
12+
{
13+
if (! ($input instanceof Id)) {
14+
throw new ValueCouldNotBeSerialized(Id::class);
15+
}
16+
17+
return $input->id;
18+
}
19+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Tempest\Database\Serializers;
4+
5+
use Tempest\Core\KernelEvent;
6+
use Tempest\Database\Id;
7+
use Tempest\EventBus\EventHandler;
8+
use Tempest\Mapper\SerializerFactory;
9+
10+
final readonly class IdSerializerProvider
11+
{
12+
public function __construct(
13+
private SerializerFactory $serializerFactory,
14+
) {}
15+
16+
#[EventHandler(KernelEvent::BOOTED)]
17+
public function __invoke(KernelEvent $_event): void
18+
{
19+
$this->serializerFactory->addSerializer(Id::class, IdSerializer::class);
20+
}
21+
}

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/Mappers/ObjectToArrayMapper.php

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
use Tempest\Reflection\ClassReflector;
1212
use Tempest\Reflection\PropertyReflector;
1313

14+
use function Tempest\map;
15+
1416
final readonly class ObjectToArrayMapper implements Mapper
1517
{
1618
public function __construct(
@@ -22,20 +24,24 @@ public function canMap(mixed $from, mixed $to): bool
2224
return false;
2325
}
2426

25-
public function map(mixed $from, mixed $to): array
27+
public function map(mixed $from, mixed $to): mixed
2628
{
2729
if ($from instanceof JsonSerializable) {
2830
return $from->jsonSerialize();
2931
}
3032

31-
$class = new ClassReflector($from);
33+
if (is_object($from)) {
34+
$class = new ClassReflector($from);
3235

33-
$mappedProperties = [];
36+
$mappedProperties = [];
3437

35-
foreach ($class->getPublicProperties() as $property) {
36-
$propertyName = $this->resolvePropertyName($property);
37-
$propertyValue = $this->resolvePropertyValue($property, $from);
38-
$mappedProperties[$propertyName] = $propertyValue;
38+
foreach ($class->getPublicProperties() as $property) {
39+
$propertyName = $this->resolvePropertyName($property);
40+
$propertyValue = $this->resolvePropertyValue($property, $from);
41+
$mappedProperties[$propertyName] = $propertyValue;
42+
}
43+
} else {
44+
$mappedProperties = $from;
3945
}
4046

4147
return $mappedProperties;
@@ -45,6 +51,16 @@ private function resolvePropertyValue(PropertyReflector $property, object $objec
4551
{
4652
$propertyValue = $property->getValue($object);
4753

54+
if ($property->getIterableType()?->isClass()) {
55+
foreach ($propertyValue as $key => $value) {
56+
if (is_object($value)) {
57+
$propertyValue[$key] = map($value)->toArray();
58+
}
59+
}
60+
61+
return $propertyValue;
62+
}
63+
4864
if ($propertyValue !== null && ($serializer = $this->serializerFactory->forProperty($property)) !== null) {
4965
return $serializer->serialize($propertyValue);
5066
}

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) { // @mago-expect best-practices/dont-catch-error
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) { // @mago-expect best-practices/dont-catch-error
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

0 commit comments

Comments
 (0)