diff --git a/packages/database/src/Casters/IdCaster.php b/packages/database/src/Casters/IdCaster.php index 6735864a2..f0c8e988b 100644 --- a/packages/database/src/Casters/IdCaster.php +++ b/packages/database/src/Casters/IdCaster.php @@ -6,7 +6,6 @@ use Tempest\Database\Id; use Tempest\Mapper\Caster; -use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; final readonly class IdCaster implements Caster { @@ -18,13 +17,4 @@ public function cast(mixed $input): Id return new Id($input); } - - public function serialize(mixed $input): string - { - if (! ($input instanceof Id)) { - throw new ValueCouldNotBeSerialized(Id::class); - } - - return (string) $input->id; - } } diff --git a/packages/database/src/DatabaseInitializer.php b/packages/database/src/DatabaseInitializer.php index 2cffe1846..57e67f50d 100644 --- a/packages/database/src/DatabaseInitializer.php +++ b/packages/database/src/DatabaseInitializer.php @@ -11,6 +11,7 @@ use Tempest\Database\Connection\Connection; use Tempest\Database\Connection\PDOConnection; use Tempest\Database\Transactions\GenericTransactionManager; +use Tempest\Mapper\SerializerFactory; use Tempest\Reflection\ClassReflector; use UnitEnum; @@ -42,6 +43,7 @@ className: Connection::class, return new GenericDatabase( $connection, new GenericTransactionManager($connection), + $container->get(SerializerFactory::class), ); } } diff --git a/packages/database/src/GenericDatabase.php b/packages/database/src/GenericDatabase.php index 0fbcfbc35..2cda2b581 100644 --- a/packages/database/src/GenericDatabase.php +++ b/packages/database/src/GenericDatabase.php @@ -4,8 +4,6 @@ namespace Tempest\Database; -use BackedEnum; -use DateTimeInterface; use PDO; use PDOException; use PDOStatement; @@ -14,6 +12,7 @@ use Tempest\Database\Connection\Connection; use Tempest\Database\Exceptions\QueryWasInvalid; use Tempest\Database\Transactions\TransactionManager; +use Tempest\Mapper\SerializerFactory; use Throwable; use UnitEnum; @@ -33,6 +32,7 @@ final class GenericDatabase implements Database public function __construct( private(set) readonly Connection $connection, private(set) readonly TransactionManager $transactionManager, + private(set) readonly SerializerFactory $serializerFactory, ) {} public function execute(BuildsQuery|Query $query): void @@ -124,21 +124,10 @@ private function resolveBindings(Query $query): array $bindings = []; foreach ($query->bindings as $key => $value) { - // TODO: this should be handled by serializers (except the Query) - if ($value instanceof Id) { - $value = $value->id; - } - if ($value instanceof Query) { $value = $value->execute(); - } - - if ($value instanceof BackedEnum) { - $value = $value->value; - } - - if ($value instanceof DateTimeInterface) { - $value = $value->format('Y-m-d H:i:s'); + } elseif ($serializer = $this->serializerFactory->forValue($value)) { + $value = $serializer->serialize($value); } $bindings[$key] = $value; diff --git a/packages/database/src/Serializers/IdSerializer.php b/packages/database/src/Serializers/IdSerializer.php new file mode 100644 index 000000000..48f94f0bc --- /dev/null +++ b/packages/database/src/Serializers/IdSerializer.php @@ -0,0 +1,19 @@ +id; + } +} diff --git a/packages/database/src/Serializers/IdSerializerProvider.php b/packages/database/src/Serializers/IdSerializerProvider.php new file mode 100644 index 000000000..a8591dd99 --- /dev/null +++ b/packages/database/src/Serializers/IdSerializerProvider.php @@ -0,0 +1,21 @@ +serializerFactory->addSerializer(Id::class, IdSerializer::class); + } +} diff --git a/packages/database/tests/GenericDatabaseTest.php b/packages/database/tests/GenericDatabaseTest.php index 2d0be158a..32ab12b92 100644 --- a/packages/database/tests/GenericDatabaseTest.php +++ b/packages/database/tests/GenericDatabaseTest.php @@ -10,6 +10,7 @@ use Tempest\Database\Connection\Connection; use Tempest\Database\GenericDatabase; use Tempest\Database\Transactions\GenericTransactionManager; +use Tempest\Mapper\SerializerFactory; /** * @internal @@ -33,7 +34,7 @@ public function test_it_executes_transactions(): void $database = new GenericDatabase( $connection, new GenericTransactionManager($connection), - DatabaseDialect::SQLITE, + new SerializerFactory(), ); $result = $database->withinTransaction(function () { @@ -60,7 +61,7 @@ public function test_it_rolls_back_transactions_on_failure(): void $database = new GenericDatabase( $connection, new GenericTransactionManager($connection), - DatabaseDialect::SQLITE, + new SerializerFactory(), ); $result = $database->withinTransaction(function (): never { diff --git a/packages/mapper/src/Mappers/ObjectToArrayMapper.php b/packages/mapper/src/Mappers/ObjectToArrayMapper.php index 9df2457c0..beaa94680 100644 --- a/packages/mapper/src/Mappers/ObjectToArrayMapper.php +++ b/packages/mapper/src/Mappers/ObjectToArrayMapper.php @@ -11,6 +11,8 @@ use Tempest\Reflection\ClassReflector; use Tempest\Reflection\PropertyReflector; +use function Tempest\map; + final readonly class ObjectToArrayMapper implements Mapper { public function __construct( @@ -22,20 +24,24 @@ public function canMap(mixed $from, mixed $to): bool return false; } - public function map(mixed $from, mixed $to): array + public function map(mixed $from, mixed $to): mixed { if ($from instanceof JsonSerializable) { return $from->jsonSerialize(); } - $class = new ClassReflector($from); + if (is_object($from)) { + $class = new ClassReflector($from); - $mappedProperties = []; + $mappedProperties = []; - foreach ($class->getPublicProperties() as $property) { - $propertyName = $this->resolvePropertyName($property); - $propertyValue = $this->resolvePropertyValue($property, $from); - $mappedProperties[$propertyName] = $propertyValue; + foreach ($class->getPublicProperties() as $property) { + $propertyName = $this->resolvePropertyName($property); + $propertyValue = $this->resolvePropertyValue($property, $from); + $mappedProperties[$propertyName] = $propertyValue; + } + } else { + $mappedProperties = $from; } return $mappedProperties; @@ -45,6 +51,16 @@ private function resolvePropertyValue(PropertyReflector $property, object $objec { $propertyValue = $property->getValue($object); + if ($property->getIterableType()?->isClass()) { + foreach ($propertyValue as $key => $value) { + if (is_object($value)) { + $propertyValue[$key] = map($value)->toArray(); + } + } + + return $propertyValue; + } + if ($propertyValue !== null && ($serializer = $this->serializerFactory->forProperty($property)) !== null) { return $serializer->serialize($propertyValue); } diff --git a/packages/mapper/src/SerializerFactory.php b/packages/mapper/src/SerializerFactory.php index f4e852c39..5f266f476 100644 --- a/packages/mapper/src/SerializerFactory.php +++ b/packages/mapper/src/SerializerFactory.php @@ -5,7 +5,10 @@ namespace Tempest\Mapper; use Closure; +use Tempest\Reflection\ClassReflector; use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; +use TypeError; use function Tempest\get; @@ -26,6 +29,40 @@ public function addSerializer(string|Closure $for, string|Closure $serializerCla return $this; } + private function serializerMatches(Closure|string $for, TypeReflector|string $input): bool + { + if (is_callable($for)) { + try { + return $for($input); + } catch (TypeError) { // @mago-expect best-practices/dont-catch-error + return false; + } + } + + if ($for === $input) { + return true; + } + + if ($input instanceof TypeReflector) { + return $input->getName() === $for || $input->matches($for); + } + + return false; + } + + private function resolveSerializer(Closure|string $serializerClass, PropertyReflector|TypeReflector|string $input): ?Serializer + { + if (is_string($serializerClass)) { + return get($serializerClass); + } + + try { + return $serializerClass($input); + } catch (TypeError) { // @mago-expect best-practices/dont-catch-error + return null; + } + } + public function forProperty(PropertyReflector $property): ?Serializer { $type = $property->getType(); @@ -46,10 +83,42 @@ public function forProperty(PropertyReflector $property): ?Serializer // Resolve serializer from manual additions foreach ($this->serializers as [$for, $serializerClass]) { - if (is_callable($for) && $for($property) || is_string($for) && $type->matches($for) || $type->getName() === $for) { - return is_callable($serializerClass) - ? $serializerClass($property) - : get($serializerClass); + if (! $this->serializerMatches($for, $type)) { + continue; + } + + $serializer = $this->resolveSerializer($serializerClass, $property); + + if ($serializer !== null) { + return $serializer; + } + } + + return null; + } + + public function forValue(mixed $value): ?Serializer + { + if ($value === null) { + return null; + } + + if (is_object($value)) { + $input = new ClassReflector($value)->getType(); + } else { + $input = gettype($value); + } + + // Resolve serializer from manual additions + foreach ($this->serializers as [$for, $serializerClass]) { + if (! $this->serializerMatches($for, $input)) { + continue; + } + + $serializer = $this->resolveSerializer($serializerClass, $input); + + if ($serializer !== null) { + return $serializer; } } diff --git a/packages/mapper/src/SerializerFactoryInitializer.php b/packages/mapper/src/SerializerFactoryInitializer.php index 990f136cf..258f27349 100644 --- a/packages/mapper/src/SerializerFactoryInitializer.php +++ b/packages/mapper/src/SerializerFactoryInitializer.php @@ -14,6 +14,7 @@ use Tempest\Container\Container; use Tempest\Container\Initializer; use Tempest\Container\Singleton; +use Tempest\Database\Id; use Tempest\DateTime\DateTime; use Tempest\DateTime\DateTimeInterface; use Tempest\Mapper\Serializers\ArrayOfObjectsSerializer; @@ -27,6 +28,7 @@ use Tempest\Mapper\Serializers\SerializableSerializer; use Tempest\Mapper\Serializers\StringSerializer; use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; final class SerializerFactoryInitializer implements Initializer { @@ -35,19 +37,25 @@ public function initialize(Container $container): SerializerFactory { return new SerializerFactory() ->addSerializer('bool', BooleanSerializer::class) + ->addSerializer('boolean', BooleanSerializer::class) ->addSerializer('float', FloatSerializer::class) + ->addSerializer('double', FloatSerializer::class) ->addSerializer('int', IntegerSerializer::class) + ->addSerializer('integer', IntegerSerializer::class) ->addSerializer('string', StringSerializer::class) ->addSerializer('array', ArrayToJsonSerializer::class) - ->addSerializer(DateTimeInterface::class, DateTimeSerializer::fromProperty(...)) - ->addSerializer(NativeDateTimeImmutable::class, NativeDateTimeSerializer::fromProperty(...)) - ->addSerializer(NativeDateTimeInterface::class, NativeDateTimeSerializer::fromProperty(...)) - ->addSerializer(NativeDateTime::class, NativeDateTimeSerializer::fromProperty(...)) - ->addSerializer(Stringable::class, StringSerializer::class) + ->addSerializer(DateTimeInterface::class, DateTimeSerializer::fromReflector(...)) + ->addSerializer(NativeDateTimeImmutable::class, NativeDateTimeSerializer::fromReflector(...)) + ->addSerializer(NativeDateTimeInterface::class, NativeDateTimeSerializer::fromReflector(...)) + ->addSerializer(NativeDateTime::class, NativeDateTimeSerializer::fromReflector(...)) ->addSerializer(Serializable::class, SerializableSerializer::class) ->addSerializer(JsonSerializable::class, SerializableSerializer::class) + ->addSerializer(Stringable::class, StringSerializer::class) ->addSerializer(BackedEnum::class, EnumSerializer::class) - ->addSerializer(DateTime::class, DateTimeSerializer::fromProperty(...)) - ->addSerializer(fn (PropertyReflector $property) => $property->getIterableType() !== null, ArrayOfObjectsSerializer::class); + ->addSerializer(DateTime::class, DateTimeSerializer::fromReflector(...)) + ->addSerializer( + fn (PropertyReflector $property) => $property->getIterableType() !== null, + ArrayOfObjectsSerializer::class, + ); } } diff --git a/packages/mapper/src/Serializers/DateTimeSerializer.php b/packages/mapper/src/Serializers/DateTimeSerializer.php index 099868e14..69bd543f3 100644 --- a/packages/mapper/src/Serializers/DateTimeSerializer.php +++ b/packages/mapper/src/Serializers/DateTimeSerializer.php @@ -11,17 +11,22 @@ use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; use Tempest\Validation\Rules\DateTimeFormat; final readonly class DateTimeSerializer implements Serializer { public function __construct( - private FormatPattern|string $format = FormatPattern::ISO8601, + private FormatPattern|string $format = FormatPattern::SQL_DATE_TIME, ) {} - public static function fromProperty(PropertyReflector $property): self + public static function fromReflector(PropertyReflector|TypeReflector $reflector): self { - $format = $property->getAttribute(DateTimeFormat::class)->format ?? FormatPattern::ISO8601; + if ($reflector instanceof PropertyReflector) { + $format = $reflector->getAttribute(DateTimeFormat::class)?->format ?? FormatPattern::SQL_DATE_TIME; + } else { + $format = FormatPattern::SQL_DATE_TIME; + } return new self($format); } diff --git a/packages/mapper/src/Serializers/NativeDateTimeSerializer.php b/packages/mapper/src/Serializers/NativeDateTimeSerializer.php index e4aad655b..004998489 100644 --- a/packages/mapper/src/Serializers/NativeDateTimeSerializer.php +++ b/packages/mapper/src/Serializers/NativeDateTimeSerializer.php @@ -7,7 +7,9 @@ use DateTimeInterface; use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; +use Tempest\Reflection\ClassReflector; use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; use Tempest\Validation\Rules\DateTimeFormat; final readonly class NativeDateTimeSerializer implements Serializer @@ -16,9 +18,13 @@ public function __construct( private string $format = 'Y-m-d H:i:s', ) {} - public static function fromProperty(PropertyReflector $property): self + public static function fromReflector(PropertyReflector|TypeReflector $property): self { - $format = $property->getAttribute(DateTimeFormat::class)->format ?? 'Y-m-d H:i:s'; + if ($property instanceof PropertyReflector) { + $format = $property->getAttribute(DateTimeFormat::class)?->format ?? 'Y-m-d H:i:s'; + } else { + $format = 'Y-m-d H:i:s'; + } return new self($format); } diff --git a/tests/Integration/Mapper/MapperTest.php b/tests/Integration/Mapper/MapperTest.php index abef0f223..50966b7d4 100644 --- a/tests/Integration/Mapper/MapperTest.php +++ b/tests/Integration/Mapper/MapperTest.php @@ -245,7 +245,7 @@ public function test_nested_value_object_mapping(): void $this->assertSame('Roose', $person->name->last); } - public function test_object_to_array_mapper_use_casters(): void + public function test_object_to_array_mapper_use_serializers(): void { $this->assertSame( expected: [ @@ -256,8 +256,8 @@ public function test_object_to_array_mapper_use_casters(): void ], actual: map(new ObjectThatShouldUseCasters( name: 'Guillaume', - date: DateTime::parse('2024-01-01'), - nativeDate: DateTimeImmutable::createFromFormat('Y-m-d', '2025-03-02'), + nativeDate: DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2025-03-02 00:00:00'), + date: DateTime::parse('2024-01-01 00:00:00'), enum: EnumToCast::FOO, ))->toArray(), ); @@ -267,8 +267,8 @@ public function test_map_two_way(): void { $object = new ObjectThatShouldUseCasters( name: 'Guillaume', - date: DateTime::parse('2024-01-01'), - nativeDate: DateTimeImmutable::createFromFormat('Y-m-d', '2025-03-02'), + nativeDate: DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2025-03-02 00:00:00'), + date: DateTime::parse('2024-01-01 00:00:00'), enum: EnumToCast::FOO, ); @@ -347,7 +347,7 @@ public function test_nested_object_to_array_casting(): void ], ); - $array = map($object)->with(ObjectToArrayMapper::class)->do(); + $array = map($object)->toArray(); $this->assertSame([ 'items' => [ diff --git a/tests/Integration/Mapper/SerializerFactoryTest.php b/tests/Integration/Mapper/SerializerFactoryTest.php index 4dda6098c..7c9a6382c 100644 --- a/tests/Integration/Mapper/SerializerFactoryTest.php +++ b/tests/Integration/Mapper/SerializerFactoryTest.php @@ -2,6 +2,9 @@ namespace Tests\Tempest\Integration\Mapper; +use DateTime as NativeDateTime; +use DateTimeImmutable as NativeDateTimeImmutable; +use Tempest\DateTime\DateTime; use Tempest\Mapper\SerializerFactory; use Tempest\Mapper\Serializers\ArrayToJsonSerializer; use Tempest\Mapper\Serializers\BooleanSerializer; @@ -13,20 +16,43 @@ use Tempest\Mapper\Serializers\StringSerializer; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use Tests\Tempest\Integration\Mapper\Fixtures\DoubleStringSerializer; +use Tests\Tempest\Integration\Mapper\Fixtures\JsonSerializableObject; +use Tests\Tempest\Integration\Mapper\Fixtures\NestedObjectB; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithSerializerProperties; +use Tests\Tempest\Integration\Mapper\Fixtures\SerializableObject; use function Tempest\reflect; final class SerializerFactoryTest extends FrameworkIntegrationTestCase { - public function test_serialize(): void + public function test_for_value(): void + { + /** @var SerializerFactory $factory */ + $factory = $this->container->get(SerializerFactory::class); + + $this->assertInstanceOf(StringSerializer::class, $factory->forValue('string')); + $this->assertInstanceOf(StringSerializer::class, $factory->forValue(\Tempest\Support\str('string'))); + $this->assertInstanceOf(IntegerSerializer::class, $factory->forValue(1)); + $this->assertInstanceOf(FloatSerializer::class, $factory->forValue(1.1)); + $this->assertInstanceOf(BooleanSerializer::class, $factory->forValue(true)); + $this->assertInstanceOf(ArrayToJsonSerializer::class, $factory->forValue([])); + $this->assertInstanceOf(SerializableSerializer::class, $factory->forValue(new JsonSerializableObject())); + $this->assertInstanceOf(SerializableSerializer::class, $factory->forValue(new SerializableObject())); + $this->assertInstanceOf(NativeDateTimeSerializer::class, $factory->forValue(new NativeDateTime())); + $this->assertInstanceOf(NativeDateTimeSerializer::class, $factory->forValue(new NativeDateTimeImmutable())); + $this->assertInstanceOf(DateTimeSerializer::class, $factory->forValue(DateTime::now())); + $this->assertNull($factory->forValue(null)); + $this->assertNull($factory->forValue(new NestedObjectB('name'))); + } + + public function test_for_property(): void { $factory = $this->container->get(SerializerFactory::class); $class = reflect(ObjectWithSerializerProperties::class); - $this->assertInstanceOf(StringSerializer::class, $factory->forProperty($class->getProperty('stringableProp'))); $this->assertInstanceOf(StringSerializer::class, $factory->forProperty($class->getProperty('stringProp'))); + $this->assertInstanceOf(StringSerializer::class, $factory->forProperty($class->getProperty('stringableProp'))); $this->assertInstanceOf(IntegerSerializer::class, $factory->forProperty($class->getProperty('intProp'))); $this->assertInstanceOf(FloatSerializer::class, $factory->forProperty($class->getProperty('floatProp'))); $this->assertInstanceOf(BooleanSerializer::class, $factory->forProperty($class->getProperty('boolProp'))); diff --git a/tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php b/tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php index 796eff965..f4a3468ba 100644 --- a/tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php +++ b/tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php @@ -31,7 +31,7 @@ public function test_serialize(): void 'nativeDateTimeImmutableProp' => '2025-01-01 00:00:00', 'nativeDateTimeProp' => '2025-01-01 00:00:00', 'nativeDateTimeInterfaceProp' => '2025-01-01 00:00:00', - 'dateTimeProp' => '2025-01-01T00:00:00.000Z', + 'dateTimeProp' => '2025-01-01 00:00:00', ], ], new ArrayOfObjectsSerializer()->serialize([new ObjectWithSerializerProperties()]), diff --git a/tests/Integration/Mapper/Serializers/DateTimeSerializerTest.php b/tests/Integration/Mapper/Serializers/DateTimeSerializerTest.php index 57e8f7228..2d0b3bc94 100644 --- a/tests/Integration/Mapper/Serializers/DateTimeSerializerTest.php +++ b/tests/Integration/Mapper/Serializers/DateTimeSerializerTest.php @@ -13,7 +13,7 @@ final class DateTimeSerializerTest extends TestCase public function test_serialize(): void { $this->assertSame( - '2025-01-01T00:00:00.000Z', + '2025-01-01 00:00:00', new DateTimeSerializer()->serialize(new DateTime('2025-01-01 00:00:00')), ); @@ -23,7 +23,7 @@ public function test_serialize(): void ); $this->assertSame( - '2025-01-01T00:00:00.000Z', + '2025-01-01 00:00:00', new DateTimeSerializer()->serialize(DateTimeDateTime::parse('2025-01-01 00:00:00')), ); } diff --git a/tests/Integration/ORM/IsDatabaseModelTest.php b/tests/Integration/ORM/IsDatabaseModelTest.php index 221a3fb74..4b16a818a 100644 --- a/tests/Integration/ORM/IsDatabaseModelTest.php +++ b/tests/Integration/ORM/IsDatabaseModelTest.php @@ -5,11 +5,13 @@ namespace Tests\Tempest\Integration\ORM; use Carbon\Carbon; +use DateTime as NativeDateTime; use DateTimeImmutable; use Tempest\Database\Exceptions\RelationWasMissing; use Tempest\Database\Exceptions\ValueWasMissing; use Tempest\Database\Id; use Tempest\Database\Migrations\CreateMigrationsTable; +use Tempest\DateTime\DateTime; use Tempest\Mapper\CasterFactory; use Tempest\Mapper\SerializerFactory; use Tempest\Validation\Exceptions\ValidationFailed; @@ -35,6 +37,7 @@ use Tests\Tempest\Integration\ORM\Migrations\CreateCarbonModelTable; use Tests\Tempest\Integration\ORM\Migrations\CreateCasterModelTable; use Tests\Tempest\Integration\ORM\Migrations\CreateCTable; +use Tests\Tempest\Integration\ORM\Migrations\CreateDateTimeModelTable; use Tests\Tempest\Integration\ORM\Migrations\CreateHasManyChildTable; use Tests\Tempest\Integration\ORM\Migrations\CreateHasManyParentTable; use Tests\Tempest\Integration\ORM\Migrations\CreateHasManyThroughTable; @@ -46,12 +49,14 @@ use Tests\Tempest\Integration\ORM\Models\CasterEnum; use Tests\Tempest\Integration\ORM\Models\CasterModel; use Tests\Tempest\Integration\ORM\Models\ChildModel; +use Tests\Tempest\Integration\ORM\Models\DateTimeModel; use Tests\Tempest\Integration\ORM\Models\ModelWithValidation; use Tests\Tempest\Integration\ORM\Models\ParentModel; use Tests\Tempest\Integration\ORM\Models\StaticMethodTableNameModel; use Tests\Tempest\Integration\ORM\Models\ThroughModel; use function Tempest\Database\model; +use function Tempest\Database\query; use function Tempest\map; /** @@ -611,4 +616,25 @@ public function test_skipped_validation(): void $this->assertStringNotContainsString('skip', $validationFailed->getMessage()); } } + + public function test_date_field(): void + { + $this->migrate( + CreateMigrationsTable::class, + CreateDateTimeModelTable::class, + ); + + $id = query(DateTimeModel::class) + ->insert([ + 'phpDateTime' => new NativeDateTime('2024-01-01 00:00:00'), + 'tempestDateTime' => DateTime::parse('2024-01-01 00:00:00'), + ]) + ->execute(); + + /** @var DateTimeModel $model */ + $model = query(DateTimeModel::class)->select()->whereField('id', $id)->first(); + + $this->assertSame('2024-01-01 00:00:00', $model->phpDateTime->format('Y-m-d H:i:s')); + $this->assertSame('2024-01-01 00:00:00', $model->tempestDateTime->format('yyyy-MM-dd HH:mm:ss')); + } } diff --git a/tests/Integration/ORM/Migrations/CreateDateTimeModelTable.php b/tests/Integration/ORM/Migrations/CreateDateTimeModelTable.php new file mode 100644 index 000000000..3dab4f381 --- /dev/null +++ b/tests/Integration/ORM/Migrations/CreateDateTimeModelTable.php @@ -0,0 +1,26 @@ +primary() + ->datetime('phpDateTime') + ->datetime('tempestDateTime'); + } + + public function down(): null + { + return null; + } +} diff --git a/tests/Integration/ORM/Models/DateTimeModel.php b/tests/Integration/ORM/Models/DateTimeModel.php new file mode 100644 index 000000000..16ef5d7ce --- /dev/null +++ b/tests/Integration/ORM/Models/DateTimeModel.php @@ -0,0 +1,16 @@ +get(SerializerFactory::class), ); } }