diff --git a/docs/Quickstart.md b/docs/Quickstart.md index ad50d79..1e3867f 100644 --- a/docs/Quickstart.md +++ b/docs/Quickstart.md @@ -54,7 +54,7 @@ The package allows you to hydrate these data objects from other types, for examp $todo = Todo::from([ 'title' => 'Learn PHP DTO', 'content' => 'Learn how to use PHP DTO', - 'status' => 'ready', + 'status' => 'ready', // Or Status::READY 'dueDate' => '2025-01-01T00:00:00+00:00' ]); ``` diff --git a/src/Serializers/DataSerializer.php b/src/Serializers/DataSerializer.php index c765b11..547c50e 100644 --- a/src/Serializers/DataSerializer.php +++ b/src/Serializers/DataSerializer.php @@ -5,8 +5,10 @@ use Exception; use Nuxtifyts\PhpDto\Concerns\SerializesArrayOfItems; use Nuxtifyts\PhpDto\Contexts\PropertyContext; +use Nuxtifyts\PhpDto\Contexts\TypeContext; use Nuxtifyts\PhpDto\Contracts\BaseData as BaseDataContract; use Nuxtifyts\PhpDto\Contracts\SerializesArrayOfItems as SerializesArrayOfItemsContract; +use Nuxtifyts\PhpDto\Data; use Nuxtifyts\PhpDto\Enums\Property\Type; use Nuxtifyts\PhpDto\Exceptions\DeserializeException; use Nuxtifyts\PhpDto\Exceptions\SerializeException; @@ -43,10 +45,10 @@ protected function serializeItem(mixed $item, PropertyContext $property, object */ protected function deserializeItem(mixed $item, PropertyContext $property): ?BaseDataContract { - if (is_array($item)) { - $typeContexts = $property->getFilteredTypeContexts(...self::supportedTypes()) - ?: $property->getFilteredSubTypeContexts(...self::supportedTypes()); + $typeContexts = $property->getFilteredTypeContexts(...self::supportedTypes()) + ?: $property->getFilteredSubTypeContexts(...self::supportedTypes()); + if (is_array($item)) { foreach ($typeContexts as $typeContext) { try { if (!$typeContext->reflection?->implementsInterface(BaseDataContract::class)) { @@ -70,6 +72,15 @@ protected function deserializeItem(mixed $item, PropertyContext $property): ?Bas } // @codeCoverageIgnoreEnd } + } elseif ($item instanceof Data) { + if ( + array_any( + $typeContexts, + static fn (TypeContext $type) => (bool) $type->reflection?->isInstance($item) + ) + ) { + return $item; + } } return is_null($item) && $property->isNullable diff --git a/src/Serializers/DateTimeSerializer.php b/src/Serializers/DateTimeSerializer.php index d50a3c3..6b67e42 100644 --- a/src/Serializers/DateTimeSerializer.php +++ b/src/Serializers/DateTimeSerializer.php @@ -2,11 +2,11 @@ namespace Nuxtifyts\PhpDto\Serializers; -use ArrayAccess; use DateTimeImmutable; use DateTimeInterface; use Exception; use Nuxtifyts\PhpDto\Contexts\PropertyContext; +use Nuxtifyts\PhpDto\Contexts\TypeContext; use Nuxtifyts\PhpDto\Contracts\SerializesArrayOfItems as SerializesArrayOfItemsContract; use Nuxtifyts\PhpDto\Concerns\SerializesArrayOfItems; use Nuxtifyts\PhpDto\Enums\Property\Type; @@ -43,10 +43,10 @@ protected function serializeItem(mixed $item, PropertyContext $property, object */ protected function deserializeItem(mixed $item, PropertyContext $property): ?DateTimeInterface { - if (is_string($item)) { - $typeContexts = $property->getFilteredTypeContexts(...self::supportedTypes()) - ?: $property->getFilteredSubTypeContexts(...self::supportedTypes()); + $typeContexts = $property->getFilteredTypeContexts(...self::supportedTypes()) + ?: $property->getFilteredSubTypeContexts(...self::supportedTypes()); + if (is_string($item)) { foreach ($typeContexts as $typeContext) { try { if (!$typeContext->reflection?->implementsInterface(DateTimeInterface::class)) { @@ -71,7 +71,12 @@ protected function deserializeItem(mixed $item, PropertyContext $property): ?Dat // @codeCoverageIgnoreEnd } } elseif ($item instanceof DateTimeInterface) { - return $item; + if (array_any( + $typeContexts, + static fn(TypeContext $typeContext) => (bool) $typeContext->reflection?->isInstance($item) + )) { + return $item; + } } return is_null($item) && $property->isNullable diff --git a/tests/Unit/Concerns/BaseDataTest.php b/tests/Unit/Concerns/BaseDataTest.php index 687cc8f..26fb957 100644 --- a/tests/Unit/Concerns/BaseDataTest.php +++ b/tests/Unit/Concerns/BaseDataTest.php @@ -8,6 +8,9 @@ use Nuxtifyts\PhpDto\Exceptions\SerializeException; use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\DeserializePipelinePassable; use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\RefineDataPipe; +use Nuxtifyts\PhpDto\Serializers\BackedEnumSerializer; +use Nuxtifyts\PhpDto\Serializers\DataSerializer; +use Nuxtifyts\PhpDto\Serializers\DateTimeSerializer; use Nuxtifyts\PhpDto\Support\Passable; use Nuxtifyts\PhpDto\Support\Pipe; use Nuxtifyts\PhpDto\Support\Pipeline; @@ -32,6 +35,7 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; use Throwable; +use DateTimeInterface; #[CoversClass(Data::class)] #[CoversClass(DeserializeException::class)] @@ -41,6 +45,9 @@ #[CoversClass(DeserializePipelinePassable::class)] #[CoversClass(RefineDataPipe::class)] #[CoversClass(Passable::class)] +#[CoversClass(DateTimeSerializer::class)] +#[CoversClass(DataSerializer::class)] +#[CoversClass(BackedEnumSerializer::class)] #[UsesClass(PersonData::class)] #[UsesClass(UnionTypedData::class)] #[UsesClass(YesOrNoData::class)] @@ -271,6 +278,41 @@ public static function will_perform_serialization_and_deserialization_data_provi ], 'expectedSerializedData' => $data ], + 'Refundable item data 3' => [ + 'dtoClass' => RefundableItemData::class, + 'data' => $data = [ + 'id' => 'id2', + 'refundable' => YesNoBackedEnum::NO, + 'refundableUntil' => null + ], + 'expectedProperties' => [ + 'id' => 'id2', + 'refundable' => YesNoBackedEnum::NO, + 'refundableUntil' => null + ], + 'expectedSerializedData' => [ + ...$data, + 'refundable' => YesNoBackedEnum::NO->value + ] + ], + 'Refundable item data 4' => [ + 'dtoClass' => RefundableItemData::class, + 'data' => $data = [ + 'id' => 'id', + 'refundable' => YesNoBackedEnum::YES->value, + 'refundableUntil' => new DateTimeImmutable('2021-01-01T00:00:00+00:00') + ], + 'expectedProperties' => [ + 'id' => 'id', + 'refundable' => YesNoBackedEnum::YES, + 'refundableUntil' => new DateTimeImmutable('2021-01-01T00:00:00+00:00') + ], + 'expectedSerializedData' => [ + ...$data, + 'refundableUntil' => new DateTimeImmutable('2021-01-01T00:00:00+00:00') + ->format(DateTimeInterface::ATOM) + ] + ], 'Address data 1' => [ 'dtoClass' => AddressData::class, 'data' => $data = [ @@ -469,6 +511,36 @@ public static function will_perform_serialization_and_deserialization_data_provi ], 'expectedSerializedData' => $data ], + 'User group data 3' => [ + 'dtoClass' => UserGroupData::class, + 'data' => $data = [ + 'name' => 'Group name 2', + 'users' => [ + new UserData('John', 'Doe'), + new UserData('Jane', 'Doe') + ] + ], + 'expectedProperties' => [ + 'name' => 'Group name 2', + 'users' => [ + new UserData('John', 'Doe'), + new UserData('Jane', 'Doe') + ] + ], + 'expectedSerializedData' => [ + ...$data, + 'users' => [ + [ + 'firstName' => 'John', + 'lastName' => 'Doe' + ], + [ + 'firstName' => 'Jane', + 'lastName' => 'Doe' + ] + ] + ] + ], 'Computed properties data' => [ 'dtoClass' => ComputedPropertiesData::class, 'data' => $data = [