diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 07a8b06..c106206 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -33,6 +33,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->isRequired() ->cannotBeEmpty() ->end() + ->scalarNode('date_time_class')->end() ->end() ->end() ->end(); diff --git a/src/DependencyInjection/OpenApiServerExtension.php b/src/DependencyInjection/OpenApiServerExtension.php index fea53b2..6a8d03b 100644 --- a/src/DependencyInjection/OpenApiServerExtension.php +++ b/src/DependencyInjection/OpenApiServerExtension.php @@ -40,7 +40,8 @@ public function load(array $configs, ContainerBuilder $container): void * path: string, * type?: string, * name_space: string, - * media_type: string + * media_type: string, + * date_time_class?: string, * } * } $config */ diff --git a/src/Serializer/ArrayDtoSerializer.php b/src/Serializer/ArrayDtoSerializer.php index 9343b94..9364189 100644 --- a/src/Serializer/ArrayDtoSerializer.php +++ b/src/Serializer/ArrayDtoSerializer.php @@ -113,8 +113,13 @@ private function convert(bool $deserialize, array $source, ObjectSchema $params) /** @psalm-suppress MissingClosureParamType */ $converter = fn ($v) => $this->convert($deserialize, $v, $objectType->getSchema()); } else { + $outputClass = null; + if ($typeId !== null && $this->resolver->isDateTime($typeId)) { + $outputClass = $property->getOutputType(); + } + /** @psalm-suppress MissingClosureParamType */ - $converter = fn ($v) => $this->resolver->convert($deserialize, $typeId ?? 0, $v); + $converter = fn ($v) => $this->resolver->convert($deserialize, $typeId ?? 0, $v, $outputClass); } if ($property->isArray()) { diff --git a/src/Specification/Definitions/Property.php b/src/Specification/Definitions/Property.php index a346983..964d2cd 100644 --- a/src/Specification/Definitions/Property.php +++ b/src/Specification/Definitions/Property.php @@ -17,6 +17,7 @@ final class Property private ObjectSchema|ObjectReference|null $objectTypeDefinition = null; private ?string $description = null; private ?string $pattern = null; + private ?object $outputType = null; public function __construct(string $name) { @@ -147,4 +148,16 @@ public function setNullable(bool $nullable): self return $this; } + + public function getOutputType(): ?object + { + return $this->outputType; + } + + public function setOutputType(?object $outputType): self + { + $this->outputType = $outputType; + + return $this; + } } diff --git a/src/Specification/Definitions/SpecificationConfig.php b/src/Specification/Definitions/SpecificationConfig.php index b984fb9..0a32229 100644 --- a/src/Specification/Definitions/SpecificationConfig.php +++ b/src/Specification/Definitions/SpecificationConfig.php @@ -10,13 +10,15 @@ final class SpecificationConfig private ?string $type; private string $nameSpace; private string $mediaType; + private ?string $dateTimeClass; - public function __construct(string $path, ?string $type, string $nameSpace, string $mediaType) + public function __construct(string $path, ?string $type, string $nameSpace, string $mediaType, ?string $dateTimeClass = null) { - $this->path = $path; - $this->type = $type; - $this->nameSpace = $nameSpace; - $this->mediaType = $mediaType; + $this->path = $path; + $this->type = $type; + $this->nameSpace = $nameSpace; + $this->mediaType = $mediaType; + $this->dateTimeClass = $dateTimeClass; } public function getPath(): string @@ -38,4 +40,9 @@ public function getMediaType(): string { return $this->mediaType; } + + public function getDateTimeClass(): ?string + { + return $this->dateTimeClass; + } } diff --git a/src/Specification/SpecificationLoader.php b/src/Specification/SpecificationLoader.php index dc03486..704246f 100644 --- a/src/Specification/SpecificationLoader.php +++ b/src/Specification/SpecificationLoader.php @@ -45,7 +45,7 @@ public function __construct(SpecificationParser $parser, FileLocatorInterface $l } /** - * @param array{path:string,type:string|null,name_space:string,media_type:string} $spec + * @param array{path:string,type:string|null,name_space:string,media_type:string,date_time_class:string|null} $spec */ public function registerSpec(string $name, array $spec): void { @@ -53,7 +53,8 @@ public function registerSpec(string $name, array $spec): void $spec['path'], $spec['type'] ?? null, $spec['name_space'], - $spec['media_type'] + $spec['media_type'], + $spec['date_time_class'] ?? null ); } diff --git a/src/Specification/SpecificationParser.php b/src/Specification/SpecificationParser.php index 7612e35..b615413 100644 --- a/src/Specification/SpecificationParser.php +++ b/src/Specification/SpecificationParser.php @@ -15,6 +15,7 @@ use cebe\openapi\spec\Responses; use cebe\openapi\spec\Schema; use cebe\openapi\spec\Type; +use DateTimeInterface; use OnMoon\OpenApiServerBundle\Exception\CannotParseOpenApi; use OnMoon\OpenApiServerBundle\Specification\Definitions\ComponentArray; use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectReference; @@ -44,12 +45,12 @@ class SpecificationParser private ScalarTypesResolver $typeResolver; /** @var string[] */ private array $skipHttpCodes; + private ?DateTimeInterface $dateTimeClass = null; /** @param array $skipHttpCodes */ public function __construct(ScalarTypesResolver $typeResolver, array $skipHttpCodes) { - $this->typeResolver = $typeResolver; - + $this->typeResolver = $typeResolver; $this->skipHttpCodes = array_map(static fn ($code) => (string) $code, $skipHttpCodes); } @@ -58,6 +59,11 @@ public function parseOpenApi(string $specificationName, SpecificationConfig $spe $componentSchemas = new ComponentArray(); $operationDefinitions = []; + + if ($specificationConfig->getDateTimeClass() !== null) { + $this->dateTimeClass = new ($specificationConfig->getDateTimeClass())(); + } + /** * @var string $url */ @@ -385,6 +391,10 @@ private function getProperty( if (Type::isScalar($itemProperty->type)) { $scalarTypeId = $this->typeResolver->findScalarType($itemProperty->type, $itemProperty->format); $propertyDefinition->setScalarTypeId($scalarTypeId); + + if ($this->typeResolver->isDateTime($scalarTypeId) && $this->dateTimeClass !== null) { + $propertyDefinition->setOutputType($this->dateTimeClass); + } } elseif ($itemProperty->type === Type::OBJECT) { $objectType = $this->getObjectSchema( $itemProperty, diff --git a/src/Types/ScalarTypesResolver.php b/src/Types/ScalarTypesResolver.php index f48fd43..d9900b2 100644 --- a/src/Types/ScalarTypesResolver.php +++ b/src/Types/ScalarTypesResolver.php @@ -6,6 +6,7 @@ use cebe\openapi\spec\Type; +use function dd; use function is_string; use function Safe\settype; @@ -71,7 +72,7 @@ public function __construct() * * @return mixed */ - public function convert(bool $deserialize, int $id, $value) + public function convert(bool $deserialize, int $id, $value, ?object $outputClass = null) { if ($value === null) { return null; @@ -80,6 +81,10 @@ public function convert(bool $deserialize, int $id, $value) $format = $this->scalarTypes[$id]; if ($deserialize && isset($format['deserializer'])) { + if ($outputClass !== null) { + return TypeSerializer::{$format['deserializer']}($value, $outputClass); + } + return TypeSerializer::{$format['deserializer']}($value); } diff --git a/src/Types/TypeSerializer.php b/src/Types/TypeSerializer.php index 654b7d8..4df2cd7 100644 --- a/src/Types/TypeSerializer.php +++ b/src/Types/TypeSerializer.php @@ -5,24 +5,37 @@ namespace OnMoon\OpenApiServerBundle\Types; use DateTime; +use DateTimeInterface; use function base64_encode; use function Safe\base64_decode; class TypeSerializer { - public static function deserializeDate(string $date): DateTime + private const DESERIALIZATION_DATE_FORMAT = 'Y-m-d'; + private const SERIALIZATION_DATE_FORMAT = 'c'; + + + public static function deserializeDate(string $date, ?DateTimeInterface $dateTimeClass = null): DateTimeInterface { - return \Safe\DateTime::createFromFormat('Y-m-d', $date); + if ($dateTimeClass !== null) { + return $dateTimeClass::createFromFormat(self::DESERIALIZATION_DATE_FORMAT, $date); + } + + return \Safe\DateTime::createFromFormat(self::DESERIALIZATION_DATE_FORMAT, $date); } public static function serializeDate(DateTime $date): string { - return $date->format('Y-m-d'); + return $date->format(self::SERIALIZATION_DATE_FORMAT); } - public static function deserializeDateTime(string $date): DateTime + public static function deserializeDateTime(string $date, ?DateTimeInterface $dateTimeClass = null): DateTimeInterface { + if ($dateTimeClass !== null) { + return new $dateTimeClass($date); + } + return new \Safe\DateTime($date); }