From ca00c351b58d05815d61fcc65ab843d3ac88f699 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 2 Oct 2026 16:05:25 +0200 Subject: [PATCH] Set date and date-time formats on Carbon and DateTime string method calls --- src/ScrambleServiceProvider.php | 2 + .../DateTimeMethodReturnTypeExtension.php | 53 +++++++++++++++ .../DateTimeMethodReturnTypeExtensionTest.php | 66 +++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 src/Support/InferExtensions/DateTimeMethodReturnTypeExtension.php create mode 100644 tests/Support/InferExtensions/DateTimeMethodReturnTypeExtensionTest.php diff --git a/src/ScrambleServiceProvider.php b/src/ScrambleServiceProvider.php index 9f1d11e..47a1999 100644 --- a/src/ScrambleServiceProvider.php +++ b/src/ScrambleServiceProvider.php @@ -47,6 +47,7 @@ use Dedoc\Scramble\Support\InferExtensions\AfterResourceCollectionDefinitionCrea use Dedoc\Scramble\Support\InferExtensions\AfterResponseDefinitionCreatedExtension; use Dedoc\Scramble\Support\InferExtensions\ArrayMergeReturnTypeExtension; use Dedoc\Scramble\Support\InferExtensions\ArrStaticMethodReturnTypeExtension; +use Dedoc\Scramble\Support\InferExtensions\DateTimeMethodReturnTypeExtension; use Dedoc\Scramble\Support\InferExtensions\EloquentBuilderExtension; use Dedoc\Scramble\Support\InferExtensions\FacadeStaticMethodReturnTypeExtension; use Dedoc\Scramble\Support\InferExtensions\JsonApiResourceCollectionMethodReturnTypeExtension; @@ -213,6 +214,7 @@ class ScrambleServiceProvider extends PackageServiceProvider new PaginateMethodsReturnTypeExtension, new FacadeStaticMethodReturnTypeExtension, new ArrStaticMethodReturnTypeExtension, + new DateTimeMethodReturnTypeExtension, new ValidatorTypeInfer, new ResourceCollectionTypeInfer, diff --git a/src/Support/InferExtensions/DateTimeMethodReturnTypeExtension.php b/src/Support/InferExtensions/DateTimeMethodReturnTypeExtension.php new file mode 100644 index 0000000..af50811 --- /dev/null +++ b/src/Support/InferExtensions/DateTimeMethodReturnTypeExtension.php @@ -0,0 +1,53 @@ +isInstanceOf(DateTimeInterface::class); + } + + public function getMethodReturnType(MethodCallEvent $event): ?Type + { + return match ($event->name) { + 'toDateString' => $this->makeStringType('date'), + 'toDateTimeString', 'toIso8601String' => $this->makeStringType('date-time'), + 'format' => $this->makeStringType($this->getFormatMethodSchemaFormat($event->getArg('format', 0))), + default => null, + }; + } + + private function getFormatMethodSchemaFormat(Type $format): ?string + { + if (! $format instanceof LiteralStringType) { + return null; + } + + return match ($format->value) { + 'Y-m-d' => 'date', + 'Y-m-d H:i:s' => 'date-time', + default => null, + }; + } + + private function makeStringType(?string $format): StringType + { + $type = new StringType; + + if ($format) { + $type->setAttribute('format', $format); + } + + return $type; + } +} diff --git a/tests/Support/InferExtensions/DateTimeMethodReturnTypeExtensionTest.php b/tests/Support/InferExtensions/DateTimeMethodReturnTypeExtensionTest.php new file mode 100644 index 0000000..73ef10c --- /dev/null +++ b/tests/Support/InferExtensions/DateTimeMethodReturnTypeExtensionTest.php @@ -0,0 +1,66 @@ +toString())->toBe('string') + ->and($type->getAttribute('format'))->toBe($format); +})->with([ + ['(new \Carbon\Carbon)->toDateString()', 'date'], + ['(new \Carbon\Carbon)->toDateTimeString()', 'date-time'], + ['(new \Carbon\Carbon)->toIso8601String()', 'date-time'], + ["(new \Carbon\Carbon)->format('Y-m-d')", 'date'], + ["(new \Carbon\Carbon)->format('Y-m-d H:i:s')", 'date-time'], + ["(new \Carbon\Carbon)->format('d.m.Y')", null], + ['(new \Carbon\CarbonImmutable)->toDateString()', 'date'], + ['(new \Illuminate\Support\Carbon)->toDateTimeString()', 'date-time'], + ["(new \DateTime)->format('Y-m-d')", 'date'], + ["(new \DateTimeImmutable)->format('Y-m-d H:i:s')", 'date-time'], +]); + +it('documents format of date time method calls in json resource', function () { + $context = new OpenApiContext(new OpenApi('3.1.0'), new GeneratorConfig); + $transformer = app()->make(TypeTransformer::class, ['context' => $context]); + + $transformer->transform(new ObjectType(DateTimeMethodReturnTypeExtensionTest_Resource::class)); + + expect($context->openApi->components->getSchema(DateTimeMethodReturnTypeExtensionTest_Resource::class)->toArray()['properties'])->toBe([ + 'date' => ['type' => 'string', 'format' => 'date'], + 'date_time' => ['type' => 'string', 'format' => 'date-time'], + 'iso' => ['type' => 'string', 'format' => 'date-time'], + 'formatted_date' => ['type' => 'string', 'format' => 'date'], + 'formatted_date_time' => ['type' => 'string', 'format' => 'date-time'], + 'formatted_custom' => ['type' => 'string'], + 'nullable_date' => ['type' => ['string', 'null'], 'format' => 'date'], + ]); +}); + +/** + * @property SamplePostModel $resource + */ +class DateTimeMethodReturnTypeExtensionTest_Resource extends JsonResource +{ + public function toArray($request) + { + return [ + 'date' => $this->created_at->toDateString(), + 'date_time' => $this->created_at->toDateTimeString(), + 'iso' => $this->created_at->toIso8601String(), + 'formatted_date' => $this->created_at->format('Y-m-d'), + 'formatted_date_time' => $this->created_at->format('Y-m-d H:i:s'), + 'formatted_custom' => $this->created_at->format('d.m.Y'), + 'nullable_date' => $this->approved_at?->toDateString(), + ]; + } +}